1   package com.atlassian.user.search.query;
2   
3   import com.atlassian.user.util.Assert;
4   
5   import java.util.Arrays;
6   import java.util.Collection;
7   import java.util.Collections;
8   import java.util.List;
9   
10  /**
11   * Checks that a boolean query contains consistent terms.
12   */
13  public final class QueryValidator
14  {
15      private static final List<Class<? extends Query>> PRIMITIVE_QUERY_TYPES = Arrays.asList(UserQuery.class, GroupQuery.class, MembershipQuery.class);
16  
17      /**
18       * Checks for validation errors using {@link #validateQuery(Query)} and throws the first
19       * problem as an exception if any are found.
20       *
21       * @param query the query to validate
22       * @throws EntityQueryException if there is a validation error found in the query
23       */
24      public void assertValid(Query query) throws EntityQueryException
25      {
26          Collection<EntityQueryException> validationErrors = validateQuery(query);
27          if (!validationErrors.isEmpty())
28              throw validationErrors.iterator().next();
29      }
30  
31      /**
32       * Checks that a query can be handled by implementations of {@link EntityQueryParser}.
33       *
34       * @param query the query to validate
35       * @return a collection of {@link EntityQueryException}s or an empty collection if there are no problems with the query
36       */
37      public Collection<EntityQueryException> validateQuery(Query query)
38      {
39          Assert.notNull(query, "Query should not be null");
40          if (query instanceof MembershipQuery)
41          {
42              throw new IllegalArgumentException("Membership queries are illegal until we verify they can be performed on" +
43                      " all LDAP systems (i.e. the membership attribute must support substring matching");
44          }
45  
46          if (!(query instanceof BooleanQuery))
47              return Collections.emptyList();
48  
49          BooleanQuery booleanQuery = (BooleanQuery) query;
50          Class<? extends Query> queryType = getNestedQueryType(booleanQuery);
51          return validateBooleanQuery(booleanQuery, queryType);
52      }
53  
54      /**
55       * Checks that all terms in a {@link BooleanQuery} match the same criteria.
56       *
57       * @param booleanQuery the query to validate
58       * @return a collection of {@link EntityQueryException}s, or empty collection if there were no validation errors
59       */
60      public Collection<EntityQueryException> validateBooleanQuery(BooleanQuery booleanQuery, Class<? extends Query> queryType)
61      {
62          for (Query query : booleanQuery.getQueries())
63          {
64              if (query instanceof BooleanQuery)
65              {
66                  return validateBooleanQuery((BooleanQuery) query, queryType);
67              }
68              if (!queryType.isInstance(query))
69              {
70                  //noinspection ThrowableInstanceNeverThrown
71                  return Collections.singleton(new EntityQueryException("Boolean query type " + queryType.getName() +
72                      " isn't matched by clause: " + query));
73              }
74          }
75          return Collections.emptyList();
76      }
77  
78      /**
79       * Returns the type of the first clause in the provided BooleanQuery that is not itself a BooleanQuery.
80       *
81       * @throws IllegalArgumentException if the query contains a term which is not one of {@link #PRIMITIVE_QUERY_TYPES}
82       */
83      private Class<? extends Query> getNestedQueryType(BooleanQuery booleanQuery) throws IllegalArgumentException
84      {
85          Query query = booleanQuery.getQueries().get(0);
86          if (query instanceof BooleanQuery)
87          {
88              return getNestedQueryType((BooleanQuery) query);
89          }
90          for (Class<? extends Query> clazz : PRIMITIVE_QUERY_TYPES)
91          {
92              if (clazz.isInstance(query)) return clazz;
93          }
94          throw new IllegalArgumentException("Clause of unknown type in boolean query: " + booleanQuery);
95      }
96  
97  }