aboutsummaryrefslogtreecommitdiffstats
path: root/upstream/odl-aaa-moon/aaa/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/RuleProcessor.java
diff options
context:
space:
mode:
Diffstat (limited to 'upstream/odl-aaa-moon/aaa/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/RuleProcessor.java')
-rw-r--r--upstream/odl-aaa-moon/aaa/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/RuleProcessor.java1368
1 files changed, 1368 insertions, 0 deletions
diff --git a/upstream/odl-aaa-moon/aaa/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/RuleProcessor.java b/upstream/odl-aaa-moon/aaa/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/RuleProcessor.java
new file mode 100644
index 00000000..0f86fde6
--- /dev/null
+++ b/upstream/odl-aaa-moon/aaa/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/RuleProcessor.java
@@ -0,0 +1,1368 @@
+/*
+ * Copyright (c) 2014 Red Hat, Inc. 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.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+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;
+
+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 <jdennis@redhat.com>
+ */
+
+public class RuleProcessor {
+ private static final Logger LOG = LoggerFactory.getLogger(RuleProcessor.class);
+
+ public String ruleIdFormat = "<rule [${rule_number}:\"${rule_name}\"]>";
+ public String statementIdFormat = "<rule [${rule_number}:\"${rule_name}\"] block [${block_number}:\"${block_name}\"] statement ${statement_number}>";
+
+ /*
+ * 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<Map<String, Object>> rules = null;
+ boolean success = true;
+ Map<String, Map<String, Object>> mappings = null;
+
+ public RuleProcessor(java.io.Reader rulesIn, Map<String, Map<String, Object>> mappings) {
+ this.mappings = mappings;
+ IdpJson json = new IdpJson();
+ @SuppressWarnings("unchecked")
+ List<Map<String, Object>> loadJson = (List<Map<String, Object>>) json.loadJson(rulesIn);
+ rules = loadJson;
+ }
+
+ public RuleProcessor(Path rulesIn, Map<String, Map<String, Object>> mappings)
+ throws IOException {
+ this.mappings = mappings;
+ IdpJson json = new IdpJson();
+ @SuppressWarnings("unchecked")
+ List<Map<String, Object>> loadJson = (List<Map<String, Object>>) json.loadJson(rulesIn);
+ rules = loadJson;
+ }
+
+ public RuleProcessor(String rulesIn, Map<String, Map<String, Object>> mappings) {
+ this.mappings = mappings;
+ IdpJson json = new IdpJson();
+ @SuppressWarnings("unchecked")
+ List<Map<String, Object>> loadJson = (List<Map<String, Object>>) 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<String, String> regexpGroupMap(String pattern, Matcher matcher) {
+ Map<String, String> groupMap = new HashMap<String, String>();
+ 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<Object> 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<String> regexpGroupList(Matcher matcher) {
+ List<String> groupList = new ArrayList<String>(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<Object> list = (List<Object>) 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<String, Object> map = (Map<String, Object>) obj;
+ boolean first = true;
+
+ sw.write('{');
+ for (Map.Entry<String, Object> 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<Object> new_list = new ArrayList<Object>();
+ @SuppressWarnings("unchecked")
+ List<Object> list = (List<Object>) obj;
+ for (Object item : list) {
+ new_list.add(deepCopy(item));
+ }
+ return new_list;
+ } else if (obj instanceof Map) {
+ Map<String, Object> new_map = new LinkedHashMap<String, Object>();
+ @SuppressWarnings("unchecked")
+ Map<String, Object> map = (Map<String, Object>) obj;
+ for (Map.Entry<String, Object> 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<String, Object> namespace) {
+ return substituteVariables(ruleIdFormat, namespace);
+ }
+
+ public String statementId(Map<String, Object> namespace) {
+ return substituteVariables(statementIdFormat, namespace);
+ }
+
+ public String substituteVariables(String string, Map<String, Object> 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<String, Object> getMapping(Map<String, Object> namespace, Map<String, Object> rule) {
+ Map<String, Object> mapping = null;
+ String mappingName = null;
+
+ try {
+ @SuppressWarnings("unchecked")
+ Map<String, Object> map = (Map<String, Object>) 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), e));
+ }
+ 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), e));
+ }
+ 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)));
+ }
+ LOG.debug(String.format("using named mapping '%s' from rule %s mapping=%s", mappingName,
+ this.ruleId(namespace), mapping));
+ return mapping;
+ }
+
+ private String getVerb(List<Object> 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<Object> statement, int index,
+ Map<String, Object> namespace, Set<TokenStorageType> storageTypes,
+ Set<TokenType> 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(), e));
+ }
+
+ 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<Object> statement, int index,
+ Map<String, Object> namespace, Set<TokenType> 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(), e));
+ }
+
+ 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<Object> statement, int index,
+ Set<TokenType> 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(), e));
+ }
+
+ 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<Object> statement, int index,
+ Map<String, Object> 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(), e));
+ }
+
+ 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<String, Object> process(String assertionJson) {
+ ProcessResult result;
+ IdpJson json = new IdpJson();
+ @SuppressWarnings("unchecked")
+ Map<String, Object> assertion = (Map<String, Object>) json.loadJson(assertionJson);
+ LOG.info("Assertion JSON: {}", json.dumpJson(assertion));
+ this.success = true;
+
+ for (int ruleNumber = 0; ruleNumber < this.rules.size(); ruleNumber++) {
+ Map<String, Object> namespace = new HashMap<String, Object>();
+ Map<String, Object> rule = (Map<String, Object>) this.rules.get(ruleNumber);
+ namespace.put(RULE_NUMBER, Long.valueOf(ruleNumber));
+ namespace.put(RULE_NAME, "");
+ namespace.put(ASSERTION, deepCopy(assertion));
+
+ result = processRule(namespace, rule);
+
+ if (result == ProcessResult.RULE_SUCCESS) {
+ Map<String, Object> mapped = new LinkedHashMap<String, Object>();
+ Map<String, Object> mapping = getMapping(namespace, rule);
+ for (Map.Entry<String, Object> entry : ((Map<String, Object>) 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<String, Object> namespace, Map<String, Object> rule) {
+ ProcessResult result = ProcessResult.BLOCK_CONTINUE;
+ @SuppressWarnings("unchecked")
+ List<List<List<Object>>> statementBlocks = (List<List<List<Object>>>) rule.get("statement_blocks");
+ if (statementBlocks == null) {
+ throw new InvalidRuleException("rule missing 'statement_blocks'");
+
+ }
+ for (int blockNumber = 0; blockNumber < statementBlocks.size(); blockNumber++) {
+ List<List<Object>> block = (List<List<Object>>) statementBlocks.get(blockNumber);
+ namespace.put(BLOCK_NUMBER, Long.valueOf(blockNumber));
+ namespace.put(BLOCK_NAME, "");
+
+ result = processBlock(namespace, block);
+ 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<String, Object> namespace, List<List<Object>> block) {
+ ProcessResult result = ProcessResult.STATEMENT_CONTINUE;
+
+ for (int statementNumber = 0; statementNumber < block.size(); statementNumber++) {
+ List<Object> statement = (List<Object>) block.get(statementNumber);
+ namespace.put(STATEMENT_NUMBER, Long.valueOf(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<String, Object> namespace, List<Object> 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<String, Object> namespace, List<Object> statement) {
+ Token variable = getVariable(verb, statement, 1, namespace);
+ Token parameter = getParameter(verb, statement, 2, namespace, null);
+
+ variable.set(parameter.getObjectValue());
+ this.success = true;
+
+ if (LOG.isDebugEnabled()) {
+ LOG.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<String, Object> namespace,
+ List<Object> 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(length);
+ this.success = true;
+
+ if (LOG.isDebugEnabled()) {
+ LOG.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<String, Object> namespace,
+ List<Object> 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 (LOG.isDebugEnabled()) {
+ LOG.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<String, Object> namespace,
+ List<Object> 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<Object> 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 (LOG.isDebugEnabled()) {
+ LOG.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<String, Object> namespace,
+ List<Object> statement) {
+ Token variable = getVariable(verb, statement, 1, namespace);
+ Token array = getParameter(verb, statement, 2, namespace, EnumSet.of(TokenType.ARRAY));
+
+ List<Object> newValue = new ArrayList<Object>();
+ Set<Object> seen = new HashSet<Object>();
+
+ for (Object member : array.getListValue()) {
+ if (seen.contains(member)) {
+ continue;
+ } else {
+ newValue.add(member);
+ seen.add(member);
+ }
+ }
+
+ variable.set(newValue);
+ this.success = true;
+
+ if (LOG.isDebugEnabled()) {
+ LOG.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<String, Object> namespace,
+ List<Object> 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<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));
+ }
+ try {
+ newValue = new ArrayList<String>(
+ 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 (LOG.isDebugEnabled()) {
+ LOG.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<String, Object> namespace,
+ List<Object> 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 (LOG.isDebugEnabled()) {
+ LOG.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<String, Object> namespace,
+ List<Object> 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<Object> oldValue = parameter.getListValue();
+ List<Object> newValue = new ArrayList<Object>(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(), e));
+ }
+ newItem = oldItem.toLowerCase();
+ newValue.add(newItem);
+ }
+ variable.set(newValue);
+ }
+ break;
+ case MAP: {
+ Map<String, Object> oldValue = parameter.getMapValue();
+ Map<String, Object> newValue = new LinkedHashMap<String, Object>(oldValue.size());
+
+ for (Map.Entry<String, Object> 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 (LOG.isDebugEnabled()) {
+ LOG.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<String, Object> namespace,
+ List<Object> 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<Object> oldValue = parameter.getListValue();
+ List<Object> newValue = new ArrayList<Object>(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(), e));
+ }
+ newItem = oldItem.toUpperCase();
+ newValue.add(newItem);
+ }
+ variable.set(newValue);
+ }
+ break;
+ case MAP: {
+ Map<String, Object> oldValue = parameter.getMapValue();
+ Map<String, Object> newValue = new LinkedHashMap<String, Object>(oldValue.size());
+
+ for (Map.Entry<String, Object> 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 (LOG.isDebugEnabled()) {
+ LOG.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<String, Object> namespace, List<Object> 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 (LOG.isDebugEnabled()) {
+ LOG.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<String, Object> namespace,
+ List<Object> 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 (LOG.isDebugEnabled()) {
+ LOG.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<String, Object> namespace,
+ List<Object> 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<Object> leftValue = left.getListValue();
+ List<Object> rightValue = right.getListValue();
+ result = leftValue.equals(rightValue);
+ }
+ break;
+ case MAP: {
+ Map<String, Object> leftValue = left.getMapValue();
+ Map<String, Object> 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 (LOG.isDebugEnabled()) {
+ LOG.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<String, Object> namespace,
+ List<Object> 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<Object>());
+ namespace.put(REGEXP_MAP_VARIABLE, new HashMap<String, Object>());
+ }
+
+ if (LOG.isDebugEnabled()) {
+ LOG.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<String, Object> namespace,
+ List<Object> 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 (LOG.isDebugEnabled()) {
+ LOG.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<String, Object> namespace,
+ List<Object> 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 (LOG.isDebugEnabled()) {
+ LOG.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<String, Object> namespace,
+ List<Object> 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 (LOG.isDebugEnabled()) {
+ LOG.debug(String.format(
+ "%s verb='%s' success=%s criteria=%s continuing=%s result=%s",
+ statementId(namespace), verb, this.success, criteria, doContinue,
+ statementResult));
+ }
+
+ return statementResult;
+ }
+
+}