001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2016 ForgeRock AS.
015 */
016
017package org.forgerock.api.models;
018
019import static org.forgerock.api.util.ValidationUtil.*;
020
021import java.lang.reflect.Method;
022import java.util.Arrays;
023import java.util.Objects;
024
025import com.fasterxml.jackson.annotation.JsonProperty;
026import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
027import org.wrensecurity.guava.common.base.Strings;
028
029import org.forgerock.api.ApiValidationException;
030import org.forgerock.api.enums.CountPolicy;
031import org.forgerock.api.enums.PagingMode;
032import org.forgerock.api.enums.QueryType;
033
034/**
035 * Class that represents the Create Operation type in API descriptor.
036 */
037@JsonDeserialize(builder = Query.Builder.class)
038public final class Query extends Operation implements Comparable<Query> {
039
040    private final QueryType type;
041    private final PagingMode[] pagingModes;
042    private final CountPolicy[] countPolicies;
043    private final String queryId;
044    private final String[] queryableFields;
045    private final String[] supportedSortKeys;
046
047    private Query(Builder builder) {
048        super(builder);
049        this.type = builder.type;
050        this.pagingModes = builder.pagingModes;
051        this.countPolicies = builder.countPolicies;
052        this.queryId = builder.queryId;
053        this.queryableFields = builder.queryableFields;
054        this.supportedSortKeys = builder.supportedSortKeys;
055
056        if (type == null) {
057            throw new ApiValidationException("type is required");
058        }
059        if (type == QueryType.ID && isEmpty(queryId)) {
060            throw new ApiValidationException("queryId required for type = ID");
061        }
062    }
063
064    /**
065     * Getter of the query type.
066     *
067     * @return Query type num
068     */
069    public QueryType getType() {
070        return type;
071    }
072
073    /**
074     * Getter of the paging modes.
075     *
076     * @return Paging mode enums
077     */
078    public PagingMode[] getPagingModes() {
079        return pagingModes;
080    }
081
082    /**
083     * Getter of the supported paging policies.
084     * If the array is empty, this means that the query does not support any form of count policy, and no value for
085     * count policy should be specified.
086     *
087     * @return Supported paging policy enums
088     */
089    public CountPolicy[] getCountPolicies() {
090        return countPolicies;
091    }
092
093    /**
094     * Getter of the query id.
095     *
096     * @return Query id
097     */
098    public String getQueryId() {
099        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}