1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.forgerock.json.resource;
18
19 import static org.forgerock.api.models.Parameter.*;
20 import static org.forgerock.api.models.Resource.AnnotatedTypeVariant.*;
21 import static org.forgerock.api.models.SubResources.*;
22 import static org.forgerock.http.routing.RoutingMode.*;
23 import static org.forgerock.json.resource.Responses.*;
24 import static org.forgerock.json.resource.RouteMatchers.*;
25 import static org.forgerock.util.promise.Promises.*;
26
27 import java.lang.reflect.InvocationTargetException;
28 import java.lang.reflect.Method;
29 import java.util.Collection;
30 import java.util.LinkedHashMap;
31 import java.util.Map;
32
33 import org.forgerock.api.annotations.CollectionProvider;
34 import org.forgerock.api.annotations.Path;
35 import org.forgerock.api.annotations.SingletonProvider;
36 import org.forgerock.api.enums.ParameterSource;
37 import org.forgerock.api.models.ApiDescription;
38 import org.forgerock.api.models.Items;
39 import org.forgerock.api.models.Parameter;
40 import org.forgerock.api.models.Resource;
41 import org.forgerock.api.models.SubResources;
42 import org.forgerock.http.ApiProducer;
43 import org.forgerock.http.routing.UriRouterContext;
44 import org.forgerock.json.JsonPointer;
45 import org.forgerock.json.JsonValue;
46 import org.forgerock.services.context.AbstractContext;
47 import org.forgerock.services.context.Context;
48 import org.forgerock.services.descriptor.Describable;
49 import org.forgerock.util.Reject;
50 import org.forgerock.util.promise.Promise;
51
52
53
54
55
56 public final class Resources {
57
58 private static final class InternalConnectionFactory implements ConnectionFactory {
59 private final RequestHandler handler;
60
61 private InternalConnectionFactory(final RequestHandler handler) {
62 this.handler = handler;
63 }
64
65 @Override
66 public void close() {
67
68 }
69
70 @Override
71 public Connection getConnection() {
72 return newInternalConnection(handler);
73 }
74
75 public Promise<Connection, ResourceException> getConnectionAsync() {
76 return newSuccessfulPromise(getConnection());
77 }
78 }
79
80
81
82
83
84
85
86
87
88 public static RequestHandler asRequestHandler(final SynchronousRequestHandler syncHandler) {
89 return syncHandler instanceof Describable
90 ? new DescribedSyncRequestHandlerAdapter(syncHandler)
91 : new SynchronousRequestHandlerAdapter(syncHandler);
92 }
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
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
118 filtered.putAll(resource.asMap());
119 } else {
120
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
134
135
136
137
138
139
140
141
142
143
144
145
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;
156 } else {
157 return newResourceResponse(resource.getId(), resource.getRevision(), filtered);
158 }
159 }
160
161
162
163
164
165
166
167
168
169
170
171 public static Connection newInternalConnection(final RequestHandler handler) {
172 return new InternalConnection(handler);
173 }
174
175
176
177
178
179
180
181
182
183
184
185
186 public static ConnectionFactory newInternalConnectionFactory(final RequestHandler handler) {
187 return new InternalConnectionFactory(handler);
188 }
189
190
191
192
193
194
195
196
197
198
199
200
201
202
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
297 final RequestHandler collectionHandler = fromInterface
298 ? new InterfaceCollectionHandler((CollectionResourceProvider) provider)
299 : new AnnotatedCollectionHandler(provider);
300 router.addRoute(requestUriMatcher(EQUALS, basePath), collectionHandler);
301
302
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
350
351
352
353
354
355
356
357
358
359
360
361 @Deprecated
362 public static RequestHandler newCollection(final Object provider) {
363 return newHandler(provider);
364 }
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379 @Deprecated
380 public static RequestHandler newSingleton(final Object provider) {
381 return newHandler(provider);
382 }
383
384
385
386
387
388
389
390
391
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
403
404
405
406
407
408
409 public static Connection uncloseable(final Connection connection) {
410 return new AbstractConnectionWrapper<Connection>(connection) {
411 @Override
412 public void close() {
413
414 }
415 };
416 }
417
418
419
420
421
422
423
424
425
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
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
462
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
542
543 enum HandlerVariant {
544
545 SINGLETON_RESOURCE,
546
547 COLLECTION_RESOURCE,
548
549 REQUEST_HANDLER
550 }
551
552
553 private Resources() {
554
555 }
556 }