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 2014-2015 ForgeRock AS.
015 */
016
017package org.forgerock.json.resource.examples;
018
019import static org.forgerock.json.resource.Responses.newResourceResponse;
020import static org.forgerock.json.resource.RouteMatchers.requestUriMatcher;
021import static org.forgerock.json.resource.Router.uriTemplate;
022import static org.forgerock.json.resource.examples.DemoUtils.ctx;
023import static org.forgerock.json.resource.examples.DemoUtils.log;
024import static org.forgerock.util.promise.Promises.newExceptionPromise;
025import static org.forgerock.util.promise.Promises.newResultPromise;
026
027import java.util.Collections;
028import java.util.LinkedList;
029import java.util.List;
030
031import org.forgerock.services.context.Context;
032import org.forgerock.http.routing.UriRouterContext;
033import org.forgerock.http.routing.RoutingMode;
034import org.forgerock.json.JsonValue;
035import org.forgerock.json.resource.AbstractRequestHandler;
036import org.forgerock.json.resource.ActionRequest;
037import org.forgerock.json.resource.ActionResponse;
038import org.forgerock.json.resource.CollectionResourceProvider;
039import org.forgerock.json.resource.Connection;
040import org.forgerock.json.resource.CreateRequest;
041import org.forgerock.json.resource.DeleteRequest;
042import org.forgerock.json.resource.NotSupportedException;
043import org.forgerock.json.resource.PatchRequest;
044import org.forgerock.json.resource.QueryRequest;
045import org.forgerock.json.resource.QueryResourceHandler;
046import org.forgerock.json.resource.QueryResponse;
047import org.forgerock.json.resource.ReadRequest;
048import org.forgerock.json.resource.RequestHandler;
049import org.forgerock.json.resource.Requests;
050import org.forgerock.json.resource.ResourceException;
051import org.forgerock.json.resource.ResourceResponse;
052import org.forgerock.json.resource.Resources;
053import org.forgerock.json.resource.Router;
054import org.forgerock.json.resource.UpdateRequest;
055import org.forgerock.util.promise.Promise;
056
057/**
058 * An example illustrating how you can route realms / sub-realm requests using
059 * dynamic routing. Resource URLs are of the form
060 * {@code realm0/realm1/.../realmx/users/id}. During dynamic routing temporary
061 * routers and request handlers are created on demand in order to maintain state
062 * while parsing the request. Dynamic routing is probably a bit overkill for a
063 * simple routing model like this.
064 */
065public final class DynamicRealmDemo {
066
067    /**
068     * Main application.
069     *
070     * @param args
071     *            No arguments required.
072     * @throws ResourceException
073     *             If an unexpected error occurred.
074     */
075    public static void main(final String... args) throws ResourceException {
076        final RequestHandler rootRealm = realm(Collections.<String> emptyList());
077        final Connection c = Resources.newInternalConnection(rootRealm);
078
079        // Realm = [], Collection = users, Resource = alice
080        c.read(ctx(), Requests.newReadRequest("users/alice"));
081
082        // Realm = [], Collection = groups, Resource = administrators
083        c.read(ctx(), Requests.newReadRequest("groups/administrators"));
084
085        // Realm = [a], Collection = users, Resource = alice
086        c.read(ctx(), Requests.newReadRequest("a/users/alice"));
087
088        // Realm = [a, b], Collection = users, Resource = alice
089        c.read(ctx(), Requests.newReadRequest("a/b/users/alice"));
090    }
091
092    /**
093     * Returns a collection for handling users or groups.
094     *
095     * @param path
096     *            The realm containing the users or groups.
097     * @param name
098     *            The type of collection, e.g. users or groups.
099     * @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}