View Javadoc
1   /*
2    * The contents of this file are subject to the terms of the Common Development and
3    * Distribution License (the License). You may not use this file except in compliance with the
4    * License.
5    *
6    * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
7    * specific language governing permission and limitations under the License.
8    *
9    * When distributing Covered Software, include this CDDL Header Notice in each file and include
10   * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
11   * Header, with the fields enclosed by brackets [] replaced by your own identifying
12   * information: "Portions copyright [year] [name of copyright owner]".
13   *
14   * Copyright 2016 ForgeRock AS.
15   */
16  
17  package org.forgerock.api.models;
18  
19  import static org.forgerock.api.util.ValidationUtil.*;
20  
21  import java.lang.reflect.Method;
22  import java.util.Arrays;
23  import java.util.Objects;
24  
25  import com.fasterxml.jackson.annotation.JsonProperty;
26  import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
27  import org.wrensecurity.guava.common.base.Strings;
28  
29  import org.forgerock.api.ApiValidationException;
30  import org.forgerock.api.enums.CountPolicy;
31  import org.forgerock.api.enums.PagingMode;
32  import org.forgerock.api.enums.QueryType;
33  
34  /**
35   * Class that represents the Create Operation type in API descriptor.
36   */
37  @JsonDeserialize(builder = Query.Builder.class)
38  public final class Query extends Operation implements Comparable<Query> {
39  
40      private final QueryType type;
41      private final PagingMode[] pagingModes;
42      private final CountPolicy[] countPolicies;
43      private final String queryId;
44      private final String[] queryableFields;
45      private final String[] supportedSortKeys;
46  
47      private Query(Builder builder) {
48          super(builder);
49          this.type = builder.type;
50          this.pagingModes = builder.pagingModes;
51          this.countPolicies = builder.countPolicies;
52          this.queryId = builder.queryId;
53          this.queryableFields = builder.queryableFields;
54          this.supportedSortKeys = builder.supportedSortKeys;
55  
56          if (type == null) {
57              throw new ApiValidationException("type is required");
58          }
59          if (type == QueryType.ID && isEmpty(queryId)) {
60              throw new ApiValidationException("queryId required for type = ID");
61          }
62      }
63  
64      /**
65       * Getter of the query type.
66       *
67       * @return Query type num
68       */
69      public QueryType getType() {
70          return type;
71      }
72  
73      /**
74       * Getter of the paging modes.
75       *
76       * @return Paging mode enums
77       */
78      public PagingMode[] getPagingModes() {
79          return pagingModes;
80      }
81  
82      /**
83       * Getter of the supported paging policies.
84       * If the array is empty, this means that the query does not support any form of count policy, and no value for
85       * count policy should be specified.
86       *
87       * @return Supported paging policy enums
88       */
89      public CountPolicy[] getCountPolicies() {
90          return countPolicies;
91      }
92  
93      /**
94       * Getter of the query id.
95       *
96       * @return Query id
97       */
98      public String getQueryId() {
99          return queryId;
100     }
101 
102     /**
103      * Getter of the queryable fields.
104      *
105      * @return Queryable fields
106      */
107     public String[] getQueryableFields() {
108         return queryableFields;
109     }
110 
111     /**
112      * Getter of the supported sort keys.
113      *
114      * @return Supported sort keys
115      */
116     public String[] getSupportedSortKeys() {
117         return supportedSortKeys;
118     }
119 
120     @Override
121     public boolean equals(Object o) {
122         if (this == o) {
123             return true;
124         }
125         if (o == null || getClass() != o.getClass()) {
126             return false;
127         }
128         if (!super.equals(o)) {
129             return false;
130         }
131         Query query = (Query) o;
132         return type == query.type
133                 && Arrays.equals(pagingModes, query.pagingModes)
134                 && Arrays.equals(countPolicies, query.countPolicies)
135                 && Objects.equals(queryId, query.queryId)
136                 && Arrays.equals(queryableFields, query.queryableFields)
137                 && Arrays.equals(supportedSortKeys, query.supportedSortKeys);
138     }
139 
140     @Override
141     public int hashCode() {
142         return Objects.hash(super.hashCode(), type, pagingModes, countPolicies, queryId, queryableFields,
143                 supportedSortKeys);
144     }
145 
146     /**
147      * Creates a new builder for Query.
148      *
149      * @return New builder instance
150      */
151     public static Builder query() {
152         return new Builder();
153     }
154 
155     /**
156      * Allocates the Query operation type to the given Resource Builder.
157      *
158      * @param resourceBuilder - Resource Builder to add the operation
159      */
160     @Override
161     protected void allocateToResource(Resource.Builder resourceBuilder) {
162         resourceBuilder.query(this);
163     }
164 
165     /**
166      * Builds a Query object from the data stored in the annotation.
167      *
168      * @param query The annotation that stores the data.
169      * @param annotated The method that the annotation was found on.
170      * @param descriptor The root descriptor to add definitions to.
171      * @param relativeType The type relative to which schema resources should be resolved.
172      * @return Query instance
173      */
174     public static Query fromAnnotation(org.forgerock.api.annotations.Query query, Method annotated,
175             ApiDescription descriptor, Class<?> relativeType) {
176         String queryId = query.id();
177         if (query.type() == QueryType.ID && Strings.isNullOrEmpty(queryId)) {
178             if (annotated == null) {
179                 throw new IllegalArgumentException("Query is missing ID: " + query);
180             }
181             queryId = annotated.getName();
182         }
183         return query()
184                 .detailsFromAnnotation(query.operationDescription(), descriptor, relativeType)
185                 .type(query.type())
186                 .pagingModes(query.pagingModes())
187                 .countPolicies(query.countPolicies())
188                 .queryId(queryId)
189                 .queryableFields(query.queryableFields())
190                 .supportedSortKeys(query.sortKeys())
191                 .build();
192     }
193 
194     /**
195      * Compares two queries.
196      *
197      * @param query Query to compare to
198      * @return the value {@code 0} if the argument string is equal to
199      * this string; a value less than {@code 0} if this string
200      * is lexicographically less than the string argument; and a
201      * value greater than {@code 0} if this string is
202      * lexicographically greater than the string argument.
203      */
204     @Override
205     public int compareTo(final Query query) {
206         // Sort Order: EXPRESSION, FILTER, ID
207         // @Checkstyle:off
208         switch (query.getType()) {
209             case EXPRESSION:
210                 if (this.getType() == QueryType.EXPRESSION) {
211                     return 0;
212                 }
213                 return 1;
214             case FILTER:
215                 if (this.getType() == QueryType.FILTER) {
216                     return 0;
217                 }
218                 if (this.getType() == QueryType.EXPRESSION) {
219                     return -1;
220                 }
221                 return 1;
222             case ID:
223                 if (this.getType() == QueryType.ID) {
224                     return this.queryId.compareTo(query.getQueryId());
225                 }
226                 return -1;
227             default:
228                 throw new IllegalStateException("Unsupported QueryType: " + query.getType());
229         }
230         // @Checkstyle:on
231     }
232 
233     /**
234      * Builder to help construct the Read.
235      */
236     public static final class Builder extends Operation.Builder<Builder> {
237 
238         private QueryType type;
239         private PagingMode[] pagingModes;
240         private CountPolicy[] countPolicies;
241         private String queryId;
242         private String[] queryableFields;
243         private String[] supportedSortKeys;
244 
245         /**
246          * Returns the builder instance.
247          *
248          * @return Builder
249          */
250         @Override
251         protected Builder self() {
252             return this;
253         }
254 
255         /**
256          * Set the query type.
257          *
258          * @param type query type enum
259          * @return Builder
260          */
261         @JsonProperty("type")
262         public Builder type(QueryType type) {
263             this.type = type;
264             return this;
265         }
266 
267         /**
268          * Set the paging mode.
269          *
270          * @param pagingMode Query paging mode enum
271          * @return Builder
272          */
273         @JsonProperty("pagingModes")
274         public Builder pagingModes(PagingMode... pagingMode) {
275             this.pagingModes = pagingMode;
276             return this;
277         }
278 
279         /**
280          * Set the supported page count policies.
281          * If the array is empty, this means that the query does not support any form of count policy, and no value
282          * for count policy should be specified.
283          *
284          * @param countPolicy Array of supported paging mode policies
285          * @return Builder
286          */
287         @JsonProperty("countPolicies")
288         public Builder countPolicies(CountPolicy... countPolicy) {
289             this.countPolicies = countPolicy;
290             return this;
291         }
292 
293         /**
294          * Set the query id. Required if “type” is ID.
295          *
296          * @param queryId Query id
297          * @return Builder
298          */
299         @JsonProperty("queryId")
300         public Builder queryId(String queryId) {
301             this.queryId = queryId;
302             return this;
303         }
304 
305         /**
306          * Set the queryable fields.
307          *
308          * @param queryableFields Array of the fileds that are queryable
309          * @return Builder
310          */
311         @JsonProperty("queryableFields")
312         public Builder queryableFields(String... queryableFields) {
313             this.queryableFields = queryableFields;
314             return this;
315         }
316 
317         /**
318          * Set the supported sort keys.
319          *
320          * @param supportedSortKeys Array of supported sort keys
321          * @return Builder
322          */
323         @JsonProperty("supportedSortKeys")
324         public Builder supportedSortKeys(String... supportedSortKeys) {
325             this.supportedSortKeys = supportedSortKeys;
326             return this;
327         }
328 
329         /**
330          * Builds the Query instance.
331          *
332          * @return Query instance
333          */
334         public Query build() {
335             return new Query(this);
336         }
337     }
338 
339 }