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 2012-2016 ForgeRock AS.
015 */
016
017package org.forgerock.json.resource;
018
019import static org.forgerock.http.routing.RoutingMode.EQUALS;
020import static org.forgerock.json.resource.Responses.newResourceResponse;
021import static org.forgerock.util.promise.Promises.newResultPromise;
022import static org.forgerock.json.resource.RouteMatchers.requestUriMatcher;
023
024import java.util.Collection;
025import java.util.LinkedHashMap;
026import java.util.Map;
027
028import org.forgerock.services.context.Context;
029import org.forgerock.http.routing.UriRouterContext;
030import org.forgerock.json.JsonPointer;
031import org.forgerock.json.JsonValue;
032import org.forgerock.util.Reject;
033import org.forgerock.util.promise.Promise;
034
035/**
036 * This class contains methods for creating and manipulating connection
037 * factories and connections.
038 */
039public final class Resources {
040
041    private static final class InternalConnectionFactory implements ConnectionFactory {
042        private final RequestHandler handler;
043
044        private InternalConnectionFactory(final RequestHandler handler) {
045            this.handler = handler;
046        }
047
048        @Override
049        public void close() {
050            // Do nothing.
051        }
052
053        @Override
054        public Connection getConnection() {
055            return newInternalConnection(handler);
056        }
057
058        public Promise<Connection, ResourceException> getConnectionAsync() {
059            return newSuccessfulPromise(getConnection());
060        }
061    }
062
063    /**
064     * Adapts the provided {@link SynchronousRequestHandler} as a
065     * {@link RequestHandler}.
066     *
067     * @param syncHandler
068     *            The synchronous request handler to be adapted.
069     * @return The adapted synchronous request handler.
070     */
071    public static RequestHandler asRequestHandler(final SynchronousRequestHandler syncHandler) {
072        return new SynchronousRequestHandlerAdapter(syncHandler);
073    }
074
075    /**
076     * Returns a JSON object containing only the specified fields from the
077     * provided JSON value. If the list of fields is empty then the value is
078     * returned unchanged.
079     * <p>
080     * <b>NOTE:</b> this method only performs a shallow copy of extracted
081     * fields, so changes to the filtered JSON value may impact the original
082     * JSON value, and vice-versa.
083     *
084     * @param resource
085     *            The JSON value whose fields are to be filtered.
086     * @param fields
087     *            The list of fields to be extracted.
088     * @return The filtered JSON value.
089     */
090    public static JsonValue filterResource(final JsonValue resource,
091            final Collection<JsonPointer> fields) {
092        if (fields.isEmpty() || resource.isNull() || resource.size() == 0) {
093            return resource;
094        } else {
095            final Map<String, Object> filtered = new LinkedHashMap<>(fields.size());
096            for (final JsonPointer field : fields) {
097                if (field.isEmpty()) {
098                    // Special case - copy resource fields (assumes Map).
099                    filtered.putAll(resource.asMap());
100                } else {
101                    // FIXME: what should we do if the field refers to an array element?
102                    final JsonValue value = resource.get(field);
103                    if (value != null) {
104                        final String key = field.leaf();
105                        filtered.put(key, value.getObject());
106                    }
107                }
108            }
109            return new JsonValue(filtered);
110        }
111    }
112
113    /**
114     * Returns a JSON object containing only the specified fields from the
115     * provided resource. If the list of fields is empty then the resource is
116     * returned unchanged.
117     * <p>
118     * <b>NOTE:</b> this method only performs a shallow copy of extracted
119     * fields, so changes to the filtered resource may impact the original
120     * resource, and vice-versa.
121     *
122     * @param resource
123     *            The resource whose fields are to be filtered.
124     * @param fields
125     *            The list of fields to be extracted.
126     * @return The filtered resource.
127     */
128    public static ResourceResponse filterResource(final ResourceResponse resource,
129            final Collection<JsonPointer> fields) {
130        final JsonValue unfiltered = resource.getContent();
131        final Collection<JsonPointer> filterFields = resource.hasFields()
132                ? resource.getFields()
133                : fields;
134        final JsonValue filtered = filterResource(unfiltered, filterFields);
135        if (filtered == unfiltered) {
136            return resource; // Unchanged.
137        } else {
138            return newResourceResponse(resource.getId(), resource.getRevision(), filtered);
139        }
140    }
141
142    /**
143     * Returns a new request handler which will forward requests on to the
144     * provided collection resource provider. Incoming requests which are not
145     * appropriate for a resource collection or resource instance will result in
146     * a bad request error being returned to the client.
147     *
148     * @param provider
149     *            The collection resource provider. Either an implementation of {@link CollectionResourceProvider} or
150     *            a POJO annotated with annotations from {@link org.forgerock.json.resource.annotations}.
151     * @return A new request handler which will forward requests on to the
152     *         provided collection resource provider.
153     */
154    public static RequestHandler newCollection(final Object provider) {
155        boolean fromInterface = provider instanceof CollectionResourceProvider;
156        // Route requests to the collection/instance using a router.
157        final Router router = new Router();
158
159        // Create a route for the collection.
160        final RequestHandler collectionHandler = fromInterface
161                ? new InterfaceCollectionHandler((CollectionResourceProvider) provider)
162                : new AnnotatedCollectionHandler(provider);
163        router.addRoute(requestUriMatcher(EQUALS, ""), collectionHandler);
164
165        // Create a route for the instances within the collection.
166        final RequestHandler instanceHandler = fromInterface
167                ? new InterfaceCollectionInstance((CollectionResourceProvider) provider)
168                : new AnnotationCollectionInstance(provider);
169        router.addRoute(requestUriMatcher(EQUALS, "{id}"), instanceHandler);
170
171        return router;
172    }
173
174    /**
175     * Creates a new connection to a {@link RequestHandler}.
176     *
177     * @param handler
178     *            The request handler to which client requests should be
179     *            forwarded.
180     * @return The new internal connection.
181     * @throws NullPointerException
182     *             If {@code handler} was {@code null}.
183     */
184    public static Connection newInternalConnection(final RequestHandler handler) {
185        return new InternalConnection(handler);
186    }
187
188    /**
189     * Creates a new connection factory which binds internal client connections
190     * to {@link RequestHandler}s.
191     *
192     * @param handler
193     *            The request handler to which client requests should be
194     *            forwarded.
195     * @return The new internal connection factory.
196     * @throws NullPointerException
197     *             If {@code handler} was {@code null}.
198     */
199    public static ConnectionFactory newInternalConnectionFactory(final RequestHandler handler) {
200        return new InternalConnectionFactory(handler);
201    }
202
203    /**
204     * Returns a new request handler which will forward requests on to the
205     * provided singleton resource provider. Incoming requests which are not
206     * appropriate for a singleton resource (e.g. query) will result in a bad
207     * request error being returned to the client.
208     *
209     * @param provider
210     *            The singleton resource provider. Either an implementation of {@link SingletonResourceProvider} or
211     *            a POJO annotated with annotations from {@link org.forgerock.json.resource.annotations}.
212     * @return A new request handler which will forward requests on to the
213     *         provided singleton resource provider.
214     */
215    public static RequestHandler newSingleton(final Object provider) {
216        if (provider instanceof SingletonResourceProvider) {
217            return new InterfaceSingletonHandler((SingletonResourceProvider) provider);
218        } else {
219            return new AnnotatedSingletonHandler(provider);
220        }
221    }
222
223    /**
224     * Returns a new request handler which will forward requests on to the
225     * provided annotated request handler.
226     *
227     * @param provider
228     *            The POJO annotated with annotations from {@link org.forgerock.json.resource.annotations}.
229     * @return A new request handler which will forward requests on to the provided annotated POJO.
230     */
231    public static RequestHandler newAnnotatedRequestHandler(final Object provider) {
232        Reject.ifTrue(provider instanceof RequestHandler,
233                "Refusing to create an annotated request handler using a provider that implements RequestHandler. "
234                        + "Use the RequestHandler implementation directly instead");
235        return new AnnotatedRequestHandler(provider);
236    }
237
238    /**
239     * Returns an uncloseable view of the provided connection. Attempts to call
240     * {@link Connection#close()} will be ignored.
241     *
242     * @param connection
243     *            The connection whose {@code close} method is to be disabled.
244     * @return An uncloseable view of the provided connection.
245     */
246    public static Connection uncloseable(final Connection connection) {
247        return new AbstractConnectionWrapper<Connection>(connection) {
248            @Override
249            public void close() {
250                // Do nothing.
251            }
252        };
253    }
254
255    /**
256     * Returns an uncloseable view of the provided connection factory. Attempts
257     * to call {@link ConnectionFactory#close()} will be ignored.
258     *
259     * @param factory
260     *            The connection factory whose {@code close} method is to be
261     *            disabled.
262     * @return An uncloseable view of the provided connection factory.
263     */
264    public static ConnectionFactory uncloseable(final ConnectionFactory factory) {
265        return new ConnectionFactory() {
266
267            @Override
268            public Promise<Connection, ResourceException> getConnectionAsync() {
269                return factory.getConnectionAsync();
270            }
271
272            @Override
273            public Connection getConnection() throws ResourceException {
274                return factory.getConnection();
275            }
276
277            @Override
278            public void close() {
279                // Do nothing.
280            }
281        };
282    }
283
284    static String idOf(final Context context) {
285        return context.asContext(UriRouterContext.class).getUriTemplateVariables().get("id");
286    }
287
288    static ResourceException newBadRequestException(final String fs, final Object... args) {
289        final String msg = String.format(fs, args);
290        return new BadRequestException(msg);
291    }
292
293    private static <V> Promise<V, ResourceException> newSuccessfulPromise(V result) {
294        return newResultPromise(result);
295    }
296
297    // Strips off the unwanted leaf routing context which was added when routing
298    // requests to a collection.
299    static Context parentOf(final Context context) {
300        assert context instanceof UriRouterContext;
301        return context.getParent();
302    }
303
304    // Prevent instantiation.
305    private Resources() {
306        // Nothing to do.
307    }
308
309}