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}