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}