0f86fde65c14101767f889b0f07ab62634cceae7
[moon.git] /
1 /*
2  * Copyright (c) 2014 Red Hat, Inc.  All rights reserved.
3  *
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
7  */
8
9 package org.opendaylight.aaa.idpmapping;
10
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;
21 import java.util.Map;
22 import java.util.Set;
23 import java.util.regex.Matcher;
24 import java.util.regex.Pattern;
25 import org.slf4j.Logger;
26 import org.slf4j.LoggerFactory;
27
28 enum ProcessResult {
29     RULE_FAIL, RULE_SUCCESS, BLOCK_CONTINUE, STATEMENT_CONTINUE
30 }
31
32 /**
33  * Evaluate a set of rules against an assertion from an external Identity
34  * Provider (IdP) mapping those assertion values to local values.
35  *
36  * @author John Dennis <jdennis@redhat.com>
37  */
38
39 public class RuleProcessor {
40     private static final Logger LOG = LoggerFactory.getLogger(RuleProcessor.class);
41
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}>";
44
45     /*
46      * Reserved variables
47      */
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";
56
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);
59
60     List<Map<String, Object>> rules = null;
61     boolean success = true;
62     Map<String, Map<String, Object>> mappings = null;
63
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);
69         rules = loadJson;
70     }
71
72     public RuleProcessor(Path rulesIn, Map<String, Map<String, Object>> mappings)
73             throws IOException {
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);
78         rules = loadJson;
79     }
80
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);
86         rules = loadJson;
87     }
88
89     /*
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.
95      */
96
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);
100
101         while (groupMatcher.find()) {
102             String groupName = groupMatcher.group(1);
103
104             groupMap.put(groupName, matcher.group(groupName));
105         }
106         return groupMap;
107     }
108
109     static public String join(List<Object> list, String conjunction) {
110         StringBuilder sb = new StringBuilder();
111         boolean first = true;
112         for (Object item : list) {
113             if (first) {
114                 first = false;
115             } else {
116                 sb.append(conjunction);
117             }
118             sb.append(item.toString());
119         }
120         return sb.toString();
121     }
122
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));
128         }
129         return groupList;
130     }
131
132     private String objToString(Object obj) {
133         StringWriter sw = new StringWriter();
134         objToStringItem(sw, obj);
135         return sw.toString();
136     }
137
138     private void objToStringItem(StringWriter sw, Object obj) {
139         // ordered by expected occurrence
140         if (obj instanceof String) {
141             sw.write('"');
142             sw.write(((String) obj).replaceAll("\"", "\\\""));
143             sw.write('"');
144         } else if (obj instanceof List) {
145             @SuppressWarnings("unchecked")
146             List<Object> list = (List<Object>) obj;
147             boolean first = true;
148
149             sw.write('[');
150             for (Object item : list) {
151                 if (first) {
152                     first = false;
153                 } else {
154                     sw.write(", ");
155                 }
156                 objToStringItem(sw, item);
157             }
158             sw.write(']');
159         } else if (obj instanceof Map) {
160             @SuppressWarnings("unchecked")
161             Map<String, Object> map = (Map<String, Object>) obj;
162             boolean first = true;
163
164             sw.write('{');
165             for (Map.Entry<String, Object> entry : map.entrySet()) {
166                 String key = entry.getKey();
167                 Object value = entry.getValue();
168
169                 if (first) {
170                     first = false;
171                 } else {
172                     sw.write(", ");
173                 }
174
175                 objToStringItem(sw, key);
176                 sw.write(": ");
177                 objToStringItem(sw, value);
178
179             }
180             sw.write('}');
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) {
186             sw.write("null");
187         } else if (obj instanceof Double) {
188             sw.write(((Double) obj).toString());
189         } else {
190             throw new IllegalStateException(
191                     String.format(
192                             "unsupported data type, must be String, Long, Double, Boolean, List, Map, or null, not %s",
193                             obj.getClass().getSimpleName()));
194         }
195     }
196
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));
207             }
208             return new_list;
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));
217             }
218             return new_map;
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) {
224             return null;
225         } else if (obj instanceof Double) {
226             return obj; // immutable
227         } else {
228             throw new IllegalStateException(
229                     String.format(
230                             "unsupported data type, must be String, Long, Double, Boolean, List, Map, or null, not %s",
231                             obj.getClass().getSimpleName()));
232         }
233     }
234
235     public String ruleId(Map<String, Object> namespace) {
236         return substituteVariables(ruleIdFormat, namespace);
237     }
238
239     public String statementId(Map<String, Object> namespace) {
240         return substituteVariables(statementIdFormat, namespace);
241     }
242
243     public String substituteVariables(String string, Map<String, Object> namespace) {
244         StringBuffer sb = new StringBuffer();
245         Matcher matcher = Token.VARIABLE_RE.matcher(string);
246
247         while (matcher.find()) {
248             Token token = new Token(matcher.group(0), namespace);
249             token.load();
250             String replacement;
251             if (token.type == TokenType.STRING) {
252                 replacement = token.getStringValue();
253             } else {
254                 replacement = objToString(token.getObjectValue());
255             }
256
257             matcher.appendReplacement(sb, replacement);
258         }
259         matcher.appendTail(sb);
260         return sb.toString();
261     }
262
263     Map<String, Object> getMapping(Map<String, Object> namespace, Map<String, Object> rule) {
264         Map<String, Object> mapping = null;
265         String mappingName = null;
266
267         try {
268             @SuppressWarnings("unchecked")
269             Map<String, Object> map = (Map<String, Object>) rule.get("mapping");
270             mapping = map;
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));
274         }
275         if (mapping != null) {
276             return mapping;
277         }
278         try {
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));
284         }
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)));
289         }
290         mapping = this.mappings.get(mappingName);
291         if (mapping == null) {
292             throw new InvalidRuleException(
293                     String.format(
294                             "%s rule specifies mapping_name '%s' but a mapping by that name does not exist, unable to load mapping",
295                             this.ruleId(namespace)));
296         }
297         LOG.debug(String.format("using named mapping '%s' from rule %s mapping=%s", mappingName,
298                 this.ruleId(namespace), mapping));
299         return mapping;
300     }
301
302     private String getVerb(List<Object> statement) {
303         Token verb;
304
305         if (statement.size() < 1) {
306             throw new InvalidRuleException("statement has no verb");
307         }
308
309         try {
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));
314         }
315
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));
319         }
320
321         return (verb.getStringValue()).toLowerCase();
322     }
323
324     private Token getToken(String verb, List<Object> statement, int index,
325             Map<String, Object> namespace, Set<TokenStorageType> storageTypes,
326             Set<TokenType> tokenTypes) {
327         Object item;
328         Token token;
329
330         try {
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));
336         }
337
338         try {
339             token = new Token(item, namespace);
340         } catch (Exception e) {
341             throw new StatementErrorException(String.format("parameter %d, %s", index, e));
342         }
343
344         if (storageTypes != null) {
345             if (!storageTypes.contains(token.storageType)) {
346                 throw new InvalidTypeException(
347                         String.format(
348                                 "verb '%s' requires parameter #%d to have storage types %s not %s. statement=%s",
349                                 verb, index, storageTypes, statement));
350             }
351         }
352
353         if (tokenTypes != null) {
354             token.load(); // Note, Token.load() sets the Token.type
355
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));
360             }
361         }
362
363         return token;
364     }
365
366     private Token getParameter(String verb, List<Object> statement, int index,
367             Map<String, Object> namespace, Set<TokenType> tokenTypes) {
368         Object item;
369         Token token;
370
371         try {
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));
377         }
378
379         try {
380             token = new Token(item, namespace);
381         } catch (Exception e) {
382             throw new StatementErrorException(String.format("parameter %d, %s", index, e));
383         }
384
385         token.load();
386
387         if (tokenTypes != null) {
388             try {
389                 token.get(); // Note, Token.get() sets the Token.type
390             } catch (UndefinedValueException e) {
391                 // OK if not yet defined
392             }
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));
397             }
398         }
399
400         return token;
401     }
402
403     private Object getRawParameter(String verb, List<Object> statement, int index,
404             Set<TokenType> tokenTypes) {
405         Object item;
406
407         try {
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));
413         }
414
415         if (tokenTypes != null) {
416             TokenType itemType = Token.classify(item);
417
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));
422             }
423         }
424
425         return item;
426     }
427
428     private Token getVariable(String verb, List<Object> statement, int index,
429             Map<String, Object> namespace) {
430         Object item;
431         Token token;
432
433         try {
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));
439         }
440
441         try {
442             token = new Token(item, namespace);
443         } catch (Exception e) {
444             throw new StatementErrorException(String.format("parameter %d, %s", index, e));
445         }
446
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));
451         }
452
453         return token;
454     }
455
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));
462         this.success = true;
463
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));
470
471             result = processRule(namespace, rule);
472
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;
480                     try {
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),
486                                 key, value, e), e);
487                     }
488                     mapped.put(key, newValue);
489                 }
490                 return mapped;
491             }
492         }
493         return null;
494     }
495
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'");
502
503         }
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, "");
508
509             result = processBlock(namespace, block);
510             if (EnumSet.of(ProcessResult.RULE_SUCCESS, ProcessResult.RULE_FAIL).contains(result)) {
511                 break;
512             } else if (result == ProcessResult.BLOCK_CONTINUE) {
513                 continue;
514             } else {
515                 throw new IllegalStateException(String.format("%s unexpected statement result: %s",
516                         result));
517             }
518         }
519         if (EnumSet.of(ProcessResult.RULE_SUCCESS, ProcessResult.BLOCK_CONTINUE).contains(result)) {
520             return ProcessResult.RULE_SUCCESS;
521         } else {
522             return ProcessResult.RULE_FAIL;
523         }
524     }
525
526     private ProcessResult processBlock(Map<String, Object> namespace, List<List<Object>> block) {
527         ProcessResult result = ProcessResult.STATEMENT_CONTINUE;
528
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));
532
533             try {
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);
538             }
539             if (EnumSet.of(ProcessResult.BLOCK_CONTINUE, ProcessResult.RULE_SUCCESS,
540                     ProcessResult.RULE_FAIL).contains(result)) {
541                 break;
542             } else if (result == ProcessResult.STATEMENT_CONTINUE) {
543                 continue;
544             } else {
545                 throw new IllegalStateException(String.format("%s unexpected statement result: %s",
546                         result));
547             }
548         }
549         if (result == ProcessResult.STATEMENT_CONTINUE) {
550             result = ProcessResult.BLOCK_CONTINUE;
551         }
552         return result;
553     }
554
555     private ProcessResult processStatement(Map<String, Object> namespace, List<Object> statement) {
556         ProcessResult result = ProcessResult.STATEMENT_CONTINUE;
557         String verb = getVerb(statement);
558
559         switch (verb) {
560         case "set":
561             result = verbSet(verb, namespace, statement);
562             break;
563         case "length":
564             result = verbLength(verb, namespace, statement);
565             break;
566         case "interpolate":
567             result = verbInterpolate(verb, namespace, statement);
568             break;
569         case "append":
570             result = verbAppend(verb, namespace, statement);
571             break;
572         case "unique":
573             result = verbUnique(verb, namespace, statement);
574             break;
575         case "split":
576             result = verbSplit(verb, namespace, statement);
577             break;
578         case "join":
579             result = verbJoin(verb, namespace, statement);
580             break;
581         case "lower":
582             result = verbLower(verb, namespace, statement);
583             break;
584         case "upper":
585             result = verbUpper(verb, namespace, statement);
586             break;
587         case "in":
588             result = verbIn(verb, namespace, statement);
589             break;
590         case "not_in":
591             result = verbNotIn(verb, namespace, statement);
592             break;
593         case "compare":
594             result = verbCompare(verb, namespace, statement);
595             break;
596         case "regexp":
597             result = verbRegexp(verb, namespace, statement);
598             break;
599         case "regexp_replace":
600             result = verbRegexpReplace(verb, namespace, statement);
601             break;
602         case "exit":
603             result = verbExit(verb, namespace, statement);
604             break;
605         case "continue":
606             result = verbContinue(verb, namespace, statement);
607             break;
608         default:
609             throw new InvalidRuleException(String.format("unknown verb '%s'", verb));
610         }
611
612         return result;
613     }
614
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);
618
619         variable.set(parameter.getObjectValue());
620         this.success = true;
621
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()));
625         }
626         return ProcessResult.STATEMENT_CONTINUE;
627     }
628
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));
634         long length;
635
636         switch (parameter.type) {
637         case ARRAY: {
638             length = parameter.getListValue().size();
639         }
640             break;
641         case MAP: {
642             length = parameter.getMapValue().size();
643         }
644             break;
645         case STRING: {
646             length = parameter.getStringValue().length();
647         }
648             break;
649         default:
650             throw new IllegalStateException(String.format("unexpected token type: %s",
651                     parameter.type));
652         }
653
654         variable.set(length);
655         this.success = true;
656
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()));
661         }
662         return ProcessResult.STATEMENT_CONTINUE;
663     }
664
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;
670
671         try {
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));
676         }
677         variable.set(newValue);
678         this.success = true;
679
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));
683         }
684
685         return ProcessResult.STATEMENT_CONTINUE;
686     }
687
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);
693
694         try {
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));
701         }
702         this.success = true;
703
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()));
708         }
709
710         return ProcessResult.STATEMENT_CONTINUE;
711     }
712
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));
717
718         List<Object> newValue = new ArrayList<Object>();
719         Set<Object> seen = new HashSet<Object>();
720
721         for (Object member : array.getListValue()) {
722             if (seen.contains(member)) {
723                 continue;
724             } else {
725                 newValue.add(member);
726                 seen.add(member);
727             }
728         }
729
730         variable.set(newValue);
731         this.success = true;
732
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()));
737         }
738
739         return ProcessResult.STATEMENT_CONTINUE;
740     }
741
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));
747
748         Pattern regexp;
749         List<String> newValue;
750
751         try {
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));
757         }
758         try {
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));
765         }
766
767         variable.set(newValue);
768         this.success = true;
769
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()));
775         }
776
777         return ProcessResult.STATEMENT_CONTINUE;
778     }
779
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));
786         String newValue;
787
788         try {
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));
794         }
795
796         variable.set(newValue);
797         this.success = true;
798
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()));
804         }
805
806         return ProcessResult.STATEMENT_CONTINUE;
807     }
808
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));
814
815         try {
816             switch (parameter.type) {
817             case STRING: {
818                 String oldValue = parameter.getStringValue();
819                 String newValue;
820                 newValue = oldValue.toLowerCase();
821                 variable.set(newValue);
822             }
823                 break;
824             case ARRAY: {
825                 List<Object> oldValue = parameter.getListValue();
826                 List<Object> newValue = new ArrayList<Object>(oldValue.size());
827                 String oldItem;
828                 String newItem;
829
830                 for (Object item : oldValue) {
831                     try {
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));
837                     }
838                     newItem = oldItem.toLowerCase();
839                     newValue.add(newItem);
840                 }
841                 variable.set(newValue);
842             }
843                 break;
844             case MAP: {
845                 Map<String, Object> oldValue = parameter.getMapValue();
846                 Map<String, Object> newValue = new LinkedHashMap<String, Object>(oldValue.size());
847
848                 for (Map.Entry<String, Object> entry : oldValue.entrySet()) {
849                     String oldKey;
850                     String newKey;
851                     Object value = entry.getValue();
852
853                     oldKey = entry.getKey();
854                     newKey = oldKey.toLowerCase();
855                     newValue.put(newKey, value);
856                 }
857                 variable.set(newValue);
858             }
859                 break;
860             default:
861                 throw new IllegalStateException(String.format("unexpected token type: %s",
862                         parameter.type));
863             }
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);
868         }
869         this.success = true;
870
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()));
875         }
876         return ProcessResult.STATEMENT_CONTINUE;
877     }
878
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));
884
885         try {
886             switch (parameter.type) {
887             case STRING: {
888                 String oldValue = parameter.getStringValue();
889                 String newValue;
890                 newValue = oldValue.toUpperCase();
891                 variable.set(newValue);
892             }
893                 break;
894             case ARRAY: {
895                 List<Object> oldValue = parameter.getListValue();
896                 List<Object> newValue = new ArrayList<Object>(oldValue.size());
897                 String oldItem;
898                 String newItem;
899
900                 for (Object item : oldValue) {
901                     try {
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));
907                     }
908                     newItem = oldItem.toUpperCase();
909                     newValue.add(newItem);
910                 }
911                 variable.set(newValue);
912             }
913                 break;
914             case MAP: {
915                 Map<String, Object> oldValue = parameter.getMapValue();
916                 Map<String, Object> newValue = new LinkedHashMap<String, Object>(oldValue.size());
917
918                 for (Map.Entry<String, Object> entry : oldValue.entrySet()) {
919                     String oldKey;
920                     String newKey;
921                     Object value = entry.getValue();
922
923                     oldKey = entry.getKey();
924                     newKey = oldKey.toUpperCase();
925                     newValue.put(newKey, value);
926                 }
927                 variable.set(newValue);
928             }
929                 break;
930             default:
931                 throw new IllegalStateException(String.format("unexpected token type: %s",
932                         parameter.type));
933             }
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);
938         }
939         this.success = true;
940
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()));
945         }
946         return ProcessResult.STATEMENT_CONTINUE;
947     }
948
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));
953
954         switch (collection.type) {
955         case ARRAY: {
956             this.success = collection.getListValue().contains(member.getObjectValue());
957         }
958             break;
959         case MAP: {
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));
964             }
965             this.success = collection.getMapValue().containsKey(member.getObjectValue());
966         }
967             break;
968         case STRING: {
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));
973             }
974             this.success = (collection.getStringValue()).contains(member.getStringValue());
975         }
976             break;
977         default:
978             throw new IllegalStateException(String.format("unexpected token type: %s",
979                     collection.type));
980         }
981
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()));
986         }
987         return ProcessResult.STATEMENT_CONTINUE;
988     }
989
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));
995
996         switch (collection.type) {
997         case ARRAY: {
998             this.success = !collection.getListValue().contains(member.getObjectValue());
999         }
1000             break;
1001         case MAP: {
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));
1006             }
1007             this.success = !collection.getMapValue().containsKey(member.getObjectValue());
1008         }
1009             break;
1010         case STRING: {
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));
1015             }
1016             this.success = !(collection.getStringValue()).contains(member.getStringValue());
1017         }
1018             break;
1019         default:
1020             throw new IllegalStateException(String.format("unexpected token type: %s",
1021                     collection.type));
1022         }
1023
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()));
1028         }
1029
1030         return ProcessResult.STATEMENT_CONTINUE;
1031     }
1032
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();
1041         boolean result;
1042
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));
1047         } else {
1048             tokenType = left.type;
1049         }
1050
1051         switch (opValue) {
1052         case "==":
1053         case "!=": {
1054             switch (tokenType) {
1055             case STRING: {
1056                 String leftValue = left.getStringValue();
1057                 String rightValue = right.getStringValue();
1058                 result = leftValue.equals(rightValue);
1059             }
1060                 break;
1061             case INTEGER: {
1062                 Long leftValue = left.getLongValue();
1063                 Long rightValue = right.getLongValue();
1064                 result = leftValue.equals(rightValue);
1065             }
1066                 break;
1067             case REAL: {
1068                 Double leftValue = left.getDoubleValue();
1069                 Double rightValue = right.getDoubleValue();
1070                 result = leftValue.equals(rightValue);
1071             }
1072                 break;
1073             case ARRAY: {
1074                 List<Object> leftValue = left.getListValue();
1075                 List<Object> rightValue = right.getListValue();
1076                 result = leftValue.equals(rightValue);
1077             }
1078                 break;
1079             case MAP: {
1080                 Map<String, Object> leftValue = left.getMapValue();
1081                 Map<String, Object> rightValue = right.getMapValue();
1082                 result = leftValue.equals(rightValue);
1083             }
1084                 break;
1085             case BOOLEAN: {
1086                 Boolean leftValue = left.getBooleanValue();
1087                 Boolean rightValue = right.getBooleanValue();
1088                 result = leftValue.equals(rightValue);
1089             }
1090                 break;
1091             case NULL: {
1092                 result = (left.getNullValue() == right.getNullValue());
1093             }
1094                 break;
1095             default: {
1096                 throw new IllegalStateException(String.format("unexpected token type: %s",
1097                         tokenType));
1098             }
1099             }
1100             if (opValue.equals("!=")) { // negate the sense of the test
1101                 result = !result;
1102             }
1103         }
1104             break;
1105         case "<":
1106         case ">=": {
1107             switch (tokenType) {
1108             case STRING: {
1109                 String leftValue = left.getStringValue();
1110                 String rightValue = right.getStringValue();
1111                 result = leftValue.compareTo(rightValue) < 0;
1112             }
1113                 break;
1114             case INTEGER: {
1115                 Long leftValue = left.getLongValue();
1116                 Long rightValue = right.getLongValue();
1117                 result = leftValue < rightValue;
1118             }
1119                 break;
1120             case REAL: {
1121                 Double leftValue = left.getDoubleValue();
1122                 Double rightValue = right.getDoubleValue();
1123                 result = leftValue < rightValue;
1124             }
1125                 break;
1126             case ARRAY:
1127             case MAP:
1128             case BOOLEAN:
1129             case NULL: {
1130                 throw new InvalidRuleException(String.format(invalidOp, opValue, tokenType));
1131             }
1132             default: {
1133                 throw new IllegalStateException(String.format("unexpected token type: %s",
1134                         tokenType));
1135             }
1136             }
1137             if (opValue.equals(">=")) { // negate the sense of the test
1138                 result = !result;
1139             }
1140         }
1141             break;
1142         case ">":
1143         case "<=": {
1144             switch (tokenType) {
1145             case STRING: {
1146                 String leftValue = left.getStringValue();
1147                 String rightValue = right.getStringValue();
1148                 result = leftValue.compareTo(rightValue) > 0;
1149             }
1150                 break;
1151             case INTEGER: {
1152                 Long leftValue = left.getLongValue();
1153                 Long rightValue = right.getLongValue();
1154                 result = leftValue > rightValue;
1155             }
1156                 break;
1157             case REAL: {
1158                 Double leftValue = left.getDoubleValue();
1159                 Double rightValue = right.getDoubleValue();
1160                 result = leftValue > rightValue;
1161             }
1162                 break;
1163             case ARRAY:
1164             case MAP:
1165             case BOOLEAN:
1166             case NULL: {
1167                 throw new InvalidRuleException(String.format(invalidOp, opValue, tokenType));
1168             }
1169             default: {
1170                 throw new IllegalStateException(String.format("unexpected token type: %s",
1171                         tokenType));
1172             }
1173             }
1174             if (opValue.equals("<=")) { // negate the sense of the test
1175                 result = !result;
1176             }
1177         }
1178             break;
1179         default: {
1180             throw new InvalidRuleException(String.format(
1181                     "verb '%s' has unknown comparison operator '%s'", verb, op.getObjectValue()));
1182         }
1183         }
1184         this.success = result;
1185
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()));
1190         }
1191         return ProcessResult.STATEMENT_CONTINUE;
1192     }
1193
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));
1198
1199         Pattern regexp;
1200         Matcher matcher;
1201
1202         try {
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));
1208         }
1209         matcher = regexp.matcher(string.getStringValue());
1210
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));
1215         } else {
1216             this.success = false;
1217             namespace.put(REGEXP_ARRAY_VARIABLE, new ArrayList<Object>());
1218             namespace.put(REGEXP_MAP_VARIABLE, new HashMap<String, Object>());
1219         }
1220
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)));
1228         }
1229
1230         return ProcessResult.STATEMENT_CONTINUE;
1231     }
1232
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));
1240
1241         Pattern regexp;
1242         Matcher matcher;
1243         String newValue;
1244
1245         try {
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));
1251         }
1252         matcher = regexp.matcher(string.getStringValue());
1253
1254         newValue = matcher.replaceAll(replacement.getStringValue());
1255         variable.set(newValue);
1256         this.success = true;
1257
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()));
1263         }
1264
1265         return ProcessResult.STATEMENT_CONTINUE;
1266     }
1267
1268     private ProcessResult verbExit(String verb, Map<String, Object> namespace,
1269             List<Object> statement) {
1270         ProcessResult statementResult = ProcessResult.STATEMENT_CONTINUE;
1271
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;
1279         boolean doExit;
1280
1281         if (exitStatus.equals("rule_succeeds")) {
1282             result = ProcessResult.RULE_SUCCESS;
1283         } else if (exitStatus.equals("rule_fails")) {
1284             result = ProcessResult.RULE_FAIL;
1285         } else {
1286             throw new InvalidRuleException(String.format("verb='%s' unknown exit status '%s'",
1287                     verb, exitStatus));
1288         }
1289
1290         if (criteria.equals("if_success")) {
1291             if (this.success) {
1292                 doExit = true;
1293             } else {
1294                 doExit = false;
1295             }
1296         } else if (criteria.equals("if_not_success")) {
1297             if (!this.success) {
1298                 doExit = true;
1299             } else {
1300                 doExit = false;
1301             }
1302         } else if (criteria.equals("always")) {
1303             doExit = true;
1304         } else if (criteria.equals("never")) {
1305             doExit = false;
1306         } else {
1307             throw new InvalidRuleException(String.format("verb='%s' unknown exit criteria '%s'",
1308                     verb, criteria));
1309         }
1310
1311         if (doExit) {
1312             statementResult = result;
1313         }
1314
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,
1319                     statementResult));
1320         }
1321
1322         return statementResult;
1323     }
1324
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();
1331         boolean doContinue;
1332
1333         if (criteria.equals("if_success")) {
1334             if (this.success) {
1335                 doContinue = true;
1336             } else {
1337                 doContinue = false;
1338             }
1339         } else if (criteria.equals("if_not_success")) {
1340             if (!this.success) {
1341                 doContinue = true;
1342             } else {
1343                 doContinue = false;
1344             }
1345         } else if (criteria.equals("always")) {
1346             doContinue = true;
1347         } else if (criteria.equals("never")) {
1348             doContinue = false;
1349         } else {
1350             throw new InvalidRuleException(String.format(
1351                     "verb='%s' unknown continue criteria '%s'", verb, criteria));
1352         }
1353
1354         if (doContinue) {
1355             statementResult = ProcessResult.BLOCK_CONTINUE;
1356         }
1357
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,
1362                     statementResult));
1363         }
1364
1365         return statementResult;
1366     }
1367
1368 }