2 * Copyright (c) 2014 Red Hat, Inc. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
9 package org.opendaylight.aaa.idpmapping;
11 import java.io.IOException;
12 import java.io.StringWriter;
13 import java.nio.file.Path;
14 import java.util.ArrayList;
15 import java.util.Arrays;
16 import java.util.EnumSet;
17 import java.util.HashMap;
18 import java.util.HashSet;
19 import java.util.LinkedHashMap;
20 import java.util.List;
23 import java.util.regex.Matcher;
24 import java.util.regex.Pattern;
25 import org.slf4j.Logger;
26 import org.slf4j.LoggerFactory;
29 RULE_FAIL, RULE_SUCCESS, BLOCK_CONTINUE, STATEMENT_CONTINUE
33 * Evaluate a set of rules against an assertion from an external Identity
34 * Provider (IdP) mapping those assertion values to local values.
36 * @author John Dennis <jdennis@redhat.com>
39 public class RuleProcessor {
40 private static final Logger LOG = LoggerFactory.getLogger(RuleProcessor.class);
42 public String ruleIdFormat = "<rule [${rule_number}:\"${rule_name}\"]>";
43 public String statementIdFormat = "<rule [${rule_number}:\"${rule_name}\"] block [${block_number}:\"${block_name}\"] statement ${statement_number}>";
48 public static final String ASSERTION = "assertion";
49 public static final String RULE_NUMBER = "rule_number";
50 public static final String RULE_NAME = "rule_name";
51 public static final String BLOCK_NUMBER = "block_number";
52 public static final String BLOCK_NAME = "block_name";
53 public static final String STATEMENT_NUMBER = "statement_number";
54 public static final String REGEXP_ARRAY_VARIABLE = "regexp_array";
55 public static final String REGEXP_MAP_VARIABLE = "regexp_map";
57 private static final String REGEXP_NAMED_GROUP_PAT = "\\(\\?<([a-zA-Z][a-zA-Z0-9]*)>";
58 private static final Pattern REGEXP_NAMED_GROUP_RE = Pattern.compile(REGEXP_NAMED_GROUP_PAT);
60 List<Map<String, Object>> rules = null;
61 boolean success = true;
62 Map<String, Map<String, Object>> mappings = null;
64 public RuleProcessor(java.io.Reader rulesIn, Map<String, Map<String, Object>> mappings) {
65 this.mappings = mappings;
66 IdpJson json = new IdpJson();
67 @SuppressWarnings("unchecked")
68 List<Map<String, Object>> loadJson = (List<Map<String, Object>>) json.loadJson(rulesIn);
72 public RuleProcessor(Path rulesIn, Map<String, Map<String, Object>> mappings)
74 this.mappings = mappings;
75 IdpJson json = new IdpJson();
76 @SuppressWarnings("unchecked")
77 List<Map<String, Object>> loadJson = (List<Map<String, Object>>) json.loadJson(rulesIn);
81 public RuleProcessor(String rulesIn, Map<String, Map<String, Object>> mappings) {
82 this.mappings = mappings;
83 IdpJson json = new IdpJson();
84 @SuppressWarnings("unchecked")
85 List<Map<String, Object>> loadJson = (List<Map<String, Object>>) json.loadJson(rulesIn);
90 * For some odd reason the Java Regular Expression API does not include a
91 * way to retrieve a map of the named groups and their values. The API only
92 * permits us to retrieve a named group if we already know the group names.
93 * So instead we parse the pattern string looking for named groups, extract
94 * the name, look up the value of the named group and build a map from that.
97 private Map<String, String> regexpGroupMap(String pattern, Matcher matcher) {
98 Map<String, String> groupMap = new HashMap<String, String>();
99 Matcher groupMatcher = REGEXP_NAMED_GROUP_RE.matcher(pattern);
101 while (groupMatcher.find()) {
102 String groupName = groupMatcher.group(1);
104 groupMap.put(groupName, matcher.group(groupName));
109 static public String join(List<Object> list, String conjunction) {
110 StringBuilder sb = new StringBuilder();
111 boolean first = true;
112 for (Object item : list) {
116 sb.append(conjunction);
118 sb.append(item.toString());
120 return sb.toString();
123 private List<String> regexpGroupList(Matcher matcher) {
124 List<String> groupList = new ArrayList<String>(matcher.groupCount() + 1);
125 groupList.add(0, matcher.group(0));
126 for (int i = 1; i < matcher.groupCount() + 1; i++) {
127 groupList.add(i, matcher.group(i));
132 private String objToString(Object obj) {
133 StringWriter sw = new StringWriter();
134 objToStringItem(sw, obj);
135 return sw.toString();
138 private void objToStringItem(StringWriter sw, Object obj) {
139 // ordered by expected occurrence
140 if (obj instanceof String) {
142 sw.write(((String) obj).replaceAll("\"", "\\\""));
144 } else if (obj instanceof List) {
145 @SuppressWarnings("unchecked")
146 List<Object> list = (List<Object>) obj;
147 boolean first = true;
150 for (Object item : list) {
156 objToStringItem(sw, item);
159 } else if (obj instanceof Map) {
160 @SuppressWarnings("unchecked")
161 Map<String, Object> map = (Map<String, Object>) obj;
162 boolean first = true;
165 for (Map.Entry<String, Object> entry : map.entrySet()) {
166 String key = entry.getKey();
167 Object value = entry.getValue();
175 objToStringItem(sw, key);
177 objToStringItem(sw, value);
181 } else if (obj instanceof Long) {
182 sw.write(((Long) obj).toString());
183 } else if (obj instanceof Boolean) {
184 sw.write(((Boolean) obj).toString());
185 } else if (obj == null) {
187 } else if (obj instanceof Double) {
188 sw.write(((Double) obj).toString());
190 throw new IllegalStateException(
192 "unsupported data type, must be String, Long, Double, Boolean, List, Map, or null, not %s",
193 obj.getClass().getSimpleName()));
197 private Object deepCopy(Object obj) {
198 // ordered by expected occurrence
199 if (obj instanceof String) {
200 return obj; // immutable
201 } else if (obj instanceof List) {
202 List<Object> new_list = new ArrayList<Object>();
203 @SuppressWarnings("unchecked")
204 List<Object> list = (List<Object>) obj;
205 for (Object item : list) {
206 new_list.add(deepCopy(item));
209 } else if (obj instanceof Map) {
210 Map<String, Object> new_map = new LinkedHashMap<String, Object>();
211 @SuppressWarnings("unchecked")
212 Map<String, Object> map = (Map<String, Object>) obj;
213 for (Map.Entry<String, Object> entry : map.entrySet()) {
214 String key = entry.getKey(); // immutable
215 Object value = entry.getValue();
216 new_map.put(key, deepCopy(value));
219 } else if (obj instanceof Long) {
220 return obj; // immutable
221 } else if (obj instanceof Boolean) {
222 return obj; // immutable
223 } else if (obj == null) {
225 } else if (obj instanceof Double) {
226 return obj; // immutable
228 throw new IllegalStateException(
230 "unsupported data type, must be String, Long, Double, Boolean, List, Map, or null, not %s",
231 obj.getClass().getSimpleName()));
235 public String ruleId(Map<String, Object> namespace) {
236 return substituteVariables(ruleIdFormat, namespace);
239 public String statementId(Map<String, Object> namespace) {
240 return substituteVariables(statementIdFormat, namespace);
243 public String substituteVariables(String string, Map<String, Object> namespace) {
244 StringBuffer sb = new StringBuffer();
245 Matcher matcher = Token.VARIABLE_RE.matcher(string);
247 while (matcher.find()) {
248 Token token = new Token(matcher.group(0), namespace);
251 if (token.type == TokenType.STRING) {
252 replacement = token.getStringValue();
254 replacement = objToString(token.getObjectValue());
257 matcher.appendReplacement(sb, replacement);
259 matcher.appendTail(sb);
260 return sb.toString();
263 Map<String, Object> getMapping(Map<String, Object> namespace, Map<String, Object> rule) {
264 Map<String, Object> mapping = null;
265 String mappingName = null;
268 @SuppressWarnings("unchecked")
269 Map<String, Object> map = (Map<String, Object>) rule.get("mapping");
271 } catch (java.lang.ClassCastException e) {
272 throw new InvalidRuleException(String.format(
273 "%s rule defines 'mapping' but it is not a Map", this.ruleId(namespace), e));
275 if (mapping != null) {
279 mappingName = (String) rule.get("mapping_name");
280 } catch (java.lang.ClassCastException e) {
281 throw new InvalidRuleException(String.format(
282 "%s rule defines 'mapping_name' but it is not a string",
283 this.ruleId(namespace), e));
285 if (mappingName == null) {
286 throw new InvalidRuleException(String.format(
287 "%s rule does not define mapping nor mapping_name unable to load mapping",
288 this.ruleId(namespace)));
290 mapping = this.mappings.get(mappingName);
291 if (mapping == null) {
292 throw new InvalidRuleException(
294 "%s rule specifies mapping_name '%s' but a mapping by that name does not exist, unable to load mapping",
295 this.ruleId(namespace)));
297 LOG.debug(String.format("using named mapping '%s' from rule %s mapping=%s", mappingName,
298 this.ruleId(namespace), mapping));
302 private String getVerb(List<Object> statement) {
305 if (statement.size() < 1) {
306 throw new InvalidRuleException("statement has no verb");
310 verb = new Token(statement.get(0), null);
311 } catch (Exception e) {
312 throw new InvalidRuleException(String.format(
313 "statement first member (i.e. verb) error %s", e));
316 if (verb.type != TokenType.STRING) {
317 throw new InvalidRuleException(String.format(
318 "statement first member (i.e. verb) must be a string, not %s", verb.type));
321 return (verb.getStringValue()).toLowerCase();
324 private Token getToken(String verb, List<Object> statement, int index,
325 Map<String, Object> namespace, Set<TokenStorageType> storageTypes,
326 Set<TokenType> tokenTypes) {
331 item = statement.get(index);
332 } catch (IndexOutOfBoundsException e) {
333 throw new InvalidRuleException(String.format(
334 "verb '%s' requires at least %d items but only %d are available.", verb,
335 index + 1, statement.size(), e));
339 token = new Token(item, namespace);
340 } catch (Exception e) {
341 throw new StatementErrorException(String.format("parameter %d, %s", index, e));
344 if (storageTypes != null) {
345 if (!storageTypes.contains(token.storageType)) {
346 throw new InvalidTypeException(
348 "verb '%s' requires parameter #%d to have storage types %s not %s. statement=%s",
349 verb, index, storageTypes, statement));
353 if (tokenTypes != null) {
354 token.load(); // Note, Token.load() sets the Token.type
356 if (!tokenTypes.contains(token.type)) {
357 throw new InvalidTypeException(String.format(
358 "verb '%s' requires parameter #%d to have types %s, not %s. statement=%s",
359 verb, index, tokenTypes, statement));
366 private Token getParameter(String verb, List<Object> statement, int index,
367 Map<String, Object> namespace, Set<TokenType> tokenTypes) {
372 item = statement.get(index);
373 } catch (IndexOutOfBoundsException e) {
374 throw new InvalidRuleException(String.format(
375 "verb '%s' requires at least %d items but only %d are available.", verb,
376 index + 1, statement.size(), e));
380 token = new Token(item, namespace);
381 } catch (Exception e) {
382 throw new StatementErrorException(String.format("parameter %d, %s", index, e));
387 if (tokenTypes != null) {
389 token.get(); // Note, Token.get() sets the Token.type
390 } catch (UndefinedValueException e) {
391 // OK if not yet defined
393 if (!tokenTypes.contains(token.type)) {
394 throw new InvalidTypeException(String.format(
395 "verb '%s' requires parameter #%d to have types %s, not %s. statement=%s",
396 verb, index, tokenTypes, item.getClass().getSimpleName(), statement));
403 private Object getRawParameter(String verb, List<Object> statement, int index,
404 Set<TokenType> tokenTypes) {
408 item = statement.get(index);
409 } catch (IndexOutOfBoundsException e) {
410 throw new InvalidRuleException(String.format(
411 "verb '%s' requires at least %d items but only %d are available.", verb,
412 index + 1, statement.size(), e));
415 if (tokenTypes != null) {
416 TokenType itemType = Token.classify(item);
418 if (!tokenTypes.contains(itemType)) {
419 throw new InvalidTypeException(String.format(
420 "verb '%s' requires parameter #%d to have types %s, not %s. statement=%s",
421 verb, index, tokenTypes, statement));
428 private Token getVariable(String verb, List<Object> statement, int index,
429 Map<String, Object> namespace) {
434 item = statement.get(index);
435 } catch (IndexOutOfBoundsException e) {
436 throw new InvalidRuleException(String.format(
437 "verb '%s' requires at least %d items but only %d are available.", verb,
438 index + 1, statement.size(), e));
442 token = new Token(item, namespace);
443 } catch (Exception e) {
444 throw new StatementErrorException(String.format("parameter %d, %s", index, e));
447 if (token.storageType != TokenStorageType.VARIABLE) {
448 throw new InvalidTypeException(String.format(
449 "verb '%s' requires parameter #%d to be a variable not %s. statement=%s", verb,
450 index, token.storageType, statement));
456 public Map<String, Object> process(String assertionJson) {
457 ProcessResult result;
458 IdpJson json = new IdpJson();
459 @SuppressWarnings("unchecked")
460 Map<String, Object> assertion = (Map<String, Object>) json.loadJson(assertionJson);
461 LOG.info("Assertion JSON: {}", json.dumpJson(assertion));
464 for (int ruleNumber = 0; ruleNumber < this.rules.size(); ruleNumber++) {
465 Map<String, Object> namespace = new HashMap<String, Object>();
466 Map<String, Object> rule = (Map<String, Object>) this.rules.get(ruleNumber);
467 namespace.put(RULE_NUMBER, Long.valueOf(ruleNumber));
468 namespace.put(RULE_NAME, "");
469 namespace.put(ASSERTION, deepCopy(assertion));
471 result = processRule(namespace, rule);
473 if (result == ProcessResult.RULE_SUCCESS) {
474 Map<String, Object> mapped = new LinkedHashMap<String, Object>();
475 Map<String, Object> mapping = getMapping(namespace, rule);
476 for (Map.Entry<String, Object> entry : ((Map<String, Object>) mapping).entrySet()) {
477 String key = entry.getKey();
478 Object value = entry.getValue();
479 Object newValue = null;
481 Token token = new Token(value, namespace);
482 newValue = token.get();
483 } catch (Exception e) {
484 throw new InvalidRuleException(String.format(
485 "%s unable to get value for mapping %s=%s, %s", ruleId(namespace),
488 mapped.put(key, newValue);
496 private ProcessResult processRule(Map<String, Object> namespace, Map<String, Object> rule) {
497 ProcessResult result = ProcessResult.BLOCK_CONTINUE;
498 @SuppressWarnings("unchecked")
499 List<List<List<Object>>> statementBlocks = (List<List<List<Object>>>) rule.get("statement_blocks");
500 if (statementBlocks == null) {
501 throw new InvalidRuleException("rule missing 'statement_blocks'");
504 for (int blockNumber = 0; blockNumber < statementBlocks.size(); blockNumber++) {
505 List<List<Object>> block = (List<List<Object>>) statementBlocks.get(blockNumber);
506 namespace.put(BLOCK_NUMBER, Long.valueOf(blockNumber));
507 namespace.put(BLOCK_NAME, "");
509 result = processBlock(namespace, block);
510 if (EnumSet.of(ProcessResult.RULE_SUCCESS, ProcessResult.RULE_FAIL).contains(result)) {
512 } else if (result == ProcessResult.BLOCK_CONTINUE) {
515 throw new IllegalStateException(String.format("%s unexpected statement result: %s",
519 if (EnumSet.of(ProcessResult.RULE_SUCCESS, ProcessResult.BLOCK_CONTINUE).contains(result)) {
520 return ProcessResult.RULE_SUCCESS;
522 return ProcessResult.RULE_FAIL;
526 private ProcessResult processBlock(Map<String, Object> namespace, List<List<Object>> block) {
527 ProcessResult result = ProcessResult.STATEMENT_CONTINUE;
529 for (int statementNumber = 0; statementNumber < block.size(); statementNumber++) {
530 List<Object> statement = (List<Object>) block.get(statementNumber);
531 namespace.put(STATEMENT_NUMBER, Long.valueOf(statementNumber));
534 result = processStatement(namespace, statement);
535 } catch (Exception e) {
536 throw new IllegalStateException(String.format("%s statement=%s %s",
537 statementId(namespace), statement, e), e);
539 if (EnumSet.of(ProcessResult.BLOCK_CONTINUE, ProcessResult.RULE_SUCCESS,
540 ProcessResult.RULE_FAIL).contains(result)) {
542 } else if (result == ProcessResult.STATEMENT_CONTINUE) {
545 throw new IllegalStateException(String.format("%s unexpected statement result: %s",
549 if (result == ProcessResult.STATEMENT_CONTINUE) {
550 result = ProcessResult.BLOCK_CONTINUE;
555 private ProcessResult processStatement(Map<String, Object> namespace, List<Object> statement) {
556 ProcessResult result = ProcessResult.STATEMENT_CONTINUE;
557 String verb = getVerb(statement);
561 result = verbSet(verb, namespace, statement);
564 result = verbLength(verb, namespace, statement);
567 result = verbInterpolate(verb, namespace, statement);
570 result = verbAppend(verb, namespace, statement);
573 result = verbUnique(verb, namespace, statement);
576 result = verbSplit(verb, namespace, statement);
579 result = verbJoin(verb, namespace, statement);
582 result = verbLower(verb, namespace, statement);
585 result = verbUpper(verb, namespace, statement);
588 result = verbIn(verb, namespace, statement);
591 result = verbNotIn(verb, namespace, statement);
594 result = verbCompare(verb, namespace, statement);
597 result = verbRegexp(verb, namespace, statement);
599 case "regexp_replace":
600 result = verbRegexpReplace(verb, namespace, statement);
603 result = verbExit(verb, namespace, statement);
606 result = verbContinue(verb, namespace, statement);
609 throw new InvalidRuleException(String.format("unknown verb '%s'", verb));
615 private ProcessResult verbSet(String verb, Map<String, Object> namespace, List<Object> statement) {
616 Token variable = getVariable(verb, statement, 1, namespace);
617 Token parameter = getParameter(verb, statement, 2, namespace, null);
619 variable.set(parameter.getObjectValue());
622 if (LOG.isDebugEnabled()) {
623 LOG.debug(String.format("%s verb='%s' success=%s variable: %s=%s",
624 statementId(namespace), verb, this.success, variable, variable.get()));
626 return ProcessResult.STATEMENT_CONTINUE;
629 private ProcessResult verbLength(String verb, Map<String, Object> namespace,
630 List<Object> statement) {
631 Token variable = getVariable(verb, statement, 1, namespace);
632 Token parameter = getParameter(verb, statement, 2, namespace,
633 EnumSet.of(TokenType.ARRAY, TokenType.MAP, TokenType.STRING));
636 switch (parameter.type) {
638 length = parameter.getListValue().size();
642 length = parameter.getMapValue().size();
646 length = parameter.getStringValue().length();
650 throw new IllegalStateException(String.format("unexpected token type: %s",
654 variable.set(length);
657 if (LOG.isDebugEnabled()) {
658 LOG.debug(String.format("%s verb='%s' success=%s variable: %s=%s parameter=%s",
659 statementId(namespace), verb, this.success, variable, variable.get(),
660 parameter.getObjectValue()));
662 return ProcessResult.STATEMENT_CONTINUE;
665 private ProcessResult verbInterpolate(String verb, Map<String, Object> namespace,
666 List<Object> statement) {
667 Token variable = getVariable(verb, statement, 1, namespace);
668 String string = (String) getRawParameter(verb, statement, 2, EnumSet.of(TokenType.STRING));
669 String newValue = null;
672 newValue = substituteVariables(string, namespace);
673 } catch (Exception e) {
674 throw new InvalidValueException(String.format(
675 "verb '%s' failed, variable='%s' string='%s': %s", verb, variable, string, e));
677 variable.set(newValue);
680 if (LOG.isDebugEnabled()) {
681 LOG.debug(String.format("%s verb='%s' success=%s variable: %s=%s string='%s'",
682 statementId(namespace), verb, this.success, variable, variable.get(), string));
685 return ProcessResult.STATEMENT_CONTINUE;
688 private ProcessResult verbAppend(String verb, Map<String, Object> namespace,
689 List<Object> statement) {
690 Token variable = getToken(verb, statement, 1, namespace,
691 EnumSet.of(TokenStorageType.VARIABLE), EnumSet.of(TokenType.ARRAY));
692 Token item = getParameter(verb, statement, 2, namespace, null);
695 List<Object> list = variable.getListValue();
696 list.add(item.getObjectValue());
697 } catch (Exception e) {
698 throw new InvalidValueException(String.format(
699 "verb '%s' failed, variable='%s' item='%s': %s", verb,
700 variable.getObjectValue(), item.getObjectValue(), e));
704 if (LOG.isDebugEnabled()) {
705 LOG.debug(String.format("%s verb='%s' success=%s variable: %s=%s item=%s",
706 statementId(namespace), verb, this.success, variable, variable.get(),
707 item.getObjectValue()));
710 return ProcessResult.STATEMENT_CONTINUE;
713 private ProcessResult verbUnique(String verb, Map<String, Object> namespace,
714 List<Object> statement) {
715 Token variable = getVariable(verb, statement, 1, namespace);
716 Token array = getParameter(verb, statement, 2, namespace, EnumSet.of(TokenType.ARRAY));
718 List<Object> newValue = new ArrayList<Object>();
719 Set<Object> seen = new HashSet<Object>();
721 for (Object member : array.getListValue()) {
722 if (seen.contains(member)) {
725 newValue.add(member);
730 variable.set(newValue);
733 if (LOG.isDebugEnabled()) {
734 LOG.debug(String.format("%s verb='%s' success=%s variable: %s=%s array=%s",
735 statementId(namespace), verb, this.success, variable, variable.get(),
736 array.getObjectValue()));
739 return ProcessResult.STATEMENT_CONTINUE;
742 private ProcessResult verbSplit(String verb, Map<String, Object> namespace,
743 List<Object> statement) {
744 Token variable = getVariable(verb, statement, 1, namespace);
745 Token string = getParameter(verb, statement, 2, namespace, EnumSet.of(TokenType.STRING));
746 Token pattern = getParameter(verb, statement, 3, namespace, EnumSet.of(TokenType.STRING));
749 List<String> newValue;
752 regexp = Pattern.compile(pattern.getStringValue());
753 } catch (Exception e) {
754 throw new InvalidValueException(String.format(
755 "verb '%s' failed, bad regular expression pattern '%s', %s", verb,
756 pattern.getObjectValue(), e));
759 newValue = new ArrayList<String>(
760 Arrays.asList(regexp.split((String) string.getStringValue())));
761 } catch (Exception e) {
762 throw new InvalidValueException(String.format(
763 "verb '%s' failed, string='%s' pattern='%s', %s", verb,
764 string.getObjectValue(), pattern.getObjectValue(), e));
767 variable.set(newValue);
770 if (LOG.isDebugEnabled()) {
771 LOG.debug(String.format(
772 "%s verb='%s' success=%s variable: %s=%s string='%s' pattern='%s'",
773 statementId(namespace), verb, this.success, variable, variable.get(),
774 string.getObjectValue(), pattern.getObjectValue()));
777 return ProcessResult.STATEMENT_CONTINUE;
780 private ProcessResult verbJoin(String verb, Map<String, Object> namespace,
781 List<Object> statement) {
782 Token variable = getVariable(verb, statement, 1, namespace);
783 Token array = getParameter(verb, statement, 2, namespace, EnumSet.of(TokenType.ARRAY));
784 Token conjunction = getParameter(verb, statement, 3, namespace,
785 EnumSet.of(TokenType.STRING));
789 newValue = join(array.getListValue(), conjunction.getStringValue());
790 } catch (Exception e) {
791 throw new InvalidValueException(String.format(
792 "verb '%s' failed, array=%s conjunction='%s', %s", verb,
793 array.getObjectValue(), conjunction.getObjectValue(), e));
796 variable.set(newValue);
799 if (LOG.isDebugEnabled()) {
800 LOG.debug(String.format(
801 "%s verb='%s' success=%s variable: %s=%s array='%s' conjunction='%s'",
802 statementId(namespace), verb, this.success, variable, variable.get(),
803 array.getObjectValue(), conjunction.getObjectValue()));
806 return ProcessResult.STATEMENT_CONTINUE;
809 private ProcessResult verbLower(String verb, Map<String, Object> namespace,
810 List<Object> statement) {
811 Token variable = getVariable(verb, statement, 1, namespace);
812 Token parameter = getParameter(verb, statement, 2, namespace,
813 EnumSet.of(TokenType.STRING, TokenType.ARRAY, TokenType.MAP));
816 switch (parameter.type) {
818 String oldValue = parameter.getStringValue();
820 newValue = oldValue.toLowerCase();
821 variable.set(newValue);
825 List<Object> oldValue = parameter.getListValue();
826 List<Object> newValue = new ArrayList<Object>(oldValue.size());
830 for (Object item : oldValue) {
832 oldItem = (String) item;
833 } catch (ClassCastException e) {
834 throw new InvalidValueException(String.format(
835 "verb '%s' failed, array item (%s) is not a string, array=%s",
836 verb, item, parameter.getObjectValue(), e));
838 newItem = oldItem.toLowerCase();
839 newValue.add(newItem);
841 variable.set(newValue);
845 Map<String, Object> oldValue = parameter.getMapValue();
846 Map<String, Object> newValue = new LinkedHashMap<String, Object>(oldValue.size());
848 for (Map.Entry<String, Object> entry : oldValue.entrySet()) {
851 Object value = entry.getValue();
853 oldKey = entry.getKey();
854 newKey = oldKey.toLowerCase();
855 newValue.put(newKey, value);
857 variable.set(newValue);
861 throw new IllegalStateException(String.format("unexpected token type: %s",
864 } catch (Exception e) {
865 throw new InvalidValueException(String.format(
866 "verb '%s' failed, variable='%s' parameter='%s': %s", verb, variable,
867 parameter.getObjectValue(), e), e);
871 if (LOG.isDebugEnabled()) {
872 LOG.debug(String.format("%s verb='%s' success=%s variable: %s=%s parameter=%s",
873 statementId(namespace), verb, this.success, variable, variable.get(),
874 parameter.getObjectValue()));
876 return ProcessResult.STATEMENT_CONTINUE;
879 private ProcessResult verbUpper(String verb, Map<String, Object> namespace,
880 List<Object> statement) {
881 Token variable = getVariable(verb, statement, 1, namespace);
882 Token parameter = getParameter(verb, statement, 2, namespace,
883 EnumSet.of(TokenType.STRING, TokenType.ARRAY, TokenType.MAP));
886 switch (parameter.type) {
888 String oldValue = parameter.getStringValue();
890 newValue = oldValue.toUpperCase();
891 variable.set(newValue);
895 List<Object> oldValue = parameter.getListValue();
896 List<Object> newValue = new ArrayList<Object>(oldValue.size());
900 for (Object item : oldValue) {
902 oldItem = (String) item;
903 } catch (ClassCastException e) {
904 throw new InvalidValueException(String.format(
905 "verb '%s' failed, array item (%s) is not a string, array=%s",
906 verb, item, parameter.getObjectValue(), e));
908 newItem = oldItem.toUpperCase();
909 newValue.add(newItem);
911 variable.set(newValue);
915 Map<String, Object> oldValue = parameter.getMapValue();
916 Map<String, Object> newValue = new LinkedHashMap<String, Object>(oldValue.size());
918 for (Map.Entry<String, Object> entry : oldValue.entrySet()) {
921 Object value = entry.getValue();
923 oldKey = entry.getKey();
924 newKey = oldKey.toUpperCase();
925 newValue.put(newKey, value);
927 variable.set(newValue);
931 throw new IllegalStateException(String.format("unexpected token type: %s",
934 } catch (Exception e) {
935 throw new InvalidValueException(String.format(
936 "verb '%s' failed, variable='%s' parameter='%s': %s", verb, variable,
937 parameter.getObjectValue(), e), e);
941 if (LOG.isDebugEnabled()) {
942 LOG.debug(String.format("%s verb='%s' success=%s variable: %s=%s parameter=%s",
943 statementId(namespace), verb, this.success, variable, variable.get(),
944 parameter.getObjectValue()));
946 return ProcessResult.STATEMENT_CONTINUE;
949 private ProcessResult verbIn(String verb, Map<String, Object> namespace, List<Object> statement) {
950 Token member = getParameter(verb, statement, 1, namespace, null);
951 Token collection = getParameter(verb, statement, 2, namespace,
952 EnumSet.of(TokenType.ARRAY, TokenType.MAP, TokenType.STRING));
954 switch (collection.type) {
956 this.success = collection.getListValue().contains(member.getObjectValue());
960 if (member.type != TokenType.STRING) {
961 throw new InvalidTypeException(String.format(
962 "verb '%s' requires parameter #1 to be a %swhen parameter #2 is a %s",
963 TokenType.STRING, collection.type));
965 this.success = collection.getMapValue().containsKey(member.getObjectValue());
969 if (member.type != TokenType.STRING) {
970 throw new InvalidTypeException(String.format(
971 "verb '%s' requires parameter #1 to be a %swhen parameter #2 is a %s",
972 TokenType.STRING, collection.type));
974 this.success = (collection.getStringValue()).contains(member.getStringValue());
978 throw new IllegalStateException(String.format("unexpected token type: %s",
982 if (LOG.isDebugEnabled()) {
983 LOG.debug(String.format("%s verb='%s' success=%s member=%s collection=%s",
984 statementId(namespace), verb, this.success, member.getObjectValue(),
985 collection.getObjectValue()));
987 return ProcessResult.STATEMENT_CONTINUE;
990 private ProcessResult verbNotIn(String verb, Map<String, Object> namespace,
991 List<Object> statement) {
992 Token member = getParameter(verb, statement, 1, namespace, null);
993 Token collection = getParameter(verb, statement, 2, namespace,
994 EnumSet.of(TokenType.ARRAY, TokenType.MAP, TokenType.STRING));
996 switch (collection.type) {
998 this.success = !collection.getListValue().contains(member.getObjectValue());
1002 if (member.type != TokenType.STRING) {
1003 throw new InvalidTypeException(String.format(
1004 "verb '%s' requires parameter #1 to be a %swhen parameter #2 is a %s",
1005 TokenType.STRING, collection.type));
1007 this.success = !collection.getMapValue().containsKey(member.getObjectValue());
1011 if (member.type != TokenType.STRING) {
1012 throw new InvalidTypeException(String.format(
1013 "verb '%s' requires parameter #1 to be a %swhen parameter #2 is a %s",
1014 TokenType.STRING, collection.type));
1016 this.success = !(collection.getStringValue()).contains(member.getStringValue());
1020 throw new IllegalStateException(String.format("unexpected token type: %s",
1024 if (LOG.isDebugEnabled()) {
1025 LOG.debug(String.format("%s verb='%s' success=%s member=%s collection=%s",
1026 statementId(namespace), verb, this.success, member.getObjectValue(),
1027 collection.getObjectValue()));
1030 return ProcessResult.STATEMENT_CONTINUE;
1033 private ProcessResult verbCompare(String verb, Map<String, Object> namespace,
1034 List<Object> statement) {
1035 Token left = getParameter(verb, statement, 1, namespace, null);
1036 Token op = getParameter(verb, statement, 2, namespace, EnumSet.of(TokenType.STRING));
1037 Token right = getParameter(verb, statement, 3, namespace, null);
1038 String invalidOp = "operator %s not supported for type %s";
1039 TokenType tokenType;
1040 String opValue = op.getStringValue();
1043 if (left.type != right.type) {
1044 throw new InvalidTypeException(String.format(
1045 "verb '%s' both items must have the same type left is %s and right is %s",
1046 verb, left.type, right.type));
1048 tokenType = left.type;
1054 switch (tokenType) {
1056 String leftValue = left.getStringValue();
1057 String rightValue = right.getStringValue();
1058 result = leftValue.equals(rightValue);
1062 Long leftValue = left.getLongValue();
1063 Long rightValue = right.getLongValue();
1064 result = leftValue.equals(rightValue);
1068 Double leftValue = left.getDoubleValue();
1069 Double rightValue = right.getDoubleValue();
1070 result = leftValue.equals(rightValue);
1074 List<Object> leftValue = left.getListValue();
1075 List<Object> rightValue = right.getListValue();
1076 result = leftValue.equals(rightValue);
1080 Map<String, Object> leftValue = left.getMapValue();
1081 Map<String, Object> rightValue = right.getMapValue();
1082 result = leftValue.equals(rightValue);
1086 Boolean leftValue = left.getBooleanValue();
1087 Boolean rightValue = right.getBooleanValue();
1088 result = leftValue.equals(rightValue);
1092 result = (left.getNullValue() == right.getNullValue());
1096 throw new IllegalStateException(String.format("unexpected token type: %s",
1100 if (opValue.equals("!=")) { // negate the sense of the test
1107 switch (tokenType) {
1109 String leftValue = left.getStringValue();
1110 String rightValue = right.getStringValue();
1111 result = leftValue.compareTo(rightValue) < 0;
1115 Long leftValue = left.getLongValue();
1116 Long rightValue = right.getLongValue();
1117 result = leftValue < rightValue;
1121 Double leftValue = left.getDoubleValue();
1122 Double rightValue = right.getDoubleValue();
1123 result = leftValue < rightValue;
1130 throw new InvalidRuleException(String.format(invalidOp, opValue, tokenType));
1133 throw new IllegalStateException(String.format("unexpected token type: %s",
1137 if (opValue.equals(">=")) { // negate the sense of the test
1144 switch (tokenType) {
1146 String leftValue = left.getStringValue();
1147 String rightValue = right.getStringValue();
1148 result = leftValue.compareTo(rightValue) > 0;
1152 Long leftValue = left.getLongValue();
1153 Long rightValue = right.getLongValue();
1154 result = leftValue > rightValue;
1158 Double leftValue = left.getDoubleValue();
1159 Double rightValue = right.getDoubleValue();
1160 result = leftValue > rightValue;
1167 throw new InvalidRuleException(String.format(invalidOp, opValue, tokenType));
1170 throw new IllegalStateException(String.format("unexpected token type: %s",
1174 if (opValue.equals("<=")) { // negate the sense of the test
1180 throw new InvalidRuleException(String.format(
1181 "verb '%s' has unknown comparison operator '%s'", verb, op.getObjectValue()));
1184 this.success = result;
1186 if (LOG.isDebugEnabled()) {
1187 LOG.debug(String.format("%s verb='%s' success=%s left=%s op='%s' right=%s",
1188 statementId(namespace), verb, this.success, left.getObjectValue(),
1189 op.getObjectValue(), right.getObjectValue()));
1191 return ProcessResult.STATEMENT_CONTINUE;
1194 private ProcessResult verbRegexp(String verb, Map<String, Object> namespace,
1195 List<Object> statement) {
1196 Token string = getParameter(verb, statement, 1, namespace, EnumSet.of(TokenType.STRING));
1197 Token pattern = getParameter(verb, statement, 2, namespace, EnumSet.of(TokenType.STRING));
1203 regexp = Pattern.compile(pattern.getStringValue());
1204 } catch (Exception e) {
1205 throw new InvalidValueException(String.format(
1206 "verb '%s' failed, bad regular expression pattern '%s', %s", verb,
1207 pattern.getObjectValue(), e));
1209 matcher = regexp.matcher(string.getStringValue());
1211 if (matcher.find()) {
1212 this.success = true;
1213 namespace.put(REGEXP_ARRAY_VARIABLE, regexpGroupList(matcher));
1214 namespace.put(REGEXP_MAP_VARIABLE, regexpGroupMap(pattern.getStringValue(), matcher));
1216 this.success = false;
1217 namespace.put(REGEXP_ARRAY_VARIABLE, new ArrayList<Object>());
1218 namespace.put(REGEXP_MAP_VARIABLE, new HashMap<String, Object>());
1221 if (LOG.isDebugEnabled()) {
1222 LOG.debug(String.format(
1223 "%s verb='%s' success=%s string='%s' pattern='%s' %s=%s %s=%s",
1224 statementId(namespace), verb, this.success, string.getObjectValue(),
1225 pattern.getObjectValue(), REGEXP_ARRAY_VARIABLE,
1226 namespace.get(REGEXP_ARRAY_VARIABLE), REGEXP_MAP_VARIABLE,
1227 namespace.get(REGEXP_MAP_VARIABLE)));
1230 return ProcessResult.STATEMENT_CONTINUE;
1233 private ProcessResult verbRegexpReplace(String verb, Map<String, Object> namespace,
1234 List<Object> statement) {
1235 Token variable = getVariable(verb, statement, 1, namespace);
1236 Token string = getParameter(verb, statement, 2, namespace, EnumSet.of(TokenType.STRING));
1237 Token pattern = getParameter(verb, statement, 3, namespace, EnumSet.of(TokenType.STRING));
1238 Token replacement = getParameter(verb, statement, 4, namespace,
1239 EnumSet.of(TokenType.STRING));
1246 regexp = Pattern.compile(pattern.getStringValue());
1247 } catch (Exception e) {
1248 throw new InvalidValueException(String.format(
1249 "verb '%s' failed, bad regular expression pattern '%s', %s", verb,
1250 pattern.getObjectValue(), e));
1252 matcher = regexp.matcher(string.getStringValue());
1254 newValue = matcher.replaceAll(replacement.getStringValue());
1255 variable.set(newValue);
1256 this.success = true;
1258 if (LOG.isDebugEnabled()) {
1259 LOG.debug(String.format(
1260 "%s verb='%s' success=%s variable: %s=%s string='%s' pattern='%s' replacement='%s'",
1261 statementId(namespace), verb, this.success, variable, variable.get(),
1262 string.getObjectValue(), pattern.getObjectValue(), replacement.getObjectValue()));
1265 return ProcessResult.STATEMENT_CONTINUE;
1268 private ProcessResult verbExit(String verb, Map<String, Object> namespace,
1269 List<Object> statement) {
1270 ProcessResult statementResult = ProcessResult.STATEMENT_CONTINUE;
1272 Token exitStatusParam = getParameter(verb, statement, 1, namespace,
1273 EnumSet.of(TokenType.STRING));
1274 Token criteriaParam = getParameter(verb, statement, 2, namespace,
1275 EnumSet.of(TokenType.STRING));
1276 String exitStatus = (exitStatusParam.getStringValue()).toLowerCase();
1277 String criteria = (criteriaParam.getStringValue()).toLowerCase();
1278 ProcessResult result;
1281 if (exitStatus.equals("rule_succeeds")) {
1282 result = ProcessResult.RULE_SUCCESS;
1283 } else if (exitStatus.equals("rule_fails")) {
1284 result = ProcessResult.RULE_FAIL;
1286 throw new InvalidRuleException(String.format("verb='%s' unknown exit status '%s'",
1290 if (criteria.equals("if_success")) {
1296 } else if (criteria.equals("if_not_success")) {
1297 if (!this.success) {
1302 } else if (criteria.equals("always")) {
1304 } else if (criteria.equals("never")) {
1307 throw new InvalidRuleException(String.format("verb='%s' unknown exit criteria '%s'",
1312 statementResult = result;
1315 if (LOG.isDebugEnabled()) {
1316 LOG.debug(String.format(
1317 "%s verb='%s' success=%s status=%s criteria=%s exiting=%s result=%s",
1318 statementId(namespace), verb, this.success, exitStatus, criteria, doExit,
1322 return statementResult;
1325 private ProcessResult verbContinue(String verb, Map<String, Object> namespace,
1326 List<Object> statement) {
1327 ProcessResult statementResult = ProcessResult.STATEMENT_CONTINUE;
1328 Token criteriaParam = getParameter(verb, statement, 1, namespace,
1329 EnumSet.of(TokenType.STRING));
1330 String criteria = (criteriaParam.getStringValue()).toLowerCase();
1333 if (criteria.equals("if_success")) {
1339 } else if (criteria.equals("if_not_success")) {
1340 if (!this.success) {
1345 } else if (criteria.equals("always")) {
1347 } else if (criteria.equals("never")) {
1350 throw new InvalidRuleException(String.format(
1351 "verb='%s' unknown continue criteria '%s'", verb, criteria));
1355 statementResult = ProcessResult.BLOCK_CONTINUE;
1358 if (LOG.isDebugEnabled()) {
1359 LOG.debug(String.format(
1360 "%s verb='%s' success=%s criteria=%s continuing=%s result=%s",
1361 statementId(namespace), verb, this.success, criteria, doContinue,
1365 return statementResult;