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