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.opendj.rest2ldap;
018
019import static org.forgerock.http.routing.RoutingMode.EQUALS;
020import static org.forgerock.http.routing.RoutingMode.STARTS_WITH;
021import static org.forgerock.json.resource.RouteMatchers.requestUriMatcher;
022import static org.forgerock.opendj.ldap.Filter.objectClassPresent;
023import static org.forgerock.opendj.ldap.SearchScope.BASE_OBJECT;
024import static org.forgerock.opendj.ldap.requests.Requests.newSearchRequest;
025import static org.forgerock.opendj.rest2ldap.Rest2ldapMessages.ERR_UNSUPPORTED_REQUEST_AGAINST_SINGLETON;
026import static org.forgerock.util.promise.Promises.newResultPromise;
027
028import org.forgerock.json.resource.ActionRequest;
029import org.forgerock.json.resource.ActionResponse;
030import org.forgerock.json.resource.BadRequestException;
031import org.forgerock.json.resource.CreateRequest;
032import org.forgerock.json.resource.DeleteRequest;
033import org.forgerock.json.resource.PatchRequest;
034import org.forgerock.json.resource.QueryRequest;
035import org.forgerock.json.resource.QueryResourceHandler;
036import org.forgerock.json.resource.QueryResponse;
037import org.forgerock.json.resource.ReadRequest;
038import org.forgerock.json.resource.Request;
039import org.forgerock.json.resource.RequestHandler;
040import org.forgerock.json.resource.ResourceException;
041import org.forgerock.json.resource.ResourceResponse;
042import org.forgerock.json.resource.Router;
043import org.forgerock.json.resource.UpdateRequest;
044import org.forgerock.opendj.ldap.DN;
045import org.forgerock.opendj.ldap.Entry;
046import org.forgerock.opendj.ldap.requests.SearchRequest;
047import org.forgerock.services.context.Context;
048import org.forgerock.util.AsyncFunction;
049import org.forgerock.util.Function;
050import org.forgerock.util.promise.Promise;
051
052/**
053 * Represents a one to one relationship between a parent resource and a child sub-resource. Removal of the parent
054 * resource implies that the child (the sub-resource) is also removed. Singletons only support read, update, patch, and
055 * action requests.
056 */
057public final class SubResourceSingleton extends SubResource {
058    /**
059     * A simple naming strategy that allows singletons to use the same processing logic as collections. The passed in
060     * resource ID will always be {@code null}.
061     */
062    private static final NamingStrategy SINGLETON_NAMING_STRATEGY = new NamingStrategy() {
063        @Override
064        public SearchRequest createSearchRequest(final DN baseDn, final String resourceId) {
065            return newSearchRequest(baseDn, BASE_OBJECT, objectClassPresent());
066        }
067
068        @Override
069        public String getResourceIdLdapAttribute() {
070            // Nothing to do.
071            return null;
072        }
073
074        @Override
075        public String decodeResourceId(final Entry entry) {
076            // It's safe to return null. The resource response will default to the _id field if present.
077            return null;
078        }
079
080        @Override
081        public void encodeResourceId(final DN baseDn, final String resourceId, final Entry entry)
082                throws ResourceException {
083            // Nothing to do because singletons cannot be created.
084        }
085    };
086
087    SubResourceSingleton(final String resourceId) {
088        super(resourceId);
089    }
090
091    /**
092     * Sets the relative URL template of the single sub-resource. The template must comprise of at least one path
093     * element. Any URL template variables will be substituted into the {@link #dnTemplate(String) DN template}.
094     *
095     * @param urlTemplate
096     *         The relative URL template.
097     * @return A reference to this object.
098     */
099    public SubResourceSingleton urlTemplate(final String urlTemplate) {
100        this.urlTemplate = urlTemplate;
101        return this;
102    }
103
104    /**
105     * Sets the relative DN template of the single sub-resource LDAP entry. The template must comprise of at least one
106     * RDN. Any DN template variables will be substituted using values extracted from the {@link #urlTemplate(String)
107     * URL template}.
108     *
109     * @param dnTemplate
110     *         The relative DN template.
111     * @return A reference to this object.
112     */
113    public SubResourceSingleton dnTemplate(final String dnTemplate) {
114        this.dnTemplate = dnTemplate;
115        return this;
116    }
117
118    /**
119     * Indicates whether this sub-resource singleton only supports read operations.
120     *
121     * @param readOnly
122     *         {@code true} if this sub-resource singleton is read-only.
123     * @return A reference to this object.
124     */
125    public SubResourceSingleton isReadOnly(final boolean readOnly) {
126        isReadOnly = readOnly;
127        return this;
128    }
129
130    @Override
131    Router addRoutes(final Router router) {
132        router.addRoute(requestUriMatcher(EQUALS, urlTemplate), readOnly(new InstanceHandler()));
133        router.addRoute(requestUriMatcher(STARTS_WITH, urlTemplate), readOnly(new SubResourceHandler()));
134        return router;
135    }
136
137    private Promise<RoutingContext, ResourceException> route(final Context context) {
138        return newResultPromise(new RoutingContext(context, dnFrom(context), resource));
139    }
140
141    private SubResourceImpl singleton(final Context context) {
142        return new SubResourceImpl(rest2Ldap, dnFrom(context), null, SINGLETON_NAMING_STRATEGY, resource);
143    }
144
145    /**
146     * Responsible for processing instance requests (RUPA) against this singleton and collection requests (CQ) to
147     * any collections sharing the same base URL as this singleton. More specifically, given the
148     * URL template /singleton/{child} then this handler processes requests against /singleton since it is
149     * both a singleton and also a collection of {child}.
150     */
151    private final class InstanceHandler extends AbstractRequestHandler {
152        @Override
153        public Promise<ActionResponse, ResourceException> handleAction(final Context context,
154                                                                       final ActionRequest request) {
155            return singleton(context).action(context, null, request);
156        }
157
158        @Override
159        public Promise<ResourceResponse, ResourceException> handleCreate(final Context context,
160                                                                         final CreateRequest request) {
161            return route(context)
162                    .thenAsync(new AsyncFunction<RoutingContext, ResourceResponse, ResourceException>() {
163                        @Override
164                        public Promise<ResourceResponse, ResourceException> apply(final RoutingContext context) {
165                            return subResourceRouterFrom(context).handleCreate(context, request);
166                        }
167                    }).thenCatch(this.<ResourceResponse>convert404To400());
168        }
169
170        @Override
171        public Promise<ResourceResponse, ResourceException> handlePatch(final Context context,
172                                                                        final PatchRequest request) {
173            return singleton(context).patch(context, null, request);
174        }
175
176        @Override
177        public Promise<QueryResponse, ResourceException> handleQuery(final Context context, final QueryRequest request,
178                                                                     final QueryResourceHandler handler) {
179            return route(context)
180                    .thenAsync(new AsyncFunction<RoutingContext, QueryResponse, ResourceException>() {
181                        @Override
182                        public Promise<QueryResponse, ResourceException> apply(final RoutingContext context) {
183                            return subResourceRouterFrom(context).handleQuery(context, request, handler);
184                        }
185                    }).thenCatch(this.<QueryResponse>convert404To400());
186        }
187
188        @Override
189        public Promise<ResourceResponse, ResourceException> handleRead(final Context context,
190                                                                       final ReadRequest request) {
191            return singleton(context).read(context, null, request);
192        }
193
194        @Override
195        public Promise<ResourceResponse, ResourceException> handleUpdate(final Context context,
196                                                                         final UpdateRequest request) {
197            return singleton(context).update(context, null, request);
198        }
199
200        @Override
201        protected <V> Promise<V, ResourceException> handleRequest(final Context context, final Request request) {
202            return new BadRequestException(ERR_UNSUPPORTED_REQUEST_AGAINST_SINGLETON.get().toString()).asPromise();
203        }
204
205        private <T> Function<ResourceException, T, ResourceException> convert404To400() {
206            return SubResource.convert404To400(ERR_UNSUPPORTED_REQUEST_AGAINST_SINGLETON.get());
207        }
208    }
209
210
211
212    /**
213     * Responsible for routing requests to sub-resources of this singleton. More specifically, given
214     * the URL template /singleton then this handler processes all requests beneath /singleton.
215     */
216    private final class SubResourceHandler implements RequestHandler {
217        @Override
218        public Promise<ActionResponse, ResourceException> handleAction(final Context context,
219                                                                       final ActionRequest request) {
220            return route(context).thenAsync(new AsyncFunction<RoutingContext, ActionResponse, ResourceException>() {
221                @Override
222                public Promise<ActionResponse, ResourceException> apply(final RoutingContext context) {
223                    return subResourceRouterFrom(context).handleAction(context, request);
224                }
225            });
226        }
227
228        @Override
229        public Promise<ResourceResponse, ResourceException> handleCreate(final Context context,
230                                                                         final CreateRequest request) {
231            return route(context).thenAsync(new AsyncFunction<RoutingContext, ResourceResponse, ResourceException>() {
232                @Override
233                public Promise<ResourceResponse, ResourceException> apply(final RoutingContext context) {
234                    return subResourceRouterFrom(context).handleCreate(context, request);
235                }
236            });
237        }
238
239        @Override
240        public Promise<ResourceResponse, ResourceException> handleDelete(final Context context,
241                                                                         final DeleteRequest request) {
242            return route(context).thenAsync(new AsyncFunction<RoutingContext, ResourceResponse, ResourceException>() {
243                @Override
244                public Promise<ResourceResponse, ResourceException> apply(final RoutingContext context) {
245                    return subResourceRouterFrom(context).handleDelete(context, request);
246                }
247            });
248        }
249
250        @Override
251        public Promise<ResourceResponse, ResourceException> handlePatch(final Context context,
252                                                                        final PatchRequest request) {
253            return route(context).thenAsync(new AsyncFunction<RoutingContext, ResourceResponse, ResourceException>() {
254                @Override
255                public Promise<ResourceResponse, ResourceException> apply(final RoutingContext context) {
256                    return subResourceRouterFrom(context).handlePatch(context, request);
257                }
258            });
259        }
260
261        @Override
262        public Promise<QueryResponse, ResourceException> handleQuery(final Context context, final QueryRequest request,
263                                                                     final QueryResourceHandler handler) {
264            return route(context).thenAsync(new AsyncFunction<RoutingContext, QueryResponse, ResourceException>() {
265                @Override
266                public Promise<QueryResponse, ResourceException> apply(final RoutingContext context) {
267                    return subResourceRouterFrom(context).handleQuery(context, request, handler);
268                }
269            });
270        }
271
272        @Override
273        public Promise<ResourceResponse, ResourceException> handleRead(final Context context,
274                                                                       final ReadRequest request) {
275            return route(context).thenAsync(new AsyncFunction<RoutingContext, ResourceResponse, ResourceException>() {
276                @Override
277                public Promise<ResourceResponse, ResourceException> apply(final RoutingContext context) {
278                    return subResourceRouterFrom(context).handleRead(context, request);
279                }
280            });
281        }
282
283        @Override
284        public Promise<ResourceResponse, ResourceException> handleUpdate(final Context context,
285                                                                         final UpdateRequest request) {
286            return route(context).thenAsync(new AsyncFunction<RoutingContext, ResourceResponse, ResourceException>() {
287                @Override
288                public Promise<ResourceResponse, ResourceException> apply(final RoutingContext context) {
289                    return subResourceRouterFrom(context).handleUpdate(context, request);
290                }
291            });
292        }
293    }
294}