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}