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.http.routing.RoutingMode.EQUALS;
20 import static org.forgerock.http.routing.RoutingMode.STARTS_WITH;
21 import static org.forgerock.json.resource.Requests.copyOfActionRequest;
22 import static org.forgerock.json.resource.Requests.copyOfApiRequest;
23 import static org.forgerock.json.resource.Requests.copyOfCreateRequest;
24 import static org.forgerock.json.resource.Requests.copyOfDeleteRequest;
25 import static org.forgerock.json.resource.Requests.copyOfPatchRequest;
26 import static org.forgerock.json.resource.Requests.copyOfQueryRequest;
27 import static org.forgerock.json.resource.Requests.copyOfReadRequest;
28 import static org.forgerock.json.resource.Requests.copyOfUpdateRequest;
29 import static org.forgerock.json.resource.ResourceApiVersionRoutingFilter.setApiVersionInfo;
30 import static org.forgerock.json.resource.Resources.newHandler;
31 import static org.forgerock.json.resource.RouteMatchers.requestResourceApiVersionMatcher;
32 import static org.forgerock.json.resource.RouteMatchers.requestUriMatcher;
33 import static org.forgerock.json.resource.RouteMatchers.selfApiMatcher;
34 import static org.forgerock.util.promise.Promises.newExceptionPromise;
35
36 import org.forgerock.api.models.ApiDescription;
37 import org.forgerock.http.ApiProducer;
38 import org.forgerock.http.routing.ApiVersionRouterContext;
39 import org.forgerock.http.routing.RoutingMode;
40 import org.forgerock.http.routing.UriRouterContext;
41 import org.forgerock.http.routing.Version;
42 import org.forgerock.services.context.Context;
43 import org.forgerock.services.descriptor.Describable;
44 import org.forgerock.services.routing.AbstractRouter;
45 import org.forgerock.services.routing.IncomparableRouteMatchException;
46 import org.forgerock.services.routing.RouteMatcher;
47 import org.forgerock.util.Pair;
48 import org.forgerock.util.promise.Promise;
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75 public class Router extends AbstractRouter<Router, Request, RequestHandler, ApiDescription>
76 implements RequestHandler {
77
78 private RequestHandler selfApiHandler = new SelfApiHandler();
79
80
81
82
83 public Router() {
84 super();
85 }
86
87
88
89
90
91
92
93
94 public Router(AbstractRouter<Router, Request, RequestHandler, ApiDescription> router) {
95 super(router);
96 }
97
98 @Override
99 protected Router getThis() {
100 return this;
101 }
102
103 @Override
104 protected RouteMatcher<Request> uriMatcher(RoutingMode mode, String pattern) {
105 return requestUriMatcher(mode, pattern);
106 }
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135 public RouteMatcher<Request> addRoute(UriTemplate uriTemplate, CollectionResourceProvider provider) {
136 RouteMatcher<Request> routeMatcher = requestUriMatcher(STARTS_WITH, uriTemplate.template);
137 addRoute(routeMatcher, newHandler(provider));
138 return routeMatcher;
139 }
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154 public RouteMatcher<Request> addRoute(UriTemplate uriTemplate, SingletonResourceProvider provider) {
155 RouteMatcher<Request> routeMatcher = requestUriMatcher(EQUALS, uriTemplate.template);
156 addRoute(routeMatcher, newHandler(provider));
157 return routeMatcher;
158 }
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174 public RouteMatcher<Request> addRoute(RoutingMode mode, UriTemplate uriTemplate, RequestHandler handler) {
175 RouteMatcher<Request> routeMatcher = requestUriMatcher(mode, uriTemplate.template);
176 addRoute(routeMatcher, handler);
177 return routeMatcher;
178 }
179
180
181
182
183
184
185
186
187 public static UriTemplate uriTemplate(String template) {
188 return new UriTemplate(template);
189 }
190
191
192
193
194
195
196
197
198
199
200
201
202 public RouteMatcher<Request> addRoute(Version version, CollectionResourceProvider provider) {
203 return addRoute(version, newHandler(provider));
204 }
205
206
207
208
209
210
211
212
213
214
215
216
217 public RouteMatcher<Request> addRoute(Version version, SingletonResourceProvider provider) {
218 return addRoute(version, newHandler(provider));
219 }
220
221
222
223
224
225
226
227
228
229
230
231 public RouteMatcher<Request> addRoute(Version version, RequestHandler handler) {
232 RouteMatcher<Request> routeMatcher = requestResourceApiVersionMatcher(version);
233 addRoute(routeMatcher, handler);
234 return routeMatcher;
235 }
236
237 private Pair<Context, RequestHandler> getBestMatch(Context context, Request request)
238 throws ResourceException {
239 try {
240 Pair<Context, RequestHandler> bestMatch = getBestRoute(context, request);
241 if (bestMatch == null) {
242 throw new NotFoundException(String.format("Resource '%s' not found", request.getResourcePath()));
243 }
244 return bestMatch;
245 } catch (IncomparableRouteMatchException e) {
246 throw new InternalServerErrorException(e.getMessage(), e);
247 }
248 }
249
250 @Override
251 public Promise<ActionResponse, ResourceException> handleAction(Context context, ActionRequest request) {
252 try {
253 Pair<Context, RequestHandler> bestMatch = getBestMatch(context, request);
254 UriRouterContext routerContext = getRouterContext(bestMatch.getFirst());
255 ActionRequest routedRequest = wasRouted(context, routerContext)
256 ? copyOfActionRequest(request).setResourcePath(getResourcePath(routerContext))
257 : request;
258 return bestMatch.getSecond().handleAction(bestMatch.getFirst(), routedRequest);
259 } catch (ResourceException e) {
260 return newExceptionPromise(e);
261 }
262 }
263
264 @Override
265 public Promise<ResourceResponse, ResourceException> handleCreate(Context context, CreateRequest request) {
266 try {
267 Pair<Context, RequestHandler> bestMatch = getBestMatch(context, request);
268 UriRouterContext routerContext = getRouterContext(bestMatch.getFirst());
269 CreateRequest routedRequest = wasRouted(context, routerContext)
270 ? copyOfCreateRequest(request).setResourcePath(getResourcePath(routerContext))
271 : request;
272 return bestMatch.getSecond().handleCreate(bestMatch.getFirst(), routedRequest);
273 } catch (ResourceException e) {
274 return newExceptionPromise(e);
275 }
276 }
277
278 @Override
279 public Promise<ResourceResponse, ResourceException> handleDelete(Context context, DeleteRequest request) {
280 try {
281 Pair<Context, RequestHandler> bestMatch = getBestMatch(context, request);
282 UriRouterContext routerContext = getRouterContext(bestMatch.getFirst());
283 DeleteRequest routedRequest = wasRouted(context, routerContext)
284 ? copyOfDeleteRequest(request).setResourcePath(getResourcePath(routerContext))
285 : request;
286 return bestMatch.getSecond().handleDelete(bestMatch.getFirst(), routedRequest);
287 } catch (ResourceException e) {
288 return newExceptionPromise(e);
289 }
290 }
291
292 @Override
293 public Promise<ResourceResponse, ResourceException> handlePatch(Context context, PatchRequest request) {
294 try {
295 Pair<Context, RequestHandler> bestMatch = getBestMatch(context, request);
296 UriRouterContext routerContext = getRouterContext(bestMatch.getFirst());
297 PatchRequest routedRequest = wasRouted(context, routerContext)
298 ? copyOfPatchRequest(request).setResourcePath(getResourcePath(routerContext))
299 : request;
300 return bestMatch.getSecond().handlePatch(bestMatch.getFirst(), routedRequest);
301 } catch (ResourceException e) {
302 return newExceptionPromise(e);
303 }
304 }
305
306 @Override
307 public Promise<QueryResponse, ResourceException> handleQuery(final Context context,
308 final QueryRequest request, final QueryResourceHandler handler) {
309 try {
310 Pair<Context, RequestHandler> bestMatch = getBestMatch(context, request);
311 final Context decoratedContext = bestMatch.getFirst();
312 UriRouterContext routerContext = getRouterContext(decoratedContext);
313 QueryRequest routedRequest = wasRouted(context, routerContext)
314 ? copyOfQueryRequest(request).setResourcePath(getResourcePath(routerContext))
315 : request;
316 QueryResourceHandler resourceHandler = new QueryResourceHandler() {
317 @Override
318 public boolean handleResource(ResourceResponse resource) {
319 if (decoratedContext.containsContext(ApiVersionRouterContext.class)) {
320 ApiVersionRouterContext apiVersionRouterContext =
321 decoratedContext.asContext(ApiVersionRouterContext.class);
322 setApiVersionInfo(apiVersionRouterContext, request, resource);
323 }
324 return handler.handleResource(resource);
325 }
326 };
327 return bestMatch.getSecond().handleQuery(decoratedContext, routedRequest, resourceHandler);
328 } catch (ResourceException e) {
329 return newExceptionPromise(e);
330 }
331 }
332
333 @Override
334 public Promise<ResourceResponse, ResourceException> handleRead(Context context, ReadRequest request) {
335 try {
336 Pair<Context, RequestHandler> bestMatch = getBestMatch(context, request);
337 UriRouterContext routerContext = getRouterContext(bestMatch.getFirst());
338 ReadRequest routedRequest = wasRouted(context, routerContext)
339 ? copyOfReadRequest(request).setResourcePath(getResourcePath(routerContext))
340 : request;
341 return bestMatch.getSecond().handleRead(bestMatch.getFirst(), routedRequest);
342 } catch (ResourceException e) {
343 return newExceptionPromise(e);
344 }
345 }
346
347 @Override
348 public Promise<ResourceResponse, ResourceException> handleUpdate(Context context, UpdateRequest request) {
349 try {
350 Pair<Context, RequestHandler> bestMatch = getBestMatch(context, request);
351 UriRouterContext routerContext = getRouterContext(bestMatch.getFirst());
352 UpdateRequest routedRequest = wasRouted(context, routerContext)
353 ? copyOfUpdateRequest(request).setResourcePath(getResourcePath(routerContext))
354 : request;
355 return bestMatch.getSecond().handleUpdate(bestMatch.getFirst(), routedRequest);
356 } catch (ResourceException e) {
357 return newExceptionPromise(e);
358 }
359 }
360
361 @Override
362 @SuppressWarnings("unchecked")
363 public ApiDescription handleApiRequest(Context context, Request request) {
364 try {
365 Pair<Context, RequestHandler> bestRoute = getBestApiRoute(context, request);
366 if (bestRoute != null) {
367 RequestHandler handler = bestRoute.getSecond();
368 if (handler instanceof Describable) {
369 Context nextContext = bestRoute.getFirst();
370 UriRouterContext routerContext = getRouterContext(nextContext);
371 Request routedRequest = wasRouted(context, routerContext)
372 ? copyOfApiRequest(request).setResourcePath(getResourcePath(routerContext))
373 : request;
374 return ((Describable<ApiDescription, Request>) handler)
375 .handleApiRequest(nextContext, routedRequest);
376 }
377 }
378 } catch (IncomparableRouteMatchException e) {
379 throw new UnsupportedOperationException(e);
380 }
381 if (thisRouterUriMatcher.evaluate(context, request) != null) {
382 return this.api;
383 }
384 throw new IllegalStateException(
385 "No route matched the request resource path " + request.getResourcePath());
386 }
387
388 private UriRouterContext getRouterContext(Context context) {
389 return context.containsContext(UriRouterContext.class)
390 ? context.asContext(UriRouterContext.class)
391 : null;
392 }
393
394 private boolean wasRouted(Context originalContext, UriRouterContext routerContext) {
395 return routerContext != null
396 && (!originalContext.containsContext(UriRouterContext.class)
397 || routerContext != originalContext.asContext(UriRouterContext.class));
398 }
399
400 private String getResourcePath(UriRouterContext routerContext) {
401 return routerContext.getRemainingUri();
402 }
403
404
405
406
407
408 public static final class UriTemplate {
409 private final String template;
410
411 private UriTemplate(String template) {
412 this.template = template;
413 }
414
415
416
417
418
419
420 @Override
421 public String toString() {
422 return template;
423 }
424 }
425
426 @Override
427 protected Pair<RouteMatcher<Request>, RequestHandler> getSelfApiHandler() {
428 return Pair.of(selfApiMatcher(), selfApiHandler);
429 }
430
431 private class SelfApiHandler extends AbstractRequestHandler implements Describable<ApiDescription, Request> {
432
433 @Override
434 public ApiDescription api(ApiProducer<ApiDescription> producer) {
435 throw new UnsupportedOperationException();
436 }
437
438 @Override
439 public ApiDescription handleApiRequest(Context context, Request request) {
440 return api;
441 }
442
443 @Override
444 public void addDescriptorListener(Listener listener) {
445 throw new UnsupportedOperationException();
446 }
447
448 @Override
449 public void removeDescriptorListener(Listener listener) {
450 throw new UnsupportedOperationException();
451 }
452 }
453 }