001: /*
002: * Copyright 2005-2007 Noelios Consulting.
003: *
004: * The contents of this file are subject to the terms of the Common Development
005: * and Distribution License (the "License"). You may not use this file except in
006: * compliance with the License.
007: *
008: * You can obtain a copy of the license at
009: * http://www.opensource.org/licenses/cddl1.txt See the License for the specific
010: * language governing permissions and limitations under the License.
011: *
012: * When distributing Covered Code, include this CDDL HEADER in each file and
013: * include the License file at http://www.opensource.org/licenses/cddl1.txt If
014: * applicable, add the following below this CDDL HEADER, with the fields
015: * enclosed by brackets "[]" replaced with your own identifying information:
016: * Portions Copyright [yyyy] [name of copyright owner]
017: */
018:
019: package org.restlet;
020:
021: import java.lang.reflect.Constructor;
022: import java.util.Set;
023: import java.util.logging.Level;
024:
025: import org.restlet.data.Method;
026: import org.restlet.data.Request;
027: import org.restlet.data.Response;
028: import org.restlet.data.Status;
029: import org.restlet.resource.Resource;
030:
031: /**
032: * Restlet that can find the target resource that will concretely handle a call.
033: * Based on a given resource class, it is also capable of instantiating the
034: * resource with the call's context, request and response without requiring the
035: * usage of a subclass. It will use either the constructor with three arguments:
036: * context, request, response; or it will invoke the default constructor then
037: * invoke the init() method with the same arguments.<br>
038: * <br>
039: * Once the target resource has been found, the call is automatically dispatched
040: * to the appropriate handle*() method (where the '*' character corresponds to
041: * the method name) if the corresponding allow*() method returns true.<br>
042: * <br>
043: * For example, if you want to support a MOVE method for a WebDAV server, you
044: * just have to add a handleMove() method in your subclass of Resource and it
045: * will be automatically be used by the Finder instance at runtime.<br>
046: * <br>
047: * If no matching handle*() method is found, then a
048: * Status.CLIENT_ERROR_METHOD_NOT_ALLOWED is returned.
049: *
050: * @see <a href="http://www.restlet.org/documentation/1.0/tutorial#part12">Tutorial: Reaching
051: * target Resources</a>
052: * @author Jerome Louvel (contact@noelios.com)
053: */
054: public class Finder extends Restlet {
055: /** Target resource class. */
056: private Class<? extends Resource> targetClass;
057:
058: /**
059: * Constructor.
060: */
061: public Finder() {
062: this (null);
063: }
064:
065: /**
066: * Constructor.
067: *
068: * @param context
069: * The context.
070: */
071: public Finder(Context context) {
072: this (context, null);
073: }
074:
075: /**
076: * Constructor.
077: *
078: * @param context
079: * The context.
080: * @param targetClass
081: * The target resource class.
082: */
083: public Finder(Context context, Class<? extends Resource> targetClass) {
084: super (context);
085: this .targetClass = targetClass;
086: }
087:
088: /**
089: * Indicates if a method is allowed on a target resource.
090: *
091: * @param method
092: * The method to test.
093: * @param target
094: * The target resource.
095: * @return True if a method is allowed on a target resource.
096: */
097: private boolean allowMethod(Method method, Resource target) {
098: boolean result = false;
099:
100: if (target != null) {
101: if (method.equals(Method.GET) || method.equals(Method.HEAD)) {
102: result = target.allowGet();
103: } else if (method.equals(Method.POST)) {
104: result = target.allowPost();
105: } else if (method.equals(Method.PUT)) {
106: result = target.allowPut();
107: } else if (method.equals(Method.DELETE)) {
108: result = target.allowDelete();
109: } else if (method.equals(Method.OPTIONS)) {
110: result = true;
111: } else {
112: // Dynamically introspect the target resource to detect a
113: // matching "allow" method.
114: java.lang.reflect.Method allowMethod = getAllowMethod(
115: method, target);
116: if (allowMethod != null) {
117: result = (Boolean) invoke(target, allowMethod);
118: }
119: }
120: }
121:
122: return result;
123: }
124:
125: /**
126: * Finds the target Resource if available. The default behavior is to invoke
127: * the {@link #createResource(Request, Response)} method.
128: *
129: * @param request
130: * The request to handle.
131: * @param response
132: * The response to update.
133: * @return The target resource if available or null.
134: */
135: public Resource findTarget(Request request, Response response) {
136: return createResource(request, response);
137: }
138:
139: /**
140: * Creates a new instance of the resource class designated by the
141: * "targetClass" property.
142: *
143: * @param request
144: * The request to handle.
145: * @param response
146: * The response to update.
147: * @return The created resource or null.
148: */
149: public Resource createResource(Request request, Response response) {
150: Resource result = null;
151:
152: if (getTargetClass() != null) {
153: try {
154: Constructor<? extends Resource> constructor;
155: try {
156: // Invoke the constructor with Context, Request and Response
157: // parameters
158: constructor = getTargetClass().getConstructor(
159: Context.class, Request.class,
160: Response.class);
161: result = constructor.newInstance(getContext(),
162: request, response);
163: } catch (NoSuchMethodException nsme) {
164: // Invoke the default constructor then the init(Context,
165: // Request, Response) method.
166: constructor = getTargetClass().getConstructor();
167: if (constructor != null) {
168: result = (Resource) constructor.newInstance();
169: result.init(getContext(), request, response);
170: }
171: }
172: } catch (Exception e) {
173: getLogger()
174: .log(
175: Level.WARNING,
176: "Exception while instantiating the target resource.",
177: e);
178: }
179: }
180:
181: return result;
182: }
183:
184: /**
185: * Returns the allow method matching the given method name.
186: *
187: * @param method
188: * The method to match.
189: * @param target
190: * The target resource.
191: * @return The allow method matching the given method name.
192: */
193: private java.lang.reflect.Method getAllowMethod(Method method,
194: Resource target) {
195: return getMethod("allow", method, target);
196: }
197:
198: /**
199: * Returns the handle method matching the given method name.
200: *
201: * @param method
202: * The method to match.
203: * @return The handle method matching the given method name.
204: */
205: private java.lang.reflect.Method getHandleMethod(Resource target,
206: Method method) {
207: return getMethod("handle", method, target);
208: }
209:
210: /**
211: * Returns the method matching the given prefix and method name.
212: *
213: * @param prefix
214: * The method prefix to match (ex: "allow" or "handle").
215: * @param method
216: * The method to match.
217: * @return The method matching the given prefix and method name.
218: */
219: private java.lang.reflect.Method getMethod(String prefix,
220: Method method, Object target, Class<?>... classes) {
221: java.lang.reflect.Method result = null;
222: StringBuilder sb = new StringBuilder();
223: String methodName = method.getName().toLowerCase();
224:
225: if ((methodName != null) && (methodName.length() > 0)) {
226: sb.append(prefix);
227: sb.append(Character.toUpperCase(methodName.charAt(0)));
228: sb.append(methodName.substring(1));
229: }
230:
231: try {
232: result = target.getClass()
233: .getMethod(sb.toString(), classes);
234: } catch (SecurityException e) {
235: getLogger().log(
236: Level.WARNING,
237: "Couldn't access the " + prefix + " method for \""
238: + method + "\"", e);
239: } catch (NoSuchMethodException e) {
240: getLogger().log(
241: Level.INFO,
242: "Couldn't find the " + prefix + " method for \""
243: + method + "\"", e);
244: }
245:
246: return result;
247: }
248:
249: /**
250: * Returns the target Resource class.
251: *
252: * @return the target Resource class.
253: */
254: public Class<? extends Resource> getTargetClass() {
255: return this .targetClass;
256: }
257:
258: /**
259: * Handles a call.
260: *
261: * @param request
262: * The request to handle.
263: * @param response
264: * The response to update.
265: */
266: public void handle(Request request, Response response) {
267: init(request, response);
268:
269: if (isStarted()) {
270: Resource target = findTarget(request, response);
271:
272: if (!response.getStatus().equals(Status.SUCCESS_OK)) {
273: // Probably during the instantiation of the target resource, or
274: // earlier the status was changed from the default one. Don't go
275: // further.
276: } else if (target == null) {
277: // If the currrent status is a success but we couldn't find the
278: // target resource for the request's resource URI, then we set
279: // the response status to 404 (Not Found).
280: response.setStatus(Status.CLIENT_ERROR_NOT_FOUND);
281: } else {
282: Method method = request.getMethod();
283:
284: if (method == null) {
285: response.setStatus(Status.CLIENT_ERROR_BAD_REQUEST,
286: "No method specified");
287: } else {
288: if (!allowMethod(method, target)) {
289: response
290: .setStatus(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED);
291: updateAllowedMethods(response, target);
292: } else {
293: if (method.equals(Method.GET)) {
294: target.handleGet();
295: } else if (method.equals(Method.HEAD)) {
296: target.handleHead();
297: } else if (method.equals(Method.POST)) {
298: target.handlePost();
299: } else if (method.equals(Method.PUT)) {
300: target.handlePut();
301: } else if (method.equals(Method.DELETE)) {
302: target.handleDelete();
303: } else if (method.equals(Method.OPTIONS)) {
304: target.handleOptions();
305: } else {
306: java.lang.reflect.Method handleMethod = getHandleMethod(
307: target, method);
308: if (handleMethod != null) {
309: invoke(target, handleMethod);
310: } else {
311: response
312: .setStatus(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED);
313: }
314: }
315: }
316: }
317: }
318: }
319: }
320:
321: /**
322: * Invokes a method with the given arguments.
323: *
324: * @param target
325: * The target object.
326: * @param method
327: * The method to invoke.
328: * @param args
329: * The arguments to pass.
330: * @return Invocation result.
331: */
332: private Object invoke(Object target,
333: java.lang.reflect.Method method, Object... args) {
334: Object result = null;
335:
336: if (method != null) {
337: try {
338: result = method.invoke(target, args);
339: } catch (Exception e) {
340: getLogger().log(
341: Level.WARNING,
342: "Couldn't invoke the handle method for \""
343: + method + "\"", e);
344: }
345: }
346:
347: return result;
348: }
349:
350: /**
351: * Updates the set of allowed methods on the response based on a target
352: * resource.
353: *
354: * @param response
355: * The response to update.
356: * @param target
357: * The target resource.
358: */
359: private void updateAllowedMethods(Response response, Resource target) {
360: Set<Method> allowedMethods = response.getAllowedMethods();
361: for (java.lang.reflect.Method classMethod : target.getClass()
362: .getMethods()) {
363: if (classMethod.getName().startsWith("allow")
364: && (classMethod.getParameterTypes().length == 0)) {
365: if ((Boolean) invoke(target, classMethod)) {
366: Method allowedMethod = Method.valueOf(classMethod
367: .getName().substring(5));
368: allowedMethods.add(allowedMethod);
369: }
370: }
371: }
372: }
373:
374: }
|