From e63b03f3d7e4851e008e4bb4d184982c2c0bd229 Mon Sep 17 00:00:00 2001 From: WuKong Date: Tue, 24 May 2016 17:13:17 +0200 Subject: odl/aaa clone Change-Id: I2b72c16aa3245e02d985a2c6189aacee7caad36e Signed-off-by: WuKong --- .../opendaylight/aaa/idpmapping/RuleProcessor.java | 1368 ++++++++++++++++++++ 1 file changed, 1368 insertions(+) create mode 100644 odl-aaa-moon/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/RuleProcessor.java (limited to 'odl-aaa-moon/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/RuleProcessor.java') diff --git a/odl-aaa-moon/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/RuleProcessor.java b/odl-aaa-moon/aaa-idp-mapping/src/main/java/org/opendaylight/aaa/idpmapping/RuleProcessor.java new file mode 100644 index 00000000..0f86fde6 --- /dev/null +++ b/odl-aaa-moon/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 = ""; + 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), 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 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(), 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 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(), 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 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(), 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 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(), 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 process(String assertionJson) { + ProcessResult result; + IdpJson json = new IdpJson(); + @SuppressWarnings("unchecked") + Map assertion = (Map) json.loadJson(assertionJson); + LOG.info("Assertion JSON: {}", 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, Long.valueOf(ruleNumber)); + namespace.put(RULE_NAME, ""); + 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, 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 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, 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 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 (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 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(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 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 (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 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 (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 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 (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 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 (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 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 (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 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(), e)); + } + 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 (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 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(), e)); + } + 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 (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 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 (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 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 (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 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 (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 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 (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 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 (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 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 (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 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 (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; + } + +} -- cgit 1.2.3-korg