View Javadoc

1   package com.atlassian.plugin.util.validation;
2   
3   import org.dom4j.Node;
4   
5   import java.util.ArrayList;
6   import java.util.List;
7   
8   import static com.google.common.base.Preconditions.checkNotNull;
9   
10  /**
11   * Validates a pattern of rules against a dom4j node, patterned off of
12   * <a href="http://www.schematron.com/">Schematron</a>
13   *
14   * @since 2.2.0
15   */
16  public class ValidationPattern
17  {
18      private final List<Rule> rules = new ArrayList<Rule>();
19  
20      private ValidationPattern() {
21      }
22  
23      /**
24       * @return a new pattern instance
25       */
26      public static ValidationPattern createPattern()
27      {
28          return new ValidationPattern();
29      }
30  
31      /**
32       * Adds a rule to the current pattern
33       * @param context The xpath expression to determine one or more nodes to evaluate the rules against
34       * @param tests A series of tests
35       * @return this for chaining
36       */
37      public ValidationPattern rule(String context, RuleTest... tests)
38      {
39          rules.add(new Rule(context, tests));
40          return this;
41      }
42  
43      /**
44       * Adds a rule to the current pattern, assuming the current context is "."
45       * @param tests A series of tests
46       * @return this for chaining
47       */
48      public ValidationPattern rule(RuleTest... tests)
49      {
50          rules.add(new Rule(".", tests));
51          return this;
52      }
53  
54      /**
55       * Evaluates the rules against the provided node
56       * @param node The node to evaluate
57       * @throws ValidationException If a validation error occurs.  If wanting to resolve i18n keys
58       * to messages, you can access the list of errors from the exception.
59       */
60      public void evaluate(Node node) throws ValidationException
61      {
62          List<String> errors = new ArrayList<String>();
63          for (Rule rule : rules)
64          {
65              rule.evaluate(node, errors);
66          }
67          if (!errors.isEmpty())
68          {
69              if (errors.size() == 1)
70              {
71                  throw new ValidationException(errors.get(0), errors);
72              }
73              else
74              {
75                  StringBuilder sb = new StringBuilder();
76                  sb.append("There were validation errors:\n");
77                  for (String msg : errors)
78                  {
79                      sb.append("\t- ").append(msg).append("\n");
80                  }
81                  throw new ValidationException(sb.toString(), errors);
82              }
83          }
84      }
85  
86      /**
87       * Creates a test using the passed xpath expression
88       * @param xpath The test expression
89       * @return The rule to mutate
90       */
91      public static RuleTest test(String xpath)
92      {
93          return new RuleTest(xpath);
94      }
95  
96      /**
97       * A test within a rule
98       */
99      public static class RuleTest
100     {
101         private final String xpath;
102         private String errorMessage;
103 
104         private RuleTest(String xpath)
105         {
106             this.xpath = checkNotNull(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             this.contextPattern = checkNotNull(contextPattern);
149             this.tests = checkNotNull(tests);
150         }
151 
152         private void evaluate(Node e, List<String> errors)
153         {
154             @SuppressWarnings("unchecked")
155             List<Node> contexts = e.selectNodes(contextPattern);
156 
157             if (contexts != null && contexts.size() > 0)
158             {
159                 for (Node ctxNode : contexts)
160                 {
161                     for (RuleTest test : tests)
162                     {
163                         test.evaluate(ctxNode, errors);
164                     }
165                 }
166             }
167         }
168     }
169 
170 }