View Javadoc
1   /*
2    * The contents of this file are subject to the terms of the Common Development and
3    * Distribution License (the License). You may not use this file except in compliance with the
4    * License.
5    *
6    * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
7    * specific language governing permission and limitations under the License.
8    *
9    * When distributing Covered Software, include this CDDL Header Notice in each file and include
10   * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
11   * Header, with the fields enclosed by brackets [] replaced by your own identifying
12   * information: "Portions copyright [year] [name of copyright owner]".
13   *
14   * Copyright 2014-2015 ForgeRock AS.
15   */
16  
17  package org.forgerock.json.resource.examples;
18  
19  import static org.forgerock.json.resource.Responses.newResourceResponse;
20  import static org.forgerock.json.resource.RouteMatchers.requestUriMatcher;
21  import static org.forgerock.json.resource.Router.uriTemplate;
22  import static org.forgerock.json.resource.examples.DemoUtils.ctx;
23  import static org.forgerock.json.resource.examples.DemoUtils.log;
24  import static org.forgerock.util.promise.Promises.newExceptionPromise;
25  import static org.forgerock.util.promise.Promises.newResultPromise;
26  
27  import java.util.Collections;
28  import java.util.LinkedList;
29  import java.util.List;
30  
31  import org.forgerock.services.context.Context;
32  import org.forgerock.http.routing.UriRouterContext;
33  import org.forgerock.http.routing.RoutingMode;
34  import org.forgerock.json.JsonValue;
35  import org.forgerock.json.resource.AbstractRequestHandler;
36  import org.forgerock.json.resource.ActionRequest;
37  import org.forgerock.json.resource.ActionResponse;
38  import org.forgerock.json.resource.CollectionResourceProvider;
39  import org.forgerock.json.resource.Connection;
40  import org.forgerock.json.resource.CreateRequest;
41  import org.forgerock.json.resource.DeleteRequest;
42  import org.forgerock.json.resource.NotSupportedException;
43  import org.forgerock.json.resource.PatchRequest;
44  import org.forgerock.json.resource.QueryRequest;
45  import org.forgerock.json.resource.QueryResourceHandler;
46  import org.forgerock.json.resource.QueryResponse;
47  import org.forgerock.json.resource.ReadRequest;
48  import org.forgerock.json.resource.RequestHandler;
49  import org.forgerock.json.resource.Requests;
50  import org.forgerock.json.resource.ResourceException;
51  import org.forgerock.json.resource.ResourceResponse;
52  import org.forgerock.json.resource.Resources;
53  import org.forgerock.json.resource.Router;
54  import org.forgerock.json.resource.UpdateRequest;
55  import org.forgerock.util.promise.Promise;
56  
57  /**
58   * An example illustrating how you can route realms / sub-realm requests using
59   * dynamic routing. Resource URLs are of the form
60   * {@code realm0/realm1/.../realmx/users/id}. During dynamic routing temporary
61   * routers and request handlers are created on demand in order to maintain state
62   * while parsing the request. Dynamic routing is probably a bit overkill for a
63   * simple routing model like this.
64   */
65  public final class DynamicRealmDemo {
66  
67      /**
68       * Main application.
69       *
70       * @param args
71       *            No arguments required.
72       * @throws ResourceException
73       *             If an unexpected error occurred.
74       */
75      public static void main(final String... args) throws ResourceException {
76          final RequestHandler rootRealm = realm(Collections.<String> emptyList());
77          final Connection c = Resources.newInternalConnection(rootRealm);
78  
79          // Realm = [], Collection = users, Resource = alice
80          c.read(ctx(), Requests.newReadRequest("users/alice"));
81  
82          // Realm = [], Collection = groups, Resource = administrators
83          c.read(ctx(), Requests.newReadRequest("groups/administrators"));
84  
85          // Realm = [a], Collection = users, Resource = alice
86          c.read(ctx(), Requests.newReadRequest("a/users/alice"));
87  
88          // Realm = [a, b], Collection = users, Resource = alice
89          c.read(ctx(), Requests.newReadRequest("a/b/users/alice"));
90      }
91  
92      /**
93       * Returns a collection for handling users or groups.
94       *
95       * @param path
96       *            The realm containing the users or groups.
97       * @param name
98       *            The type of collection, e.g. users or groups.
99       * @return A collection for handling users or groups.
100      */
101     private static CollectionResourceProvider collection(final List<String> path, final String name) {
102         return new CollectionResourceProvider() {
103 
104             @Override
105             public Promise<ActionResponse, ResourceException> actionCollection(final Context context,
106                     final ActionRequest request) {
107                 ResourceException e = new NotSupportedException();
108                 return newExceptionPromise(e);
109             }
110 
111             @Override
112             public Promise<ActionResponse, ResourceException> actionInstance(final Context context,
113                     final String resourceId, final ActionRequest request) {
114                 ResourceException e = new NotSupportedException();
115                 return newExceptionPromise(e);
116             }
117 
118             @Override
119             public Promise<ResourceResponse, ResourceException> createInstance(final Context context,
120                     final CreateRequest request) {
121                 ResourceException e = new NotSupportedException();
122                 return newExceptionPromise(e);
123             }
124 
125             @Override
126             public Promise<ResourceResponse, ResourceException> deleteInstance(final Context context,
127                     final String resourceId, final DeleteRequest request) {
128                 ResourceException e = new NotSupportedException();
129                 return newExceptionPromise(e);
130             }
131 
132             @Override
133             public Promise<ResourceResponse, ResourceException> patchInstance(final Context context,
134                     final String resourceId, final PatchRequest request) {
135                 ResourceException e = new NotSupportedException();
136                 return newExceptionPromise(e);
137             }
138 
139             @Override
140             public Promise<QueryResponse, ResourceException> queryCollection(final Context context,
141                     final QueryRequest request, final QueryResourceHandler handler) {
142                 ResourceException e = new NotSupportedException();
143                 return newExceptionPromise(e);
144             }
145 
146             @Override
147             public Promise<ResourceResponse, ResourceException> readInstance(final Context context,
148                     final String resourceId, final ReadRequest request) {
149                 log("Reading " + name);
150                 log("    resource ID : " + resourceId);
151                 log("    realm path  : " + path);
152                 final JsonValue content =
153                         new JsonValue(Collections.singletonMap("id", (Object) resourceId));
154                 return newResultPromise(newResourceResponse(resourceId, "1", content));
155             }
156 
157             @Override
158             public Promise<ResourceResponse, ResourceException> updateInstance(final Context context,
159                     final String resourceId, final UpdateRequest request) {
160                 ResourceException e = new NotSupportedException();
161                 return newExceptionPromise(e);
162             }
163         };
164     }
165 
166     /**
167      * Returns a request handler which will handle all requests to a realm,
168      * including sub-realms, users, and groups.
169      *
170      * @param path
171      *            The realm.
172      * @return A request handler which will handle all requests to a realm,
173      *         including sub-realms, users, and groups.
174      */
175     private static RequestHandler realm(final List<String> path) {
176         final Router router = new Router();
177         router.addRoute(uriTemplate("/users"), collection(path, "user"));
178         router.addRoute(uriTemplate("/groups"), collection(path, "group"));
179         router.addRoute(requestUriMatcher(RoutingMode.STARTS_WITH, "/{realm}"), subrealms(path));
180         return router;
181     }
182 
183     /**
184      * Returns a request handler which will handle requests to a sub-realm.
185      *
186      * @param parentPath
187      *            The parent realm.
188      * @return A request handler which will handle requests to a sub-realm.
189      */
190     private static RequestHandler subrealms(final List<String> parentPath) {
191         return new AbstractRequestHandler() {
192             @Override
193             public Promise<ResourceResponse, ResourceException> handleRead(final Context context,
194                     final ReadRequest request) {
195                 return subrealm(parentPath, context).handleRead(context, request);
196             }
197 
198             private RequestHandler subrealm(final List<String> parentPath, final Context context) {
199                 final String realm = context.asContext(UriRouterContext.class).getUriTemplateVariables().get("realm");
200                 final List<String> path = new LinkedList<>(parentPath);
201                 path.add(realm);
202 
203                 // TODO: check that the path references an existing realm?
204                 return realm(path);
205             }
206         };
207     }
208 
209     private DynamicRealmDemo() {
210         // Prevent instantiation.
211     }
212 }