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 2015 ForgeRock AS.
015 */
016
017package org.forgerock.json.resource;
018
019import static org.forgerock.json.JsonValue.field;
020import static org.forgerock.json.JsonValue.json;
021import static org.forgerock.json.JsonValue.object;
022
023import java.util.ArrayList;
024import java.util.Collections;
025import java.util.LinkedHashMap;
026import java.util.List;
027import java.util.Objects;
028
029import org.forgerock.http.routing.Version;
030import org.forgerock.json.JsonPointer;
031import org.forgerock.json.JsonValue;
032import org.forgerock.util.promise.Promise;
033import org.forgerock.util.promise.Promises;
034
035/**
036 * A utility class containing various factory methods for creating and
037 * manipulating responses.
038 */
039public final class Responses {
040
041    private Responses() {
042    }
043
044    /**
045     * Returns a new {@code JsonValue} response with the provided JSON content.
046     *
047     * @param json The JSON content.
048     * @return The new {@code ActionResponse}.
049     */
050    public static ActionResponse newActionResponse(JsonValue json) {
051        return new ActionResponseImpl(json);
052    }
053
054    /**
055     * Returns a new {@code Resource} response with the provided Resource as
056     * content.
057     *
058     * @param id The resource ID if applicable otherwise {@code null}.
059     * @param revision The resource version, if known.
060     * @param content The resource content.
061     * @return The new {@code Resource} response.
062     */
063    public static ResourceResponse newResourceResponse(String id, String revision, JsonValue content) {
064        return new ResourceResponseImpl(id, revision, content);
065    }
066
067    /**
068     * Creates a new query result with a {@code null} paged results cookie and
069     * no count of the total number of remaining results.
070     *
071     * @return The new {@code QueryResponse}.
072     */
073    public static QueryResponse newQueryResponse() {
074        return newQueryResponse(null);
075    }
076
077    /**
078     * Creates a new query result with the provided paged results cookie and
079     * no count.
080     *
081     * @param pagedResultsCookie
082     *            The opaque cookie which should be used with the next paged
083     *            results query request, or {@code null} if paged results were
084     *            not requested, or if there are not more pages to be returned.
085     * @return The new {@code QueryResponse}.
086     */
087    public static QueryResponse newQueryResponse(String pagedResultsCookie) {
088        return newQueryResponse(pagedResultsCookie, CountPolicy.NONE, QueryResponse.NO_COUNT);
089    }
090
091    /**
092     * Creates a new query result with the provided paged results cookie and
093     * a count of the total number of remaining results according to
094     * {@literal totalPagedResultsPolicy}.
095     *
096     * @param pagedResultsCookie
097     *            The opaque cookie which should be used with the next paged
098     *            results query request, or {@code null} if paged results were
099     *            not requested, or if there are not more pages to be returned.
100     * @param totalPagedResultsPolicy
101     *            The policy that was used to calculate {@literal totalPagedResults}
102     * @param totalPagedResults
103     *            The total number of paged results requested in adherence to
104     *            the {@link QueryRequest#getTotalPagedResultsPolicy()} in the request,
105     *            or {@link QueryResponse#NO_COUNT} if paged results were not requested,
106     *            the count policy is {@code NONE}, or if the total number of remaining
107     *            results is unknown.
108     * @return The new {@code QueryResponse}.
109     */
110    public static QueryResponse newQueryResponse(String pagedResultsCookie, CountPolicy totalPagedResultsPolicy,
111            int totalPagedResults) {
112        return new QueryResponseImpl(pagedResultsCookie, totalPagedResultsPolicy, totalPagedResults,
113                QueryResponse.NO_COUNT);
114    }
115
116    /**
117     * Creates a new query result with the provided paged results cookie and an
118     * estimate of the total number of remaining results.
119     *
120     * @param pagedResultsCookie
121     *            The opaque cookie which should be used with the next paged
122     *            results query request, or {@code null} if paged results were
123     *            not requested, or if there are not more pages to be returned.
124     * @param remainingPagedResults
125     *            An estimate of the total number of remaining results to be
126     *            returned in subsequent paged results query requests, or
127     *            {@code -1} if paged results were not requested, or if the total
128     *            number of remaining results is unknown.
129     *
130     * @return The new {@code QueryResponse}.
131     *
132     * @deprecated Use {@link #newQueryResponse(String, CountPolicy, int)} instead.
133     */
134    @Deprecated
135    public static QueryResponse newRemainingResultsResponse(String pagedResultsCookie, int remainingPagedResults) {
136        return new QueryResponseImpl(pagedResultsCookie, CountPolicy.NONE, QueryResponse.NO_COUNT,
137                remainingPagedResults);
138    }
139
140    private static abstract class AbstractResponseImpl implements Response {
141        private Version resourceApiVersion;
142
143        @Override
144        public void setResourceApiVersion(Version version) {
145            resourceApiVersion = version;
146        }
147
148        @Override
149        public Version getResourceApiVersion() {
150            return resourceApiVersion;
151        }
152    }
153
154    private static final class ActionResponseImpl extends AbstractResponseImpl implements ActionResponse {
155
156        private final JsonValue content;
157
158        private ActionResponseImpl(JsonValue content) {
159            this.content = content;
160        }
161
162        @Override
163        public JsonValue getJsonContent() {
164            return content;
165        }
166
167        @Override
168        public boolean equals(Object o) {
169            if (this == o) {
170                return true;
171            }
172            if (o == null || getClass() != o.getClass()) {
173                return false;
174            }
175            ActionResponse that = (ActionResponse) o;
176            return getJsonContent().getObject().equals(that.getJsonContent().getObject());
177        }
178
179        @Override
180        public int hashCode() {
181            // FIXME Implies that content is both not null and doesn't wrap null
182            return getJsonContent().getObject().hashCode();
183        }
184
185        @Override
186        public Promise<ActionResponse, ResourceException> asPromise() {
187            return Promises.<ActionResponse, ResourceException>newResultPromise(this);
188        }
189
190        @Override
191        public String toString() {
192            return json(object(field("content", content.getObject()))).toString();
193        }
194
195    }
196
197    private static final class ResourceResponseImpl extends AbstractResponseImpl implements ResourceResponse {
198
199        private final JsonValue content;
200        private final String id;
201        private final String revision;
202        private final List<JsonPointer> fields;
203
204        private ResourceResponseImpl(String id, String revision, JsonValue content) {
205            this.id = id;
206            this.revision = revision;
207            this.content = content;
208            this.fields = new ArrayList<JsonPointer>();
209        }
210
211        @Override
212        public JsonValue getContent() {
213            return content;
214        }
215
216        @Override
217        public String getId() {
218            return id;
219        }
220
221        @Override
222        public String getRevision() {
223            return revision;
224        }
225
226        @Override
227        public List<JsonPointer> getFields() {
228            return Collections.unmodifiableList(fields);
229        }
230
231        @Override
232        public boolean hasFields() {
233            return !fields.isEmpty();
234        }
235
236        @Override
237        public void addField(JsonPointer... fields) {
238            for (final JsonPointer field : fields) {
239                this.fields.add(field);
240            }
241        }
242
243        public Promise<ResourceResponse, ResourceException> asPromise() {
244            return Promises.<ResourceResponse, ResourceException>newResultPromise(this);
245        }
246
247        @Override
248        public boolean equals(final Object obj) {
249            if (obj == this) {
250                return true;
251            } else if (obj instanceof ResourceResponseImpl) {
252                final ResourceResponseImpl that = (ResourceResponseImpl) obj;
253                return isEqual(id, that.id) && isEqual(revision, that.revision);
254            } else {
255                return false;
256            }
257        }
258
259        private boolean isEqual(final String s1, final String s2) {
260            if (s1 == s2) {
261                return true;
262            } else if (s1 == null || s2 == null) {
263                return false;
264            } else {
265                return s1.equals(s2);
266            }
267        }
268
269        @Override
270        public int hashCode() {
271            final int hash = id != null ? id.hashCode() : 17;
272            return (hash * 31) + (revision != null ? revision.hashCode() : 0);
273        }
274
275        @Override
276        public String toString() {
277            final JsonValue wrapper = new JsonValue(new LinkedHashMap<>(3));
278            wrapper.add("id", id);
279            wrapper.add("rev", revision);
280            wrapper.add("content", content);
281            return wrapper.toString();
282        }
283    }
284
285    private static final class QueryResponseImpl extends AbstractResponseImpl implements QueryResponse {
286
287        private final String pagedResultsCookie;
288        private final CountPolicy totalPagedResultsPolicy;
289        private final int totalPagedResults;
290        private final int remainingPagedResults;
291
292        /**
293         * Creates a new query response with the provided paged results cookie and
294         * a count of the total number of resources according to
295         * {@link #totalPagedResultsPolicy}.
296         *
297         * @param pagedResultsCookie
298         *            The opaque cookie which should be used with the next paged
299         *            results query request, or {@code null} if paged results were
300         *            not requested, or if there are not more pages to be returned.
301         * @param totalPagedResultsPolicy
302         *            The policy that was used to calculate {@link #totalPagedResults}.
303         *            If none is specified ({@code null}), then {@link CountPolicy#NONE} is assumed.
304         * @param totalPagedResults
305         *            The total number of paged results requested in adherence to
306         *            the {@link QueryRequest#getTotalPagedResultsPolicy()} in the request,
307         *            or {@link #NO_COUNT} if paged results were not requested, the count
308         *            policy is {@code NONE}, or if the total number of results is unknown.
309         * @param remainingPagedResults
310         *            An estimate of the total number of remaining results to be
311         *            returned in subsequent paged results query requests, or
312         *            {@code -1} if paged results were not requested, or if the total
313         *            number of remaining results is unknown.
314         */
315        private QueryResponseImpl(String pagedResultsCookie, CountPolicy totalPagedResultsPolicy,
316                int totalPagedResults, int remainingPagedResults) {
317            this.pagedResultsCookie = pagedResultsCookie;
318            if (totalPagedResultsPolicy == null) {
319                totalPagedResultsPolicy = CountPolicy.NONE;
320            }
321            this.totalPagedResultsPolicy = totalPagedResultsPolicy;
322            this.totalPagedResults = totalPagedResults;
323            this.remainingPagedResults = remainingPagedResults;
324        }
325
326        @Override
327        public CountPolicy getTotalPagedResultsPolicy() {
328            return totalPagedResultsPolicy;
329        }
330
331        @Override
332        public String getPagedResultsCookie() {
333            return pagedResultsCookie;
334        }
335
336        @Override
337        public int getTotalPagedResults() {
338            return totalPagedResults;
339        }
340
341        @Override
342        public int getRemainingPagedResults() {
343            return remainingPagedResults;
344        }
345
346        @Override
347        public Promise<QueryResponse, ResourceException> asPromise() {
348            return Promises.<QueryResponse, ResourceException>newResultPromise(this);
349        }
350
351        @Override
352        public boolean equals(Object o) {
353            if (this == o) {
354                return true;
355            }
356            if (o == null || getClass() != o.getClass()) {
357                return false;
358            }
359            QueryResponseImpl that = (QueryResponseImpl) o;
360            return totalPagedResults == that.totalPagedResults
361                    && Objects.equals(pagedResultsCookie, this.pagedResultsCookie)
362                    && totalPagedResultsPolicy == that.totalPagedResultsPolicy
363                    && remainingPagedResults == that.remainingPagedResults;
364        }
365
366        @Override
367        public int hashCode() {
368            int result = Objects.hashCode(pagedResultsCookie);
369            result = 31 * result + totalPagedResultsPolicy.hashCode();
370            result = 31 * result + totalPagedResults;
371            result = 31 * result + remainingPagedResults;
372            return result;
373        }
374
375        @Override
376        public String toString() {
377            final JsonValue wrapper = new JsonValue(new LinkedHashMap<>(4));
378            wrapper.add(FIELD_TOTAL_PAGED_RESULTS, totalPagedResults);
379            wrapper.add(FIELD_TOTAL_PAGED_RESULTS_POLICY, totalPagedResultsPolicy);
380            wrapper.add(FIELD_REMAINING_PAGED_RESULTS, remainingPagedResults);
381            wrapper.add(FIELD_PAGED_RESULTS_COOKIE, pagedResultsCookie);
382            return wrapper.toString();
383        }
384    }
385}