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