/* * Copyright (C) 2014 Red Hat * All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ package org.opendaylight.aaa.idpmapping; import java.io.IOException; import java.io.StringWriter; import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.EnumSet; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.opendaylight.aaa.idpmapping.IdpJson; import org.opendaylight.aaa.idpmapping.Token; enum ProcessResult { RULE_FAIL, RULE_SUCCESS, BLOCK_CONTINUE, STATEMENT_CONTINUE } /** * Evaluate a set of rules against an assertion from an external * Identity Provider (IdP) mapping those assertion values to local * values. * * @author John Dennis */ public class RuleProcessor { private static final Logger logger = LoggerFactory .getLogger(RuleProcessor.class); public String ruleIdFormat = ""; public String statementIdFormat = ""; /* * Reserved variables */ public static final String ASSERTION = "assertion"; public static final String RULE_NUMBER = "rule_number"; public static final String RULE_NAME = "rule_name"; public static final String BLOCK_NUMBER = "block_number"; public static final String BLOCK_NAME = "block_name"; public static final String STATEMENT_NUMBER = "statement_number"; public static final String REGEXP_ARRAY_VARIABLE = "regexp_array"; public static final String REGEXP_MAP_VARIABLE = "regexp_map"; private static final String REGEXP_NAMED_GROUP_PAT = "\\(\\?<([a-zA-Z][a-zA-Z0-9]*)>"; private static final Pattern REGEXP_NAMED_GROUP_RE = Pattern.compile(REGEXP_NAMED_GROUP_PAT); List> rules = null; boolean success = true; Map> mappings = null; public RuleProcessor(java.io.Reader rulesIn, Map> mappings) { this.mappings = mappings; IdpJson json = new IdpJson(); @SuppressWarnings("unchecked") List> loadJson = (List>) json.loadJson(rulesIn); rules = loadJson; } public RuleProcessor(Path rulesIn, Map> mappings) throws IOException { this.mappings = mappings; IdpJson json = new IdpJson(); @SuppressWarnings("unchecked") List> loadJson = (List>) json.loadJson(rulesIn); rules = loadJson; } public RuleProcessor(String rulesIn, Map> mappings) { this.mappings = mappings; IdpJson json = new IdpJson(); @SuppressWarnings("unchecked") List> loadJson = (List>) json.loadJson(rulesIn); rules = loadJson; } /* * For some odd reason the Java Regular Expression API does not include a way to retrieve a map of * the named groups and their values. The API only permits us to retrieve a named group if we * already know the group names. So instead we parse the pattern string looking for named groups, * extract the name, look up the value of the named group and build a map from that. */ private Map regexpGroupMap(String pattern, Matcher matcher) { Map groupMap = new HashMap(); Matcher groupMatcher = REGEXP_NAMED_GROUP_RE.matcher(pattern); while (groupMatcher.find()) { String groupName = groupMatcher.group(1); groupMap.put(groupName, matcher.group(groupName)); } return groupMap; } static public String join(List list, String conjunction) { StringBuilder sb = new StringBuilder(); boolean first = true; for (Object item : list) { if (first) { first = false; } else { sb.append(conjunction); } sb.append(item.toString()); } return sb.toString(); } private List regexpGroupList(Matcher matcher) { List groupList = new ArrayList(matcher.groupCount() + 1); groupList.add(0, matcher.group(0)); for (int i = 1; i < matcher.groupCount() + 1; i++) { groupList.add(i, matcher.group(i)); } return groupList; } private String objToString(Object obj) { StringWriter sw = new StringWriter(); objToStringItem(sw, obj); return sw.toString(); } private void objToStringItem(StringWriter sw, Object obj) { // ordered by expected occurrence if (obj instanceof String) { sw.write('"'); sw.write(((String) obj).replaceAll("\"", "\\\"")); sw.write('"'); } else if (obj instanceof List) { @SuppressWarnings("unchecked") List list = (List) obj; boolean first = true; sw.write('['); for (Object item : list) { if (first) { first = false; } else { sw.write(", "); } objToStringItem(sw, item); } sw.write(']'); } else if (obj instanceof Map) { @SuppressWarnings("unchecked") Map map = (Map) obj; boolean first = true; sw.write('{'); for (Map.Entry entry : map.entrySet()) { String key = entry.getKey(); Object value = entry.getValue(); if (first) { first = false; } else { sw.write(", "); } objToStringItem(sw, key); sw.write(": "); objToStringItem(sw, value); } sw.write('}'); } else if (obj instanceof Long) { sw.write(((Long) obj).toString()); } else if (obj instanceof Boolean) { sw.write(((Boolean) obj).toString()); } else if (obj == null) { sw.write("null"); } else if (obj instanceof Double) { sw.write(((Double) obj).toString()); } else { throw new IllegalStateException( String .format( "unsupported data type, must be String, Long, Double, Boolean, List, Map, or null, not %s", obj.getClass().getSimpleName())); } } private Object deepCopy(Object obj) { // ordered by expected occurrence if (obj instanceof String) { return obj; // immutable } else if (obj instanceof List) { List new_list = new ArrayList(); @SuppressWarnings("unchecked") List list = (List) obj; for (Object item : list) { new_list.add(deepCopy(item)); } return new_list; } else if (obj instanceof Map) { Map new_map = new LinkedHashMap(); @SuppressWarnings("unchecked") Map map = (Map) obj; for (Map.Entry entry : map.entrySet()) { String key = entry.getKey(); // immutable Object value = entry.getValue(); new_map.put(key, deepCopy(value)); } return new_map; } else if (obj instanceof Long) { return obj; // immutable } else if (obj instanceof Boolean) { return obj; // immutable } else if (obj == null) { return null; } else if (obj instanceof Double) { return obj; // immutable } else { throw new IllegalStateException( String .format( "unsupported data type, must be String, Long, Double, Boolean, List, Map, or null, not %s", obj.getClass().getSimpleName())); } } public String ruleId(Map namespace) { return substituteVariables(ruleIdFormat, namespace); } public String statementId(Map namespace) { return substituteVariables(statementIdFormat, namespace); } public String substituteVariables(String string, Map namespace) { StringBuffer sb = new StringBuffer(); Matcher matcher = Token.VARIABLE_RE.matcher(string); while (matcher.find()) { Token token = new Token(matcher.group(0), namespace); token.load(); String replacement; if (token.type == TokenType.STRING) { replacement = token.getStringValue(); } else { replacement = objToString(token.getObjectValue()); } matcher.appendReplacement(sb, replacement); } matcher.appendTail(sb); return sb.toString(); } Map getMapping(Map namespace, Map rule) { Map mapping = null; String mappingName = null; try { @SuppressWarnings("unchecked") Map map = (Map) rule.get("mapping"); mapping = map; } catch (java.lang.ClassCastException e) { throw new InvalidRuleException(String.format("%s rule defines 'mapping' but it is not a Map", this.ruleId(namespace))); } if (mapping != null) { return mapping; } try { mappingName = (String) rule.get("mapping_name"); } catch (java.lang.ClassCastException e) { throw new InvalidRuleException(String.format( "%s rule defines 'mapping_name' but it is not a string", this.ruleId(namespace))); } if (mappingName == null) { throw new InvalidRuleException(String.format( "%s rule does not define mapping nor mapping_name unable to load mapping", this.ruleId(namespace))); } mapping = this.mappings.get(mappingName); if (mapping == null) { throw new InvalidRuleException( String .format( "%s rule specifies mapping_name '%s' but a mapping by that name does not exist, unable to load mapping", this.ruleId(namespace))); } logger.debug(String.format("using named mapping '%s' from rule %s mapping=%s", mappingName, this.ruleId(namespace), mapping)); return mapping; } private String getVerb(List statement) { Token verb; if (statement.size() < 1) { throw new InvalidRuleException("statement has no verb"); } try { verb = new Token(statement.get(0), null); } catch (Exception e) { throw new InvalidRuleException( String.format("statement first member (i.e. verb) error %s", e)); } if (verb.type != TokenType.STRING) { throw new InvalidRuleException(String.format( "statement first member (i.e. verb) must be a string, not %s", verb.type)); } return (verb.getStringValue()).toLowerCase(); } private Token getToken(String verb, List statement, int index, Map namespace, Set storageTypes, Set tokenTypes) { Object item; Token token; try { item = statement.get(index); } catch (IndexOutOfBoundsException e) { throw new InvalidRuleException(String.format( "verb '%s' requires at least %d items but only %d are available.", verb, index + 1, statement.size())); } try { token = new Token(item, namespace); } catch (Exception e) { throw new StatementErrorException(String.format("parameter %d, %s", index, e)); } if (storageTypes != null) { if (!storageTypes.contains(token.storageType)) { throw new InvalidTypeException(String.format( "verb '%s' requires parameter #%d to have storage types %s not %s. statement=%s", verb, index, storageTypes, statement)); } } if (tokenTypes != null) { token.load(); // Note, Token.load() sets the Token.type if (!tokenTypes.contains(token.type)) { throw new InvalidTypeException(String.format( "verb '%s' requires parameter #%d to have types %s, not %s. statement=%s", verb, index, tokenTypes, statement)); } } return token; } private Token getParameter(String verb, List statement, int index, Map namespace, Set tokenTypes) { Object item; Token token; try { item = statement.get(index); } catch (IndexOutOfBoundsException e) { throw new InvalidRuleException(String.format( "verb '%s' requires at least %d items but only %d are available.", verb, index + 1, statement.size())); } try { token = new Token(item, namespace); } catch (Exception e) { throw new StatementErrorException(String.format("parameter %d, %s", index, e)); } token.load(); if (tokenTypes != null) { try { token.get(); // Note, Token.get() sets the Token.type } catch (UndefinedValueException e) { // OK if not yet defined } if (!tokenTypes.contains(token.type)) { throw new InvalidTypeException(String.format( "verb '%s' requires parameter #%d to have types %s, not %s. statement=%s", verb, index, tokenTypes, item.getClass().getSimpleName(), statement)); } } return token; } private Object getRawParameter(String verb, List statement, int index, Set tokenTypes) { Object item; try { item = statement.get(index); } catch (IndexOutOfBoundsException e) { throw new InvalidRuleException(String.format( "verb '%s' requires at least %d items but only %d are available.", verb, index + 1, statement.size())); } if (tokenTypes != null) { TokenType itemType = Token.classify(item); if (!tokenTypes.contains(itemType)) { throw new InvalidTypeException(String.format( "verb '%s' requires parameter #%d to have types %s, not %s. statement=%s", verb, index, tokenTypes, statement)); } } return item; } private Token getVariable(String verb, List statement, int index, Map namespace) { Object item; Token token; try { item = statement.get(index); } catch (IndexOutOfBoundsException e) { throw new InvalidRuleException(String.format( "verb '%s' requires at least %d items but only %d are available.", verb, index + 1, statement.size())); } try { token = new Token(item, namespace); } catch (Exception e) { throw new StatementErrorException(String.format("parameter %d, %s", index, e)); } if (token.storageType != TokenStorageType.VARIABLE) { throw new InvalidTypeException(String.format( "verb '%s' requires parameter #%d to be a variable not %s. statement=%s", verb, index, token.storageType, statement)); } return token; } public Map process(String assertionJson) { ProcessResult result; IdpJson json = new IdpJson(); @SuppressWarnings("unchecked") Map assertion = (Map) json.loadJson(assertionJson); System.out.println(assertionJson); System.out.println(json.dumpJson(assertion)); this.success = true; for (int ruleNumber = 0; ruleNumber < this.rules.size(); ruleNumber++) { Map namespace = new HashMap(); Map rule = (Map) this.rules.get(ruleNumber); namespace.put(RULE_NUMBER, new Long(ruleNumber)); namespace.put(RULE_NAME, new String("")); namespace.put(ASSERTION, deepCopy(assertion)); result = processRule(namespace, rule); if (result == ProcessResult.RULE_SUCCESS) { Map mapped = new LinkedHashMap(); Map mapping = getMapping(namespace, rule); for (Map.Entry entry : ((Map) mapping).entrySet()) { String key = entry.getKey(); Object value = entry.getValue(); Object newValue = null; try { Token token = new Token(value, namespace); newValue = token.get(); } catch (Exception e) { throw new InvalidRuleException(String.format( "%s unable to get value for mapping %s=%s, %s", ruleId(namespace), key, value, e), e); } mapped.put(key, newValue); } return mapped; } } return null; } private ProcessResult processRule(Map namespace, Map rule) { ProcessResult result = ProcessResult.BLOCK_CONTINUE; @SuppressWarnings("unchecked") List>> statementBlocks = (List>>) rule.get("statement_blocks"); if (statementBlocks == null) { throw new InvalidRuleException("rule missing 'statement_blocks'"); } for (int blockNumber = 0; blockNumber < statementBlocks.size(); blockNumber++) { List> block = (List>) statementBlocks.get(blockNumber); namespace.put(BLOCK_NUMBER, new Long(blockNumber)); namespace.put(BLOCK_NAME, ""); result = processBlock(namespace, block); System.out.println(); if (EnumSet.of(ProcessResult.RULE_SUCCESS, ProcessResult.RULE_FAIL).contains(result)) { break; } else if (result == ProcessResult.BLOCK_CONTINUE) { continue; } else { throw new IllegalStateException(String.format("%s unexpected statement result: %s", result)); } } if (EnumSet.of(ProcessResult.RULE_SUCCESS, ProcessResult.BLOCK_CONTINUE).contains(result)) { return ProcessResult.RULE_SUCCESS; } else { return ProcessResult.RULE_FAIL; } } private ProcessResult processBlock(Map namespace, List> block) { ProcessResult result = ProcessResult.STATEMENT_CONTINUE; for (int statementNumber = 0; statementNumber < block.size(); statementNumber++) { List statement = (List) block.get(statementNumber); namespace.put(STATEMENT_NUMBER, new Long(statementNumber)); try { result = processStatement(namespace, statement); } catch (Exception e) { throw new IllegalStateException(String.format("%s statement=%s %s", statementId(namespace), statement, e), e); } if (EnumSet.of(ProcessResult.BLOCK_CONTINUE, ProcessResult.RULE_SUCCESS, ProcessResult.RULE_FAIL).contains(result)) { break; } else if (result == ProcessResult.STATEMENT_CONTINUE) { continue; } else { throw new IllegalStateException(String.format("%s unexpected statement result: %s", result)); } } if (result == ProcessResult.STATEMENT_CONTINUE) { result = ProcessResult.BLOCK_CONTINUE; } return result; } private ProcessResult processStatement(Map namespace, List statement) { ProcessResult result = ProcessResult.STATEMENT_CONTINUE; String verb = getVerb(statement); switch (verb) { case "set": result = verbSet(verb, namespace, statement); break; case "length": result = verbLength(verb, namespace, statement); break; case "interpolate": result = verbInterpolate(verb, namespace, statement); break; case "append": result = verbAppend(verb, namespace, statement); break; case "unique": result = verbUnique(verb, namespace, statement); break; case "split": result = verbSplit(verb, namespace, statement); break; case "join": result = verbJoin(verb, namespace, statement); break; case "lower": result = verbLower(verb, namespace, statement); break; case "upper": result = verbUpper(verb, namespace, statement); break; case "in": result = verbIn(verb, namespace, statement); break; case "not_in": result = verbNotIn(verb, namespace, statement); break; case "compare": result = verbCompare(verb, namespace, statement); break; case "regexp": result = verbRegexp(verb, namespace, statement); break; case "regexp_replace": result = verbRegexpReplace(verb, namespace, statement); break; case "exit": result = verbExit(verb, namespace, statement); break; case "continue": result = verbContinue(verb, namespace, statement); break; default: throw new InvalidRuleException(String.format("unknown verb '%s'", verb)); } return result; } private ProcessResult verbSet(String verb, Map namespace, List statement) { Token variable = getVariable(verb, statement, 1, namespace); Token parameter = getParameter(verb, statement, 2, namespace, null); variable.set(parameter.getObjectValue()); this.success = true; if (logger.isDebugEnabled()) { logger.debug(String.format("%s verb='%s' success=%s variable: %s=%s", statementId(namespace), verb, this.success, variable, variable.get())); } return ProcessResult.STATEMENT_CONTINUE; } private ProcessResult verbLength(String verb, Map namespace, List statement) { Token variable = getVariable(verb, statement, 1, namespace); Token parameter = getParameter(verb, statement, 2, namespace, EnumSet.of(TokenType.ARRAY, TokenType.MAP, TokenType.STRING)); long length; switch (parameter.type) { case ARRAY: { length = parameter.getListValue().size(); } break; case MAP: { length = parameter.getMapValue().size(); } break; case STRING: { length = parameter.getStringValue().length(); } break; default: throw new IllegalStateException(String.format("unexpected token type: %s", parameter.type)); } variable.set(new Long(length)); this.success = true; if (logger.isDebugEnabled()) { logger.debug(String.format("%s verb='%s' success=%s variable: %s=%s parameter=%s", statementId(namespace), verb, this.success, variable, variable.get(), parameter.getObjectValue())); } return ProcessResult.STATEMENT_CONTINUE; } private ProcessResult verbInterpolate(String verb, Map namespace, List statement) { Token variable = getVariable(verb, statement, 1, namespace); String string = (String) getRawParameter(verb, statement, 2, EnumSet.of(TokenType.STRING)); String newValue = null; try { newValue = substituteVariables(string, namespace); } catch (Exception e) { throw new InvalidValueException(String.format( "verb '%s' failed, variable='%s' string='%s': %s", verb, variable, string, e)); } variable.set(newValue); this.success = true; if (logger.isDebugEnabled()) { logger.debug(String.format("%s verb='%s' success=%s variable: %s=%s string='%s'", statementId(namespace), verb, this.success, variable, variable.get(), string)); } return ProcessResult.STATEMENT_CONTINUE; } private ProcessResult verbAppend(String verb, Map namespace, List statement) { Token variable = getToken(verb, statement, 1, namespace, EnumSet.of(TokenStorageType.VARIABLE), EnumSet.of(TokenType.ARRAY)); Token item = getParameter(verb, statement, 2, namespace, null); try { List list = variable.getListValue(); list.add(item.getObjectValue()); } catch (Exception e) { throw new InvalidValueException(String.format( "verb '%s' failed, variable='%s' item='%s': %s", verb, variable.getObjectValue(), item.getObjectValue(), e)); } this.success = true; if (logger.isDebugEnabled()) { logger.debug(String.format("%s verb='%s' success=%s variable: %s=%s item=%s", statementId(namespace), verb, this.success, variable, variable.get(), item.getObjectValue())); } return ProcessResult.STATEMENT_CONTINUE; } private ProcessResult verbUnique(String verb, Map namespace, List statement) { Token variable = getVariable(verb, statement, 1, namespace); Token array = getParameter(verb, statement, 2, namespace, EnumSet.of(TokenType.ARRAY)); List newValue = new ArrayList(); Set seen = new HashSet(); for (Object member : array.getListValue()) { if (seen.contains(member)) { continue; } else { newValue.add(member); seen.add(member); } } variable.set(newValue); this.success = true; if (logger.isDebugEnabled()) { logger.debug(String.format("%s verb='%s' success=%s variable: %s=%s array=%s", statementId(namespace), verb, this.success, variable, variable.get(), array.getObjectValue())); } return ProcessResult.STATEMENT_CONTINUE; } private ProcessResult verbSplit(String verb, Map namespace, List statement) { Token variable = getVariable(verb, statement, 1, namespace); Token string = getParameter(verb, statement, 2, namespace, EnumSet.of(TokenType.STRING)); Token pattern = getParameter(verb, statement, 3, namespace, EnumSet.of(TokenType.STRING)); Pattern regexp; List newValue; try { regexp = Pattern.compile(pattern.getStringValue()); } catch (Exception e) { throw new InvalidValueException(String.format( "verb '%s' failed, bad regular expression pattern '%s', %s", verb, pattern.getObjectValue(), e)); } try { newValue = new ArrayList(Arrays.asList(regexp.split((String) string.getStringValue()))); } catch (Exception e) { throw new InvalidValueException(String.format( "verb '%s' failed, string='%s' pattern='%s', %s", verb, string.getObjectValue(), pattern.getObjectValue(), e)); } variable.set(newValue); this.success = true; if (logger.isDebugEnabled()) { logger.debug(String.format("%s verb='%s' success=%s variable: %s=%s string='%s' pattern='%s'", statementId(namespace), verb, this.success, variable, variable.get(), string.getObjectValue(), pattern.getObjectValue())); } return ProcessResult.STATEMENT_CONTINUE; } private ProcessResult verbJoin(String verb, Map namespace, List statement) { Token variable = getVariable(verb, statement, 1, namespace); Token array = getParameter(verb, statement, 2, namespace, EnumSet.of(TokenType.ARRAY)); Token conjunction = getParameter(verb, statement, 3, namespace, EnumSet.of(TokenType.STRING)); String newValue; try { newValue = join(array.getListValue(), conjunction.getStringValue()); } catch (Exception e) { throw new InvalidValueException(String.format( "verb '%s' failed, array=%s conjunction='%s', %s", verb, array.getObjectValue(), conjunction.getObjectValue(), e)); } variable.set(newValue); this.success = true; if (logger.isDebugEnabled()) { logger.debug(String.format( "%s verb='%s' success=%s variable: %s=%s array='%s' conjunction='%s'", statementId(namespace), verb, this.success, variable, variable.get(), array.getObjectValue(), conjunction.getObjectValue())); } return ProcessResult.STATEMENT_CONTINUE; } private ProcessResult verbLower(String verb, Map namespace, List statement) { Token variable = getVariable(verb, statement, 1, namespace); Token parameter = getParameter(verb, statement, 2, namespace, EnumSet.of(TokenType.STRING, TokenType.ARRAY, TokenType.MAP)); try { switch (parameter.type) { case STRING: { String oldValue = parameter.getStringValue(); String newValue; newValue = oldValue.toLowerCase(); variable.set(newValue); } break; case ARRAY: { List oldValue = parameter.getListValue(); List newValue = new ArrayList(oldValue.size()); String oldItem; String newItem; for (Object item : oldValue) { try { oldItem = (String) item; } catch (ClassCastException e) { throw new InvalidValueException(String.format( "verb '%s' failed, array item (%s) is not a string, array=%s", verb, item, parameter.getObjectValue())); } newItem = oldItem.toLowerCase(); newValue.add(newItem); } variable.set(newValue); } break; case MAP: { Map oldValue = parameter.getMapValue(); Map newValue = new LinkedHashMap(oldValue.size()); for (Map.Entry entry : oldValue.entrySet()) { String oldKey; String newKey; Object value = entry.getValue(); oldKey = entry.getKey(); newKey = oldKey.toLowerCase(); newValue.put(newKey, value); } variable.set(newValue); } break; default: throw new IllegalStateException( String.format("unexpected token type: %s", parameter.type)); } } catch (Exception e) { throw new InvalidValueException(String.format( "verb '%s' failed, variable='%s' parameter='%s': %s", verb, variable, parameter.getObjectValue(), e), e); } this.success = true; if (logger.isDebugEnabled()) { logger.debug(String.format("%s verb='%s' success=%s variable: %s=%s parameter=%s", statementId(namespace), verb, this.success, variable, variable.get(), parameter.getObjectValue())); } return ProcessResult.STATEMENT_CONTINUE; } private ProcessResult verbUpper(String verb, Map namespace, List statement) { Token variable = getVariable(verb, statement, 1, namespace); Token parameter = getParameter(verb, statement, 2, namespace, EnumSet.of(TokenType.STRING, TokenType.ARRAY, TokenType.MAP)); try { switch (parameter.type) { case STRING: { String oldValue = parameter.getStringValue(); String newValue; newValue = oldValue.toUpperCase(); variable.set(newValue); } break; case ARRAY: { List oldValue = parameter.getListValue(); List newValue = new ArrayList(oldValue.size()); String oldItem; String newItem; for (Object item : oldValue) { try { oldItem = (String) item; } catch (ClassCastException e) { throw new InvalidValueException(String.format( "verb '%s' failed, array item (%s) is not a string, array=%s", verb, item, parameter.getObjectValue())); } newItem = oldItem.toUpperCase(); newValue.add(newItem); } variable.set(newValue); } break; case MAP: { Map oldValue = parameter.getMapValue(); Map newValue = new LinkedHashMap(oldValue.size()); for (Map.Entry entry : oldValue.entrySet()) { String oldKey; String newKey; Object value = entry.getValue(); oldKey = entry.getKey(); newKey = oldKey.toUpperCase(); newValue.put(newKey, value); } variable.set(newValue); } break; default: throw new IllegalStateException( String.format("unexpected token type: %s", parameter.type)); } } catch (Exception e) { throw new InvalidValueException(String.format( "verb '%s' failed, variable='%s' parameter='%s': %s", verb, variable, parameter.getObjectValue(), e), e); } this.success = true; if (logger.isDebugEnabled()) { logger.debug(String.format("%s verb='%s' success=%s variable: %s=%s parameter=%s", statementId(namespace), verb, this.success, variable, variable.get(), parameter.getObjectValue())); } return ProcessResult.STATEMENT_CONTINUE; } private ProcessResult verbIn(String verb, Map namespace, List statement) { Token member = getParameter(verb, statement, 1, namespace, null); Token collection = getParameter(verb, statement, 2, namespace, EnumSet.of(TokenType.ARRAY, TokenType.MAP, TokenType.STRING)); switch (collection.type) { case ARRAY: { this.success = collection.getListValue().contains(member.getObjectValue()); } break; case MAP: { if (member.type != TokenType.STRING) { throw new InvalidTypeException(String.format( "verb '%s' requires parameter #1 to be a %swhen parameter #2 is a %s", TokenType.STRING, collection.type)); } this.success = collection.getMapValue().containsKey(member.getObjectValue()); } break; case STRING: { if (member.type != TokenType.STRING) { throw new InvalidTypeException(String.format( "verb '%s' requires parameter #1 to be a %swhen parameter #2 is a %s", TokenType.STRING, collection.type)); } this.success = (collection.getStringValue()).contains(member.getStringValue()); } break; default: throw new IllegalStateException(String.format("unexpected token type: %s", collection.type)); } if (logger.isDebugEnabled()) { logger.debug(String.format("%s verb='%s' success=%s member=%s collection=%s", statementId(namespace), verb, this.success, member.getObjectValue(), collection.getObjectValue())); } return ProcessResult.STATEMENT_CONTINUE; } private ProcessResult verbNotIn(String verb, Map namespace, List statement) { Token member = getParameter(verb, statement, 1, namespace, null); Token collection = getParameter(verb, statement, 2, namespace, EnumSet.of(TokenType.ARRAY, TokenType.MAP, TokenType.STRING)); switch (collection.type) { case ARRAY: { this.success = !collection.getListValue().contains(member.getObjectValue()); } break; case MAP: { if (member.type != TokenType.STRING) { throw new InvalidTypeException(String.format( "verb '%s' requires parameter #1 to be a %swhen parameter #2 is a %s", TokenType.STRING, collection.type)); } this.success = !collection.getMapValue().containsKey(member.getObjectValue()); } break; case STRING: { if (member.type != TokenType.STRING) { throw new InvalidTypeException(String.format( "verb '%s' requires parameter #1 to be a %swhen parameter #2 is a %s", TokenType.STRING, collection.type)); } this.success = !(collection.getStringValue()).contains(member.getStringValue()); } break; default: throw new IllegalStateException(String.format("unexpected token type: %s", collection.type)); } if (logger.isDebugEnabled()) { logger.debug(String.format("%s verb='%s' success=%s member=%s collection=%s", statementId(namespace), verb, this.success, member.getObjectValue(), collection.getObjectValue())); } return ProcessResult.STATEMENT_CONTINUE; } private ProcessResult verbCompare(String verb, Map namespace, List statement) { Token left = getParameter(verb, statement, 1, namespace, null); Token op = getParameter(verb, statement, 2, namespace, EnumSet.of(TokenType.STRING)); Token right = getParameter(verb, statement, 3, namespace, null); String invalidOp = "operator %s not supported for type %s"; TokenType tokenType; String opValue = op.getStringValue(); boolean result; if (left.type != right.type) { throw new InvalidTypeException(String.format( "verb '%s' both items must have the same type left is %s and right is %s", verb, left.type, right.type)); } else { tokenType = left.type; } switch (opValue) { case "==": case "!=": { switch (tokenType) { case STRING: { String leftValue = left.getStringValue(); String rightValue = right.getStringValue(); result = leftValue.equals(rightValue); } break; case INTEGER: { Long leftValue = left.getLongValue(); Long rightValue = right.getLongValue(); result = leftValue.equals(rightValue); } break; case REAL: { Double leftValue = left.getDoubleValue(); Double rightValue = right.getDoubleValue(); result = leftValue.equals(rightValue); } break; case ARRAY: { List leftValue = left.getListValue(); List rightValue = right.getListValue(); result = leftValue.equals(rightValue); } break; case MAP: { Map leftValue = left.getMapValue(); Map rightValue = right.getMapValue(); result = leftValue.equals(rightValue); } break; case BOOLEAN: { Boolean leftValue = left.getBooleanValue(); Boolean rightValue = right.getBooleanValue(); result = leftValue.equals(rightValue); } break; case NULL: { result = (left.getNullValue() == right.getNullValue()); } break; default: { throw new IllegalStateException(String.format("unexpected token type: %s", tokenType)); } } if (opValue.equals("!=")) { // negate the sense of the test result = !result; } } break; case "<": case ">=": { switch (tokenType) { case STRING: { String leftValue = left.getStringValue(); String rightValue = right.getStringValue(); result = leftValue.compareTo(rightValue) < 0; } break; case INTEGER: { Long leftValue = left.getLongValue(); Long rightValue = right.getLongValue(); result = leftValue < rightValue; } break; case REAL: { Double leftValue = left.getDoubleValue(); Double rightValue = right.getDoubleValue(); result = leftValue < rightValue; } break; case ARRAY: case MAP: case BOOLEAN: case NULL: { throw new InvalidRuleException(String.format(invalidOp, opValue, tokenType)); } default: { throw new IllegalStateException(String.format("unexpected token type: %s", tokenType)); } } if (opValue.equals(">=")) { // negate the sense of the test result = !result; } } break; case ">": case "<=": { switch (tokenType) { case STRING: { String leftValue = left.getStringValue(); String rightValue = right.getStringValue(); result = leftValue.compareTo(rightValue) > 0; } break; case INTEGER: { Long leftValue = left.getLongValue(); Long rightValue = right.getLongValue(); result = leftValue > rightValue; } break; case REAL: { Double leftValue = left.getDoubleValue(); Double rightValue = right.getDoubleValue(); result = leftValue > rightValue; } break; case ARRAY: case MAP: case BOOLEAN: case NULL: { throw new InvalidRuleException(String.format(invalidOp, opValue, tokenType)); } default: { throw new IllegalStateException(String.format("unexpected token type: %s", tokenType)); } } if (opValue.equals("<=")) { // negate the sense of the test result = !result; } } break; default: { throw new InvalidRuleException(String.format( "verb '%s' has unknown comparison operator '%s'", verb, op.getObjectValue())); } } this.success = result; if (logger.isDebugEnabled()) { logger.debug(String.format("%s verb='%s' success=%s left=%s op='%s' right=%s", statementId(namespace), verb, this.success, left.getObjectValue(), op.getObjectValue(), right.getObjectValue())); } return ProcessResult.STATEMENT_CONTINUE; } private ProcessResult verbRegexp(String verb, Map namespace, List statement) { Token string = getParameter(verb, statement, 1, namespace, EnumSet.of(TokenType.STRING)); Token pattern = getParameter(verb, statement, 2, namespace, EnumSet.of(TokenType.STRING)); Pattern regexp; Matcher matcher; try { regexp = Pattern.compile(pattern.getStringValue()); } catch (Exception e) { throw new InvalidValueException(String.format( "verb '%s' failed, bad regular expression pattern '%s', %s", verb, pattern.getObjectValue(), e)); } matcher = regexp.matcher(string.getStringValue()); if (matcher.find()) { this.success = true; namespace.put(REGEXP_ARRAY_VARIABLE, regexpGroupList(matcher)); namespace.put(REGEXP_MAP_VARIABLE, regexpGroupMap(pattern.getStringValue(), matcher)); } else { this.success = false; namespace.put(REGEXP_ARRAY_VARIABLE, new ArrayList()); namespace.put(REGEXP_MAP_VARIABLE, new HashMap()); } if (logger.isDebugEnabled()) { logger.debug(String.format("%s verb='%s' success=%s string='%s' pattern='%s' %s=%s %s=%s", statementId(namespace), verb, this.success, string.getObjectValue(), pattern.getObjectValue(), REGEXP_ARRAY_VARIABLE, namespace.get(REGEXP_ARRAY_VARIABLE), REGEXP_MAP_VARIABLE, namespace.get(REGEXP_MAP_VARIABLE))); } return ProcessResult.STATEMENT_CONTINUE; } private ProcessResult verbRegexpReplace(String verb, Map namespace, List statement) { Token variable = getVariable(verb, statement, 1, namespace); Token string = getParameter(verb, statement, 2, namespace, EnumSet.of(TokenType.STRING)); Token pattern = getParameter(verb, statement, 3, namespace, EnumSet.of(TokenType.STRING)); Token replacement = getParameter(verb, statement, 4, namespace, EnumSet.of(TokenType.STRING)); Pattern regexp; Matcher matcher; String newValue; try { regexp = Pattern.compile(pattern.getStringValue()); } catch (Exception e) { throw new InvalidValueException(String.format( "verb '%s' failed, bad regular expression pattern '%s', %s", verb, pattern.getObjectValue(), e)); } matcher = regexp.matcher(string.getStringValue()); newValue = matcher.replaceAll(replacement.getStringValue()); variable.set(newValue); this.success = true; if (logger.isDebugEnabled()) { logger.debug(String.format( "%s verb='%s' success=%s variable: %s=%s string='%s' pattern='%s' replacement='%s'", statementId(namespace), verb, this.success, variable, variable.get(), string.getObjectValue(), pattern.getObjectValue(), replacement.getObjectValue())); } return ProcessResult.STATEMENT_CONTINUE; } private ProcessResult verbExit(String verb, Map namespace, List statement) { ProcessResult statementResult = ProcessResult.STATEMENT_CONTINUE; Token exitStatusParam = getParameter(verb, statement, 1, namespace, EnumSet.of(TokenType.STRING)); Token criteriaParam = getParameter(verb, statement, 2, namespace, EnumSet.of(TokenType.STRING)); String exitStatus = (exitStatusParam.getStringValue()).toLowerCase(); String criteria = (criteriaParam.getStringValue()).toLowerCase(); ProcessResult result; boolean doExit; if (exitStatus.equals("rule_succeeds")) { result = ProcessResult.RULE_SUCCESS; } else if (exitStatus.equals("rule_fails")) { result = ProcessResult.RULE_FAIL; } else { throw new InvalidRuleException(String.format("verb='%s' unknown exit status '%s'", verb, exitStatus)); } if (criteria.equals("if_success")) { if (this.success) { doExit = true; } else { doExit = false; } } else if (criteria.equals("if_not_success")) { if (!this.success) { doExit = true; } else { doExit = false; } } else if (criteria.equals("always")) { doExit = true; } else if (criteria.equals("never")) { doExit = false; } else { throw new InvalidRuleException(String.format("verb='%s' unknown exit criteria '%s'", verb, criteria)); } if (doExit) { statementResult = result; } if (logger.isDebugEnabled()) { logger.debug(String .format("%s verb='%s' success=%s status=%s criteria=%s exiting=%s result=%s", statementId(namespace), verb, this.success, exitStatus, criteria, doExit, statementResult)); } return statementResult; } private ProcessResult verbContinue(String verb, Map namespace, List statement) { ProcessResult statementResult = ProcessResult.STATEMENT_CONTINUE; Token criteriaParam = getParameter(verb, statement, 1, namespace, EnumSet.of(TokenType.STRING)); String criteria = (criteriaParam.getStringValue()).toLowerCase(); boolean doContinue; if (criteria.equals("if_success")) { if (this.success) { doContinue = true; } else { doContinue = false; } } else if (criteria.equals("if_not_success")) { if (!this.success) { doContinue = true; } else { doContinue = false; } } else if (criteria.equals("always")) { doContinue = true; } else if (criteria.equals("never")) { doContinue = false; } else { throw new InvalidRuleException(String.format("verb='%s' unknown continue criteria '%s'", verb, criteria)); } if (doContinue) { statementResult = ProcessResult.BLOCK_CONTINUE; } if (logger.isDebugEnabled()) { logger.debug(String.format("%s verb='%s' success=%s criteria=%s continuing=%s result=%s", statementId(namespace), verb, this.success, criteria, doContinue, statementResult)); } return statementResult; } }