View Javadoc

1   package com.atlassian.plugin.util.validation;
2   
3   import org.dom4j.Node;
4   import org.apache.commons.lang.Validate;
5   
6   import java.util.ArrayList;
7   import java.util.List;
8   
9   /**
10   * Validates a pattern of rules against a dom4j node, patterned off of
11   * <a href="http://www.schematron.com/">Schematron</a>
12   *
13   * @since 2.2.0
14   */
15  public class ValidationPattern
16  {
17      private final List<Rule> rules = new ArrayList<Rule>();
18  
19      private ValidationPattern() {
20      }
21  
22      /**
23       * @return a new pattern instance
24       */
25      public static ValidationPattern createPattern()
26      {
27          return new ValidationPattern();
28      }
29  
30      /**
31       * Adds a rule to the current pattern
32       * @param context The xpath expression to determine one or more nodes to evaluate the rules against
33       * @param tests A series of tests
34       * @return this for chaining
35       */
36      public ValidationPattern rule(String context, RuleTest... tests)
37      {
38          rules.add(new Rule(context, tests));
39          return this;
40      }
41  
42      /**
43       * Adds a rule to the current pattern, assuming the current context is "."
44       * @param tests A series of tests
45       * @return this for chaining
46       */
47      public ValidationPattern rule(RuleTest... tests)
48      {
49          rules.add(new Rule(".", tests));
50          return this;
51      }
52  
53      /**
54       * Evaluates the rules against the provided node
55       * @param node The node to evaluate
56       * @throws ValidationException If a validation error occurs.  If wanting to resolve i18n keys
57       * to messages, you can access the list of errors from the exception.
58       */
59      public void evaluate(Node node) throws ValidationException
60      {
61          List<String> errors = new ArrayList<String>();
62          for (Rule rule : rules)
63          {
64              rule.evaluate(node, errors);
65          }
66          if (!errors.isEmpty())
67          {
68              if (errors.size() == 1)
69              {
70                  throw new ValidationException(errors.get(0), errors);
71              }
72              else
73              {
74                  StringBuilder sb = new StringBuilder();
75                  sb.append("There were validation errors:\n");
76                  for (String msg : errors)
77                  {
78                      sb.append("\t- ").append(msg).append("\n");
79                  }
80                  throw new ValidationException(sb.toString(), errors);
81              }
82          }
83      }
84  
85      /**
86       * Creates a test using the passed xpath expression
87       * @param xpath The test expression
88       * @return The rule to mutate
89       */
90      public static RuleTest test(String xpath)
91      {
92          return new RuleTest(xpath);
93      }
94  
95      /**
96       * A test within a rule
97       */
98      public static class RuleTest
99      {
100         private final String xpath;
101         private String errorMessage;
102 
103         private RuleTest(String xpath)
104         {
105             Validate.notNull(xpath);
106             this.xpath = xpath;
107         }
108 
109         /**
110          * The error message to use in the thrown exception if the test failes
111          * @param msg The message
112          * @return this for chaining
113          */
114         public RuleTest withError(String msg)
115         {
116             this.errorMessage = msg;
117             return this;
118         }
119 
120         private void evaluate(Node ctxNode, List<String> errors)
121         {
122             Object obj = ctxNode.selectObject(xpath);
123             if (obj == null)
124             {
125                 errors.add(errorMessage  + ": " + ctxNode.asXML());
126             }
127             else if (obj instanceof Boolean && !((Boolean)obj))
128             {
129                 errors.add(errorMessage  + ": " + ctxNode.asXML());
130             }
131             else if (obj instanceof List && ((List<?>)obj).isEmpty())
132             {
133                 errors.add(errorMessage + ": " + ctxNode.asXML());
134             }
135         }
136     }
137 
138     /**
139      * The rule as a series of tests
140      */
141     public static class Rule
142     {
143         private final String contextPattern;
144         private final RuleTest[] tests;
145 
146         private Rule(String contextPattern, RuleTest[] tests)
147         {
148             Validate.notNull(contextPattern);
149             Validate.notNull(tests);
150             this.contextPattern = contextPattern;
151             this.tests = tests;
152         }
153 
154         private void evaluate(Node e, List<String> errors)
155         {
156             @SuppressWarnings("unchecked")
157             List<Node> contexts = e.selectNodes(contextPattern);
158 
159             if (contexts != null && contexts.size() > 0)
160             {
161                 for (Node ctxNode : contexts)
162                 {
163                     for (RuleTest test : tests)
164                     {
165                         test.evaluate(ctxNode, errors);
166                     }
167                 }
168             }
169         }
170     }
171 
172 }