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 2012-2016 ForgeRock AS. 015 */ 016 017package org.forgerock.json.resource; 018 019import static org.forgerock.http.routing.RoutingMode.EQUALS; 020import static org.forgerock.json.resource.Responses.newResourceResponse; 021import static org.forgerock.util.promise.Promises.newResultPromise; 022import static org.forgerock.json.resource.RouteMatchers.requestUriMatcher; 023 024import java.util.Collection; 025import java.util.LinkedHashMap; 026import java.util.Map; 027 028import org.forgerock.services.context.Context; 029import org.forgerock.http.routing.UriRouterContext; 030import org.forgerock.json.JsonPointer; 031import org.forgerock.json.JsonValue; 032import org.forgerock.util.Reject; 033import org.forgerock.util.promise.Promise; 034 035/** 036 * This class contains methods for creating and manipulating connection 037 * factories and connections. 038 */ 039public final class Resources { 040 041 private static final class InternalConnectionFactory implements ConnectionFactory { 042 private final RequestHandler handler; 043 044 private InternalConnectionFactory(final RequestHandler handler) { 045 this.handler = handler; 046 } 047 048 @Override 049 public void close() { 050 // Do nothing. 051 } 052 053 @Override 054 public Connection getConnection() { 055 return newInternalConnection(handler); 056 } 057 058 public Promise<Connection, ResourceException> getConnectionAsync() { 059 return newSuccessfulPromise(getConnection()); 060 } 061 } 062 063 /** 064 * Adapts the provided {@link SynchronousRequestHandler} as a 065 * {@link RequestHandler}. 066 * 067 * @param syncHandler 068 * The synchronous request handler to be adapted. 069 * @return The adapted synchronous request handler. 070 */ 071 public static RequestHandler asRequestHandler(final SynchronousRequestHandler syncHandler) { 072 return new SynchronousRequestHandlerAdapter(syncHandler); 073 } 074 075 /** 076 * Returns a JSON object containing only the specified fields from the 077 * provided JSON value. If the list of fields is empty then the value is 078 * returned unchanged. 079 * <p> 080 * <b>NOTE:</b> this method only performs a shallow copy of extracted 081 * fields, so changes to the filtered JSON value may impact the original 082 * JSON value, and vice-versa. 083 * 084 * @param resource 085 * The JSON value whose fields are to be filtered. 086 * @param fields 087 * The list of fields to be extracted. 088 * @return The filtered JSON value. 089 */ 090 public static JsonValue filterResource(final JsonValue resource, 091 final Collection<JsonPointer> fields) { 092 if (fields.isEmpty() || resource.isNull() || resource.size() == 0) { 093 return resource; 094 } else { 095 final Map<String, Object> filtered = new LinkedHashMap<>(fields.size()); 096 for (final JsonPointer field : fields) { 097 if (field.isEmpty()) { 098 // Special case - copy resource fields (assumes Map). 099 filtered.putAll(resource.asMap()); 100 } else { 101 // FIXME: what should we do if the field refers to an array element? 102 final JsonValue value = resource.get(field); 103 if (value != null) { 104 final String key = field.leaf(); 105 filtered.put(key, value.getObject()); 106 } 107 } 108 } 109 return new JsonValue(filtered); 110 } 111 } 112 113 /** 114 * Returns a JSON object containing only the specified fields from the 115 * provided resource. If the list of fields is empty then the resource is 116 * returned unchanged. 117 * <p> 118 * <b>NOTE:</b> this method only performs a shallow copy of extracted 119 * fields, so changes to the filtered resource may impact the original 120 * resource, and vice-versa. 121 * 122 * @param resource 123 * The resource whose fields are to be filtered. 124 * @param fields 125 * The list of fields to be extracted. 126 * @return The filtered resource. 127 */ 128 public static ResourceResponse filterResource(final ResourceResponse resource, 129 final Collection<JsonPointer> fields) { 130 final JsonValue unfiltered = resource.getContent(); 131 final Collection<JsonPointer> filterFields = resource.hasFields() 132 ? resource.getFields() 133 : fields; 134 final JsonValue filtered = filterResource(unfiltered, filterFields); 135 if (filtered == unfiltered) { 136 return resource; // Unchanged. 137 } else { 138 return newResourceResponse(resource.getId(), resource.getRevision(), filtered); 139 } 140 } 141 142 /** 143 * Returns a new request handler which will forward requests on to the 144 * provided collection resource provider. Incoming requests which are not 145 * appropriate for a resource collection or resource instance will result in 146 * a bad request error being returned to the client. 147 * 148 * @param provider 149 * The collection resource provider. Either an implementation of {@link CollectionResourceProvider} or 150 * a POJO annotated with annotations from {@link org.forgerock.json.resource.annotations}. 151 * @return A new request handler which will forward requests on to the 152 * provided collection resource provider. 153 */ 154 public static RequestHandler newCollection(final Object provider) { 155 boolean fromInterface = provider instanceof CollectionResourceProvider; 156 // Route requests to the collection/instance using a router. 157 final Router router = new Router(); 158 159 // Create a route for the collection. 160 final RequestHandler collectionHandler = fromInterface 161 ? new InterfaceCollectionHandler((CollectionResourceProvider) provider) 162 : new AnnotatedCollectionHandler(provider); 163 router.addRoute(requestUriMatcher(EQUALS, ""), collectionHandler); 164 165 // Create a route for the instances within the collection. 166 final RequestHandler instanceHandler = fromInterface 167 ? new InterfaceCollectionInstance((CollectionResourceProvider) provider) 168 : new AnnotationCollectionInstance(provider); 169 router.addRoute(requestUriMatcher(EQUALS, "{id}"), instanceHandler); 170 171 return router; 172 } 173 174 /** 175 * Creates a new connection to a {@link RequestHandler}. 176 * 177 * @param handler 178 * The request handler to which client requests should be 179 * forwarded. 180 * @return The new internal connection. 181 * @throws NullPointerException 182 * If {@code handler} was {@code null}. 183 */ 184 public static Connection newInternalConnection(final RequestHandler handler) { 185 return new InternalConnection(handler); 186 } 187 188 /** 189 * Creates a new connection factory which binds internal client connections 190 * to {@link RequestHandler}s. 191 * 192 * @param handler 193 * The request handler to which client requests should be 194 * forwarded. 195 * @return The new internal connection factory. 196 * @throws NullPointerException 197 * If {@code handler} was {@code null}. 198 */ 199 public static ConnectionFactory newInternalConnectionFactory(final RequestHandler handler) { 200 return new InternalConnectionFactory(handler); 201 } 202 203 /** 204 * Returns a new request handler which will forward requests on to the 205 * provided singleton resource provider. Incoming requests which are not 206 * appropriate for a singleton resource (e.g. query) will result in a bad 207 * request error being returned to the client. 208 * 209 * @param provider 210 * The singleton resource provider. Either an implementation of {@link SingletonResourceProvider} or 211 * a POJO annotated with annotations from {@link org.forgerock.json.resource.annotations}. 212 * @return A new request handler which will forward requests on to the 213 * provided singleton resource provider. 214 */ 215 public static RequestHandler newSingleton(final Object provider) { 216 if (provider instanceof SingletonResourceProvider) { 217 return new InterfaceSingletonHandler((SingletonResourceProvider) provider); 218 } else { 219 return new AnnotatedSingletonHandler(provider); 220 } 221 } 222 223 /** 224 * Returns a new request handler which will forward requests on to the 225 * provided annotated request handler. 226 * 227 * @param provider 228 * The POJO annotated with annotations from {@link org.forgerock.json.resource.annotations}. 229 * @return A new request handler which will forward requests on to the provided annotated POJO. 230 */ 231 public static RequestHandler newAnnotatedRequestHandler(final Object provider) { 232 Reject.ifTrue(provider instanceof RequestHandler, 233 "Refusing to create an annotated request handler using a provider that implements RequestHandler. " 234 + "Use the RequestHandler implementation directly instead"); 235 return new AnnotatedRequestHandler(provider); 236 } 237 238 /** 239 * Returns an uncloseable view of the provided connection. Attempts to call 240 * {@link Connection#close()} will be ignored. 241 * 242 * @param connection 243 * The connection whose {@code close} method is to be disabled. 244 * @return An uncloseable view of the provided connection. 245 */ 246 public static Connection uncloseable(final Connection connection) { 247 return new AbstractConnectionWrapper<Connection>(connection) { 248 @Override 249 public void close() { 250 // Do nothing. 251 } 252 }; 253 } 254 255 /** 256 * Returns an uncloseable view of the provided connection factory. Attempts 257 * to call {@link ConnectionFactory#close()} will be ignored. 258 * 259 * @param factory 260 * The connection factory whose {@code close} method is to be 261 * disabled. 262 * @return An uncloseable view of the provided connection factory. 263 */ 264 public static ConnectionFactory uncloseable(final ConnectionFactory factory) { 265 return new ConnectionFactory() { 266 267 @Override 268 public Promise<Connection, ResourceException> getConnectionAsync() { 269 return factory.getConnectionAsync(); 270 } 271 272 @Override 273 public Connection getConnection() throws ResourceException { 274 return factory.getConnection(); 275 } 276 277 @Override 278 public void close() { 279 // Do nothing. 280 } 281 }; 282 } 283 284 static String idOf(final Context context) { 285 return context.asContext(UriRouterContext.class).getUriTemplateVariables().get("id"); 286 } 287 288 static ResourceException newBadRequestException(final String fs, final Object... args) { 289 final String msg = String.format(fs, args); 290 return new BadRequestException(msg); 291 } 292 293 private static <V> Promise<V, ResourceException> newSuccessfulPromise(V result) { 294 return newResultPromise(result); 295 } 296 297 // Strips off the unwanted leaf routing context which was added when routing 298 // requests to a collection. 299 static Context parentOf(final Context context) { 300 assert context instanceof UriRouterContext; 301 return context.getParent(); 302 } 303 304 // Prevent instantiation. 305 private Resources() { 306 // Nothing to do. 307 } 308 309}