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.api.models.Parameter.*;
020import static org.forgerock.api.models.Resource.AnnotatedTypeVariant.*;
021import static org.forgerock.api.models.SubResources.*;
022import static org.forgerock.http.routing.RoutingMode.*;
023import static org.forgerock.json.resource.Responses.*;
024import static org.forgerock.json.resource.RouteMatchers.*;
025import static org.forgerock.util.promise.Promises.*;
026
027import java.lang.reflect.InvocationTargetException;
028import java.lang.reflect.Method;
029import java.util.Collection;
030import java.util.LinkedHashMap;
031import java.util.Map;
032
033import org.forgerock.api.annotations.CollectionProvider;
034import org.forgerock.api.annotations.Path;
035import org.forgerock.api.annotations.SingletonProvider;
036import org.forgerock.api.enums.ParameterSource;
037import org.forgerock.api.models.ApiDescription;
038import org.forgerock.api.models.Items;
039import org.forgerock.api.models.Parameter;
040import org.forgerock.api.models.Resource;
041import org.forgerock.api.models.SubResources;
042import org.forgerock.http.ApiProducer;
043import org.forgerock.http.routing.UriRouterContext;
044import org.forgerock.json.JsonPointer;
045import org.forgerock.json.JsonValue;
046import org.forgerock.services.context.AbstractContext;
047import org.forgerock.services.context.Context;
048import org.forgerock.services.descriptor.Describable;
049import org.forgerock.util.Reject;
050import org.forgerock.util.promise.Promise;
051
052/**
053 * This class contains methods for creating and manipulating connection
054 * factories and connections.
055 */
056public final class Resources {
057
058    private static final class InternalConnectionFactory implements ConnectionFactory {
059        private final RequestHandler handler;
060
061        private InternalConnectionFactory(final RequestHandler handler) {
062            this.handler = handler;
063        }
064
065        @Override
066        public void close() {
067            // Do nothing.
068        }
069
070        @Override
071        public Connection getConnection() {
072            return newInternalConnection(handler);
073        }
074
075        public Promise<Connection, ResourceException> getConnectionAsync() {
076            return newSuccessfulPromise(getConnection());
077        }
078    }
079
080    /**
081     * Adapts the provided {@link SynchronousRequestHandler} as a
082     * {@link RequestHandler}.
083     *
084     * @param syncHandler
085     *            The synchronous request handler to be adapted.
086     * @return The adapted synchronous request handler.
087     */
088    public static RequestHandler asRequestHandler(final SynchronousRequestHandler syncHandler) {
089        return syncHandler instanceof Describable
090                ? new DescribedSyncRequestHandlerAdapter(syncHandler)
091                : new SynchronousRequestHandlerAdapter(syncHandler);
092    }
093
094    /**
095     * Returns a JSON object containing only the specified fields from the
096     * provided JSON value. If the list of fields is empty then the value is
097     * returned unchanged.
098     * <p>
099     * <b>NOTE:</b> this method only performs a shallow copy of extracted
100     * fields, so changes to the filtered JSON value may impact the original
101     * JSON value, and vice-versa.
102     *
103     * @param resource
104     *            The JSON value whose fields are to be filtered.
105     * @param fields
106     *            The list of fields to be extracted.
107     * @return The filtered JSON value.
108     */
109    public static JsonValue filterResource(final JsonValue resource,
110            final Collection<JsonPointer> fields) {
111        if (fields.isEmpty() || resource.isNull() || resource.size() == 0) {
112            return resource;
113        } else {
114            final Map<String, Object> filtered = new LinkedHashMap<>(fields.size());
115            for (final JsonPointer field : fields) {
116                if (field.isEmpty()) {
117                    // Special case - copy resource fields (assumes Map).
118                    filtered.putAll(resource.asMap());
119                } else {
120                    // FIXME: what should we do if the field refers to an array element?
121                    final JsonValue value = resource.get(field);
122                    if (value != null) {
123                        final String key = field.leaf();
124                        filtered.put(key, value.getObject());
125                    }
126                }
127            }
128            return new JsonValue(filtered);
129        }
130    }
131
132    /**
133     * Returns a JSON object containing only the specified fields from the
134     * provided resource. If the list of fields is empty then the resource is
135     * returned unchanged.
136     * <p>
137     * <b>NOTE:</b> this method only performs a shallow copy of extracted
138     * fields, so changes to the filtered resource may impact the original
139     * resource, and vice-versa.
140     *
141     * @param resource
142     *            The resource whose fields are to be filtered.
143     * @param fields
144     *            The list of fields to be extracted.
145     * @return The filtered resource.
146     */
147    public static ResourceResponse filterResource(final ResourceResponse resource,
148            final Collection<JsonPointer> fields) {
149        final JsonValue unfiltered = resource.getContent();
150        final Collection<JsonPointer> filterFields = resource.hasFields()
151                ? resource.getFields()
152                : fields;
153        final JsonValue filtered = filterResource(unfiltered, filterFields);
154        if (filtered == unfiltered) {
155            return resource; // Unchanged.
156        } else {
157            return newResourceResponse(resource.getId(), resource.getRevision(), filtered);
158        }
159    }
160
161    /**
162     * Creates a new connection to a {@link RequestHandler}.
163     *
164     * @param handler
165     *            The request handler to which client requests should be
166     *            forwarded.
167     * @return The new internal connection.
168     * @throws NullPointerException
169     *             If {@code handler} was {@code null}.
170     */
171    public static Connection newInternalConnection(final RequestHandler handler) {
172        return new InternalConnection(handler);
173    }
174
175    /**
176     * Creates a new connection factory which binds internal client connections
177     * to {@link RequestHandler}s.
178     *
179     * @param handler
180     *            The request handler to which client requests should be
181     *            forwarded.
182     * @return The new internal connection factory.
183     * @throws NullPointerException
184     *             If {@code handler} was {@code null}.
185     */
186    public static ConnectionFactory newInternalConnectionFactory(final RequestHandler handler) {
187        return new InternalConnectionFactory(handler);
188    }
189
190    /**
191     * Creates a new {@link RequestHandler} backed by the supplied provider. The provider can be an instance of an
192     * interface resource handler, and annotated resource handler or an annotated request handler. The type of the
193     * provider will be determined from the {@code org.forgerock.api.annotations.RequestHandler#variant}
194     * annotation property, or from the type of interface implemented.
195     * <p>
196     * Sub-paths that are declared using the {@code org.forgerock.api.annotations.Path} annotation will also be routed
197     * through the returned handler.
198     * <p>
199     * This method uses the same logic as {@link #newCollection(Object)}, {@link #newSingleton(Object)} and
200     * {@link #newAnnotatedRequestHandler(Object)} to create the underlying {@link RequestHandler}s.
201     * @param provider The provider instance.
202     * @return The constructed handler.
203     */
204    public static RequestHandler newHandler(Object provider) {
205        Router router;
206        if (provider instanceof Describable) {
207            router = new Router();
208            addHandlers(provider, router, "", null);
209        } else {
210            final DescribableResourceHandler descriptorProvider = new DescribableResourceHandler();
211            router = new Router() {
212                @Override
213                protected ApiDescription buildApi(ApiProducer<ApiDescription> producer) {
214                    return descriptorProvider.api(producer);
215                }
216            };
217            descriptorProvider.describes(addHandlers(provider, router, "",
218                    descriptorProvider.getDefinitionDescriptions()));
219        }
220        Path path = provider.getClass().getAnnotation(Path.class);
221        if (path != null) {
222            Router pathRouter = new Router();
223            pathRouter.addRoute(requestUriMatcher(STARTS_WITH, path.value()), router);
224            router = pathRouter;
225        }
226        return router;
227    }
228
229    private static Resource addHandlers(Object provider, Router router, String basePath,
230            ApiDescription definitions, Parameter... pathParameters) {
231        HandlerVariant variant = deduceHandlerVariant(provider);
232        Parameter[] nextPathParameters = pathParameters;
233        switch (variant) {
234        case SINGLETON_RESOURCE:
235            addSingletonHandlerToRouter(provider, router, basePath);
236            break;
237        case COLLECTION_RESOURCE:
238            nextPathParameters = new Parameter[pathParameters.length + 1];
239            System.arraycopy(pathParameters, 0, nextPathParameters, 0, pathParameters.length);
240            nextPathParameters[pathParameters.length] = addCollectionHandlersToRouter(provider, router, basePath);
241            String pathParameter = nextPathParameters[pathParameters.length].getName();
242            basePath = (basePath.isEmpty() ? "" : basePath + "/") + "{" + pathParameter + "}";
243            break;
244        case REQUEST_HANDLER:
245            addRequestHandlerToRouter(provider, router, basePath);
246            break;
247        default:
248            return null;
249        }
250
251        SubResources.Builder subResourcesBuilder = null;
252        for (Method m : provider.getClass().getMethods()) {
253            Path subpathAnnotation = m.getAnnotation(Path.class);
254            if (subpathAnnotation != null) {
255                if (subResourcesBuilder == null) {
256                    subResourcesBuilder = subresources();
257                }
258                String subpath = subpathAnnotation.value().replaceAll("^/", "");
259                try {
260                    Resource subResource = addHandlers(m.invoke(provider), router,
261                            basePath.isEmpty() ? subpath : basePath + "/" + subpath, definitions, nextPathParameters);
262                    if (subResource != null) {
263                        subResourcesBuilder.put(subpath, subResource);
264                    }
265                } catch (IllegalAccessException | InvocationTargetException e) {
266                    throw new IllegalArgumentException("Could not construct handler tree", e);
267                }
268            }
269        }
270        SubResources subResources = subResourcesBuilder == null ? null : subResourcesBuilder.build();
271        return makeDescriptor(provider, variant, subResources, definitions, pathParameters);
272    }
273
274    private static HandlerVariant deduceHandlerVariant(Object provider) {
275        Class<?> type = provider.getClass();
276        org.forgerock.api.annotations.RequestHandler handler =
277                provider.getClass().getAnnotation(org.forgerock.api.annotations.RequestHandler.class);
278        if (handler != null || provider instanceof RequestHandler) {
279            return HandlerVariant.REQUEST_HANDLER;
280        } else if (type.getAnnotation(SingletonProvider.class) != null
281                || provider instanceof SingletonResourceProvider) {
282            return HandlerVariant.SINGLETON_RESOURCE;
283        } else if (type.getAnnotation(CollectionProvider.class) != null
284                || provider instanceof CollectionResourceProvider) {
285            return HandlerVariant.COLLECTION_RESOURCE;
286        }
287        throw new IllegalArgumentException("Cannot deduce provider variant" + provider.getClass());
288    }
289
290    private static void addRequestHandlerToRouter(Object provider, Router router, String basePath) {
291        router.addRoute(requestUriMatcher(STARTS_WITH, basePath), new AnnotatedRequestHandler(provider));
292    }
293
294    private static Parameter addCollectionHandlersToRouter(Object provider, Router router, String basePath) {
295        boolean fromInterface = provider instanceof CollectionResourceProvider;
296        // Create a route for the collection.
297        final RequestHandler collectionHandler = fromInterface
298                ? new InterfaceCollectionHandler((CollectionResourceProvider) provider)
299                : new AnnotatedCollectionHandler(provider);
300        router.addRoute(requestUriMatcher(EQUALS, basePath), collectionHandler);
301
302        // Create a route for the instances within the collection.
303        RequestHandler instanceHandler = fromInterface
304                ? new InterfaceCollectionInstance((CollectionResourceProvider) provider)
305                : new AnnotationCollectionInstance(provider);
306        Class<?> providerClass = provider.getClass();
307        CollectionProvider providerAnnotation = providerClass.getAnnotation(CollectionProvider.class);
308        Parameter pathParameter;
309        if (providerAnnotation != null) {
310            pathParameter = Parameter.fromAnnotation(providerClass, providerAnnotation.pathParam());
311        } else {
312            pathParameter = parameter().name("id").type("string").source(ParameterSource.PATH).required(true).build();
313        }
314        String pathParam = pathParameter.getName();
315        instanceHandler = new FilterChain(instanceHandler, new CollectionInstanceIdContextFilter(pathParam));
316        router.addRoute(requestUriMatcher(EQUALS, (basePath.isEmpty() ? "" : basePath + "/") + "{" + pathParam + "}"),
317                instanceHandler);
318        return pathParameter;
319    }
320
321    private static void addSingletonHandlerToRouter(Object provider, Router router, String basePath) {
322        router.addRoute(requestUriMatcher(EQUALS, basePath),
323                provider instanceof SingletonResourceProvider
324                        ? new InterfaceSingletonHandler((SingletonResourceProvider) provider)
325                        : new AnnotatedSingletonHandler(provider));
326    }
327
328    private static Resource makeDescriptor(Object provider, HandlerVariant variant, SubResources subResources,
329            ApiDescription definitions, Parameter[] pathParameters) {
330        if (provider instanceof Describable) {
331            return null;
332        }
333        Class<?> type = provider.getClass();
334        switch (variant) {
335        case SINGLETON_RESOURCE:
336            return Resource.fromAnnotatedType(type, SINGLETON_RESOURCE, subResources, definitions, pathParameters);
337        case COLLECTION_RESOURCE:
338            final Items items = Items.fromAnnotatedType(type, definitions, subResources);
339            return Resource.fromAnnotatedType(type, COLLECTION_RESOURCE_COLLECTION, items,
340                    definitions, pathParameters);
341        case REQUEST_HANDLER:
342            return Resource.fromAnnotatedType(type, REQUEST_HANDLER, subResources, definitions, pathParameters);
343        default:
344            return null;
345        }
346    }
347
348    /**
349     * Returns a new request handler which will forward requests on to the
350     * provided collection resource provider. Incoming requests which are not
351     * appropriate for a resource collection or resource instance will result in
352     * a bad request error being returned to the client.
353     *
354     * @param provider
355     *            The collection resource provider. Either an implementation of {@link CollectionResourceProvider} or
356     *            a POJO annotated with annotations from {@link org.forgerock.json.resource.annotations}.
357     * @return A new request handler which will forward requests on to the
358     *         provided collection resource provider.
359     * @deprecated Use {@link #newHandler(Object)} instead.
360     */
361    @Deprecated
362    public static RequestHandler newCollection(final Object provider) {
363        return newHandler(provider);
364    }
365
366    /**
367     * Returns a new request handler which will forward requests on to the
368     * provided singleton resource provider. Incoming requests which are not
369     * appropriate for a singleton resource (e.g. query) will result in a bad
370     * request error being returned to the client.
371     *
372     * @param provider
373     *            The singleton resource provider. Either an implementation of {@link SingletonResourceProvider} or
374     *            a POJO annotated with annotations from {@link org.forgerock.json.resource.annotations}.
375     * @return A new request handler which will forward requests on to the
376     *         provided singleton resource provider.
377     * @deprecated Use {@link #newHandler(Object)} instead.
378     */
379    @Deprecated
380    public static RequestHandler newSingleton(final Object provider) {
381        return newHandler(provider);
382    }
383
384    /**
385     * Returns a new request handler which will forward requests on to the
386     * provided annotated request handler.
387     *
388     * @param provider
389     *            The POJO annotated with annotations from {@link org.forgerock.json.resource.annotations}.
390     * @return A new request handler which will forward requests on to the provided annotated POJO.
391     * @deprecated Use {@link #newHandler(Object)} instead.
392     */
393    @Deprecated
394    public static RequestHandler newAnnotatedRequestHandler(final Object provider) {
395        Reject.ifTrue(provider instanceof RequestHandler,
396                "Refusing to create an annotated request handler using a provider that implements RequestHandler. "
397                        + "Use the RequestHandler implementation directly instead");
398        return newHandler(provider);
399    }
400
401    /**
402     * Returns an uncloseable view of the provided connection. Attempts to call
403     * {@link Connection#close()} will be ignored.
404     *
405     * @param connection
406     *            The connection whose {@code close} method is to be disabled.
407     * @return An uncloseable view of the provided connection.
408     */
409    public static Connection uncloseable(final Connection connection) {
410        return new AbstractConnectionWrapper<Connection>(connection) {
411            @Override
412            public void close() {
413                // Do nothing.
414            }
415        };
416    }
417
418    /**
419     * Returns an uncloseable view of the provided connection factory. Attempts
420     * to call {@link ConnectionFactory#close()} will be ignored.
421     *
422     * @param factory
423     *            The connection factory whose {@code close} method is to be
424     *            disabled.
425     * @return An uncloseable view of the provided connection factory.
426     */
427    public static ConnectionFactory uncloseable(final ConnectionFactory factory) {
428        return new ConnectionFactory() {
429
430            @Override
431            public Promise<Connection, ResourceException> getConnectionAsync() {
432                return factory.getConnectionAsync();
433            }
434
435            @Override
436            public Connection getConnection() throws ResourceException {
437                return factory.getConnection();
438            }
439
440            @Override
441            public void close() {
442                // Do nothing.
443            }
444        };
445    }
446
447    static String idOf(final Context context) {
448        String idFieldName = context.asContext(IdFieldContext.class).getIdFieldName();
449        return context.asContext(UriRouterContext.class).getUriTemplateVariables().get(idFieldName);
450    }
451
452    static ResourceException newBadRequestException(final String fs, final Object... args) {
453        final String msg = String.format(fs, args);
454        return new BadRequestException(msg);
455    }
456
457    private static <V> Promise<V, ResourceException> newSuccessfulPromise(V result) {
458        return newResultPromise(result);
459    }
460
461    // Strips off the unwanted leaf routing context which was added when routing
462    // requests to a collection.
463    static Context parentOf(Context context) {
464        if (context instanceof IdFieldContext) {
465            context = context.getParent();
466        }
467        assert context instanceof UriRouterContext;
468        return context.getParent();
469    }
470
471    private static class IdFieldContext extends AbstractContext {
472
473        private static final String ID_FIELD_NAME = "IdFieldName";
474
475        protected IdFieldContext(Context parent, String idFieldName) {
476            super(parent, "IdField");
477            this.data.put(ID_FIELD_NAME, idFieldName);
478        }
479
480        protected IdFieldContext(JsonValue data, ClassLoader loader) {
481            super(data, loader);
482        }
483
484        String getIdFieldName() {
485            return this.data.get(ID_FIELD_NAME).asString();
486        }
487    }
488
489    private static final class CollectionInstanceIdContextFilter implements Filter {
490
491        private final String idFieldName;
492
493        private CollectionInstanceIdContextFilter(String idFieldName) {
494            this.idFieldName = idFieldName;
495        }
496
497        @Override
498        public Promise<ActionResponse, ResourceException> filterAction(Context context, ActionRequest request,
499                RequestHandler next) {
500            return next.handleAction(new IdFieldContext(context, idFieldName), request);
501        }
502
503        @Override
504        public Promise<ResourceResponse, ResourceException> filterCreate(Context context, CreateRequest request,
505                RequestHandler next) {
506            return next.handleCreate(new IdFieldContext(context, idFieldName), request);
507        }
508
509        @Override
510        public Promise<ResourceResponse, ResourceException> filterDelete(Context context, DeleteRequest request,
511                RequestHandler next) {
512            return next.handleDelete(new IdFieldContext(context, idFieldName), request);
513        }
514
515        @Override
516        public Promise<ResourceResponse, ResourceException> filterPatch(Context context, PatchRequest request,
517                RequestHandler next) {
518            return next.handlePatch(new IdFieldContext(context, idFieldName), request);
519        }
520
521        @Override
522        public Promise<QueryResponse, ResourceException> filterQuery(Context context, QueryRequest request,
523                QueryResourceHandler handler, RequestHandler next) {
524            return next.handleQuery(new IdFieldContext(context, idFieldName), request, handler);
525        }
526
527        @Override
528        public Promise<ResourceResponse, ResourceException> filterRead(Context context, ReadRequest request,
529                RequestHandler next) {
530            return next.handleRead(new IdFieldContext(context, idFieldName), request);
531        }
532
533        @Override
534        public Promise<ResourceResponse, ResourceException> filterUpdate(Context context, UpdateRequest request,
535                RequestHandler next) {
536            return next.handleUpdate(new IdFieldContext(context, idFieldName), request);
537        }
538    }
539
540    /**
541     * Enumeration of the possible CREST handler variants.
542     */
543    enum HandlerVariant {
544        /** A singleton resource handler. */
545        SINGLETON_RESOURCE,
546        /** A collection resource handler. */
547        COLLECTION_RESOURCE,
548        /** A plain request handler. */
549        REQUEST_HANDLER
550    }
551
552    // Prevent instantiation.
553    private Resources() {
554        // Nothing to do.
555    }
556}