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.resource;
020:
021: import java.util.ArrayList;
022: import java.util.List;
023: import java.util.Set;
024: import java.util.logging.Level;
025: import java.util.logging.Logger;
026:
027: import org.restlet.Application;
028: import org.restlet.Context;
029: import org.restlet.data.Dimension;
030: import org.restlet.data.Language;
031: import org.restlet.data.Method;
032: import org.restlet.data.Parameter;
033: import org.restlet.data.Reference;
034: import org.restlet.data.ReferenceList;
035: import org.restlet.data.Request;
036: import org.restlet.data.Response;
037: import org.restlet.data.Status;
038: import org.restlet.util.Series;
039: import org.restlet.util.Template;
040:
041: /**
042: * Intended conceptual target of a hypertext reference. "Any information that
043: * can be named can be a resource: a document or image, a temporal service (e.g.
044: * "today's weather in Los Angeles"), a collection of other resources, a
045: * non-virtual object (e.g. a person), and so on. In other words, any concept
046: * that might be the target of an author's hypertext reference must fit within
047: * the definition of a resource. The only thing that is required to be static
048: * for a resource is the semantics of the mapping, since the semantics is what
049: * distinguishes one resource from another." Roy T. Fielding<br>
050: * <br>
051: * Another definition adapted from the URI standard (RFC 3986): a resource is
052: * the conceptual mapping to a representation (also known as entity) or set of
053: * representations, not necessarily the representation which corresponds to that
054: * mapping at any particular instance in time. Thus, a resource can remain
055: * constant even when its content (the representations to which it currently
056: * corresponds) changes over time, provided that the conceptual mapping is not
057: * changed in the process. In addition, a resource is always identified by a
058: * URI.<br>
059: * <br>
060: * Typically created by Finders, Resource instances are the final handlers of
061: * calls received by server connectors. Unlike the other handlers in the
062: * processing chain, a Resource is generally not shared between calls and
063: * doesn't have to be thread-safe. This is the point where the RESTful view of
064: * your Web application can be integrated with your domain objects. Those domain
065: * objects can be implemented using any technology, relational databases, object
066: * databases, transactional components like EJB, etc. You just have to extend
067: * this class to override the REST methods you want to support like post(),
068: * put() or delete(). The common GET method is supported by the modifiable
069: * "variants" list property and the {@link #getRepresentation(Variant)} method.
070: * This allows an easy and cheap declaration of the available variants in the
071: * constructor for example, then the on-demand creation of costly
072: * representations via the {@link #getRepresentation(Variant)} method.<br>
073: * <br>
074: * At a lower level, you have a handle*(Request,Response) method for each REST
075: * method that is supported by the Resource, where the '*' is replaced by the
076: * method name. The Finder handler for example, will be able to dynamically
077: * dispatch a call to the appropriate handle*() method. Most common REST methods
078: * like GET, POST, PUT and DELETE have default implementations that pre-handle
079: * calls to do content negotiation for example, based on the higher-level
080: * methods that we discussed previously. For example if you want to support a
081: * MOVE method, just add an handleMove(Request,Response) method and it will be
082: * detected automatically by a Finder handler.<br>
083: * <br>
084: * Finally, you need to declare which REST methods are allowed by your Resource
085: * by overiding the matching allow*() method. By default, allowGet() returns
086: * true, but all other allow*() methods will return false. Therefore, if you
087: * want to support the DELETE method, just override allowDelete() and return
088: * true. Again, a previous Finder handler will be able to detect this method and
089: * know whether or not your Resource should be invoked. It is also used by the
090: * handleOptions() method to return the list of allowed methods.
091: *
092: * @see <a
093: * href="http://roy.gbiv.com/pubs/dissertation/rest_arch_style.htm#sec_5_2_1_1">Source
094: * dissertation</a>
095: * @see <a href="http://www.restlet.org/documentation/1.0/tutorial#part12">Tutorial: Reaching
096: * target Resources</a>
097: * @see org.restlet.resource.Representation
098: * @see org.restlet.Finder
099: * @author Jerome Louvel (contact@noelios.com)
100: * @author Thierry Boileau (thboileau@gmail.com)
101: */
102: public class Resource {
103: /** The parent context. */
104: private Context context;
105:
106: /** The logger to use. */
107: private Logger logger;
108:
109: /** Indicates if the best content is automatically negotiated. */
110: private boolean negotiateContent;
111:
112: /** The handled request. */
113: private Request request;
114:
115: /** The returned response. */
116: private Response response;
117:
118: /** The modifiable list of variants. */
119: private List<Variant> variants;
120:
121: /**
122: * Default constructor. Note that the init() method must be invoked right
123: * after the creation of the resource.
124: */
125: public Resource() {
126: }
127:
128: /**
129: * Constructor. This constructor will invoke the init() method by default.
130: *
131: * @param context
132: * The parent context.
133: * @param request
134: * The request to handle.
135: * @param response
136: * The response to return.
137: */
138: public Resource(Context context, Request request, Response response) {
139: init(context, request, response);
140: }
141:
142: /**
143: * Indicates if it is allowed to delete the resource. The default value is
144: * false.
145: *
146: * @return True if the method is allowed.
147: */
148: public boolean allowDelete() {
149: return false;
150: }
151:
152: /**
153: * Indicates if it is allowed to get the variants. The default value is
154: * true.
155: *
156: * @return True if the method is allowed.
157: */
158: public boolean allowGet() {
159: return true;
160: }
161:
162: /**
163: * Indicates if it is allowed to post to the resource. The default value is
164: * false.
165: *
166: * @return True if the method is allowed.
167: */
168: public boolean allowPost() {
169: return false;
170: }
171:
172: /**
173: * Indicates if it is allowed to put to the resource. The default value is
174: * false.
175: *
176: * @return True if the method is allowed.
177: */
178: public boolean allowPut() {
179: return false;
180: }
181:
182: /**
183: * Asks the resource to delete itself and all its representations.
184: */
185: public void delete() {
186: getResponse().setStatus(Status.SERVER_ERROR_INTERNAL);
187: }
188:
189: /**
190: * Generates a reference based on a template URI. Note that you can leverage
191: * all the variables defined in the Template class as they will be resolved
192: * using the resource's request and response properties.
193: *
194: * @param uriTemplate
195: * The URI template to use for generation.
196: * @return The generated reference.
197: */
198: public Reference generateRef(String uriTemplate) {
199: Template tplt = new Template(getLogger(), uriTemplate);
200: return new Reference(tplt.format(getRequest(), getResponse()));
201: }
202:
203: /**
204: * Returns the context.
205: *
206: * @return The context.
207: */
208: public Context getContext() {
209: if (this .context == null)
210: this .context = new Context(getClass().getCanonicalName());
211: return this .context;
212: }
213:
214: /**
215: * Returns the logger to use.
216: *
217: * @return The logger to use.
218: */
219: public Logger getLogger() {
220: if (this .logger == null)
221: this .logger = getContext().getLogger();
222: return this .logger;
223: }
224:
225: /**
226: * Returns the preferred representation according to the client preferences
227: * specified in the associated request.
228: *
229: * @return The preferred representation.
230: */
231: public Representation getPreferredRepresentation() {
232: return getRepresentation(getPreferredVariant());
233: }
234:
235: /**
236: * Returns the preferred variant according to the client preferences
237: * specified in the associated request.
238: *
239: * @return The preferred variant.
240: */
241: public Variant getPreferredVariant() {
242: Variant result = null;
243: List<Variant> variants = getVariants();
244:
245: if ((variants != null) && (!variants.isEmpty())) {
246: Language language = null;
247: // Compute the preferred variant. Get the default language
248: // preference from the Application (if any).
249: Object app = getContext().getAttributes().get(
250: Application.KEY);
251:
252: if (app instanceof Application) {
253: language = ((Application) app).getMetadataService()
254: .getDefaultLanguage();
255: }
256: result = getRequest().getClientInfo().getPreferredVariant(
257: variants, language);
258:
259: }
260:
261: return result;
262: }
263:
264: /**
265: * Returns a full representation for a given variant previously returned via
266: * the getVariants() method. The default implementation directly returns the
267: * variant in case the variants are already full representations. In all
268: * other cases, you will need to override this method in order to provide
269: * your own implementation. <br/><br/>
270: *
271: * This method is very useful for content negotiation when it is too costly
272: * to initilize all the potential representations. It allows a resource to
273: * simply expose the available variants via the getVariants() method and to
274: * actually server the one selected via this method.
275: *
276: * @param variant
277: * The variant whose full representation must be returned.
278: * @return The full representation for the variant.
279: * @see #getVariants()
280: */
281: public Representation getRepresentation(Variant variant) {
282: Representation result = null;
283:
284: if (variant instanceof Representation) {
285: result = (Representation) variant;
286: }
287:
288: return result;
289: }
290:
291: /**
292: * Returns the request.
293: *
294: * @return the request.
295: */
296: public Request getRequest() {
297: return this .request;
298: }
299:
300: /**
301: * Returns the response.
302: *
303: * @return the response.
304: */
305: public Response getResponse() {
306: return this .response;
307: }
308:
309: /**
310: * Returns the modifiable list of variants. A variant can be a purely
311: * descriptive representation, with no actual content that can be served. It
312: * can also be a full representation in case a resource has only one variant
313: * or if the initialization cost is very low.<br>
314: * <br>
315: * Note that the order in which the variants are inserted in the list
316: * matters. For example, if the client has no preference defined, or if the
317: * acceptable variants have the same quality level for the client, the first
318: * acceptable variant in the list will be returned.<br>
319: * <br>
320: * It is recommended to not override this method and to simply use it at
321: * construction time to initialize the list of available variants.
322: * Overriding it will force you to reconstruct the list for each call which
323: * is expensive.
324: *
325: * @return The list of variants.
326: * @see #getRepresentation(Variant)
327: */
328: public List<Variant> getVariants() {
329: if (this .variants == null)
330: this .variants = new ArrayList<Variant>();
331: return this .variants;
332: }
333:
334: /**
335: * Handles a DELETE call invoking the 'delete' method of the target resource
336: * (as provided by the 'findTarget' method).
337: */
338: public void handleDelete() {
339: boolean bContinue = true;
340: if (getRequest().getConditions().hasSome()) {
341: Variant preferredVariant = null;
342:
343: if (isNegotiateContent()) {
344: preferredVariant = getPreferredVariant();
345: } else {
346: List<Variant> variants = getVariants();
347:
348: if (variants.size() == 1) {
349: preferredVariant = variants.get(0);
350: } else {
351: getResponse().setStatus(
352: Status.CLIENT_ERROR_PRECONDITION_FAILED);
353: bContinue = false;
354: }
355: }
356: // The conditions have to be checked even if there is no preferred
357: // variant.
358: if (bContinue) {
359: Status status = getRequest().getConditions().getStatus(
360: getRequest().getMethod(), preferredVariant);
361:
362: if (status != null) {
363: getResponse().setStatus(status);
364: bContinue = false;
365: }
366: }
367: }
368:
369: if (bContinue) {
370: delete();
371: }
372:
373: }
374:
375: /**
376: * Handles a GET call by automatically returning the best entity available
377: * from the target resource (as provided by the 'findTarget' method). The
378: * content negotiation is based on the client's preferences available in the
379: * handled call and can be turned off using the "negotiateContent" property.
380: * If it is disabled and multiple variants are available for the target
381: * resource, then a 300 (Multiple Choices) status will be returned with the
382: * list of variants URI if available.
383: */
384: public void handleGet() {
385: // The variant that may need to meet the request conditions
386: Variant selectedVariant = null;
387:
388: List<Variant> variants = getVariants();
389: if ((variants == null) || (variants.isEmpty())) {
390: // Resource not found
391: getResponse().setStatus(Status.CLIENT_ERROR_NOT_FOUND);
392: } else if (isNegotiateContent()) {
393: Variant preferredVariant = getPreferredVariant();
394:
395: // Set the variant dimensions used for content negotiation
396: getResponse().getDimensions().clear();
397: getResponse().getDimensions().add(Dimension.CHARACTER_SET);
398: getResponse().getDimensions().add(Dimension.ENCODING);
399: getResponse().getDimensions().add(Dimension.LANGUAGE);
400: getResponse().getDimensions().add(Dimension.MEDIA_TYPE);
401:
402: if (preferredVariant == null) {
403: // No variant was found matching the client preferences
404: getResponse().setStatus(
405: Status.CLIENT_ERROR_NOT_ACCEPTABLE);
406:
407: // The list of all variants is transmitted to the client
408: ReferenceList refs = new ReferenceList(variants.size());
409: for (Variant variant : variants) {
410: if (variant.getIdentifier() != null) {
411: refs.add(variant.getIdentifier());
412: }
413: }
414:
415: getResponse().setEntity(refs.getTextRepresentation());
416: } else {
417: getResponse().setEntity(
418: getRepresentation(preferredVariant));
419: selectedVariant = preferredVariant;
420: }
421: selectedVariant = getResponse().getEntity();
422: } else {
423: if (variants.size() == 1) {
424: getResponse().setEntity(
425: getRepresentation(variants.get(0)));
426: selectedVariant = getResponse().getEntity();
427: } else {
428: ReferenceList variantRefs = new ReferenceList();
429:
430: for (Variant variant : variants) {
431: if (variant.getIdentifier() != null) {
432: variantRefs.add(variant.getIdentifier());
433: } else {
434: getLogger()
435: .warning(
436: "A resource with multiple variants should provide and identifier for each variants when content negotiation is turned off");
437: }
438: }
439:
440: if (variantRefs.size() > 0) {
441: // Return the list of variants
442: getResponse().setStatus(
443: Status.REDIRECTION_MULTIPLE_CHOICES);
444: getResponse().setEntity(
445: variantRefs.getTextRepresentation());
446: } else {
447: getResponse().setStatus(
448: Status.CLIENT_ERROR_NOT_FOUND);
449: }
450: }
451: }
452:
453: // The given representation (even if null) must meet the request
454: // conditions
455: // (if any).
456: if (getRequest().getConditions().hasSome()) {
457: Status status = getRequest().getConditions().getStatus(
458: getRequest().getMethod(), selectedVariant);
459: if (status != null) {
460: getResponse().setStatus(status);
461: getResponse().setEntity(null);
462: }
463: }
464: }
465:
466: /**
467: * Handles a HEAD call, using a logic similar to the handleGet method.
468: */
469: public void handleHead() {
470: handleGet();
471: }
472:
473: /**
474: * Handles an OPTIONS call introspecting the target resource (as provided by
475: * the 'findTarget' method).
476: */
477: public void handleOptions() {
478: // HTTP spec says that OPTIONS should return the list of allowed methods
479: updateAllowedMethods();
480: getResponse().setStatus(Status.SUCCESS_OK);
481: }
482:
483: /**
484: * Handles a POST call invoking the 'post' method of the target resource (as
485: * provided by the 'findTarget' method).
486: */
487: public void handlePost() {
488: if (getRequest().isEntityAvailable()) {
489: post(getRequest().getEntity());
490: } else {
491: getResponse().setStatus(
492: new Status(Status.CLIENT_ERROR_BAD_REQUEST,
493: "Missing request entity"));
494: }
495: }
496:
497: /**
498: * Handles a PUT call invoking the 'put' method of the target resource (as
499: * provided by the 'findTarget' method).
500: */
501: @SuppressWarnings("unchecked")
502: public void handlePut() {
503: boolean bContinue = true;
504:
505: if (getRequest().getConditions().hasSome()) {
506: Variant preferredVariant = null;
507:
508: if (isNegotiateContent()) {
509: preferredVariant = getPreferredVariant();
510: } else {
511: List<Variant> variants = getVariants();
512:
513: if (variants.size() == 1) {
514: preferredVariant = variants.get(0);
515: } else {
516: getResponse().setStatus(
517: Status.CLIENT_ERROR_PRECONDITION_FAILED);
518: bContinue = false;
519: }
520: }
521: // The conditions have to be checked even if there is no preferred
522: // variant.
523: if (bContinue) {
524: Status status = getRequest().getConditions().getStatus(
525: getRequest().getMethod(), preferredVariant);
526: if (status != null) {
527: getResponse().setStatus(status);
528: bContinue = false;
529: }
530: }
531: }
532:
533: if (bContinue) {
534: // Check the Content-Range HTTP Header in order to prevent usage of
535: // partial PUTs
536: Object oHeaders = getRequest().getAttributes().get(
537: "org.restlet.http.headers");
538: if (oHeaders != null) {
539: Series<Parameter> headers = (Series<Parameter>) oHeaders;
540: if (headers.getFirst("Content-Range", true) != null) {
541: getResponse()
542: .setStatus(
543: new Status(
544: Status.SERVER_ERROR_NOT_IMPLEMENTED,
545: "the Content-Range header is not understood"));
546: bContinue = false;
547: }
548: }
549: }
550:
551: if (bContinue) {
552: if (getRequest().isEntityAvailable()) {
553: put(getRequest().getEntity());
554:
555: // HTTP spec says that PUT may return the list of allowed
556: // methods
557: updateAllowedMethods();
558: } else {
559: getResponse().setStatus(
560: new Status(Status.CLIENT_ERROR_BAD_REQUEST,
561: "Missing request entity"));
562: }
563: }
564: }
565:
566: /**
567: * Initialize the resource with its context. If you override this method,
568: * make sure that you don't forget to call super.init() first, otherwise
569: * your Resource won't behave properly.
570: *
571: * @param context
572: * The parent context.
573: * @param request
574: * The request to handle.
575: * @param response
576: * The response to return.
577: */
578: public void init(Context context, Request request, Response response) {
579: this .context = context;
580: this .logger = (context != null) ? context.getLogger() : null;
581: this .negotiateContent = true;
582: this .request = request;
583: this .response = response;
584: this .variants = null;
585: }
586:
587: /**
588: * Invokes a method with the given arguments.
589: *
590: * @param method
591: * The method to invoke.
592: * @param args
593: * The arguments to pass.
594: * @return Invocation result.
595: */
596: private Object invoke(java.lang.reflect.Method method,
597: Object... args) {
598: Object result = null;
599:
600: if (method != null) {
601: try {
602: result = method.invoke(this , args);
603: } catch (Exception e) {
604: getLogger().log(
605: Level.WARNING,
606: "Couldn't invoke the handle method for \""
607: + method + "\"", e);
608: }
609: }
610:
611: return result;
612: }
613:
614: /**
615: * Indicates if the best content is automatically negotiated. Default value
616: * is true.
617: *
618: * @return True if the best content is automatically negotiated.
619: */
620: public boolean isNegotiateContent() {
621: return this .negotiateContent;
622: }
623:
624: /**
625: * Posts a representation to the resource.
626: *
627: * @param entity
628: * The posted entity.
629: */
630: public void post(Representation entity) {
631: getResponse().setStatus(Status.SERVER_ERROR_INTERNAL);
632: }
633:
634: /**
635: * Puts a representation in the resource.
636: *
637: * @param entity
638: * A new or updated representation.
639: */
640: public void put(Representation entity) {
641: getResponse().setStatus(Status.SERVER_ERROR_INTERNAL);
642: }
643:
644: /**
645: * Sets the parent context.
646: *
647: * @param context
648: * The parent context.
649: */
650: public void setContext(Context context) {
651: this .context = context;
652: }
653:
654: /**
655: * Indicates if the best content is automatically negotiated. Default value
656: * is true.
657: *
658: * @param negotiateContent
659: * True if the best content is automatically negotiated.
660: */
661: public void setNegotiateContent(boolean negotiateContent) {
662: this .negotiateContent = negotiateContent;
663: }
664:
665: /**
666: * Sets the request to handle.
667: *
668: * @param request
669: * The request to handle.
670: */
671: public void setRequest(Request request) {
672: this .request = request;
673: }
674:
675: /**
676: * Sets the response to update.
677: *
678: * @param response
679: * The response to update.
680: */
681: public void setResponse(Response response) {
682: this .response = response;
683: }
684:
685: /**
686: * Updates the set of allowed methods on the response.
687: */
688: private void updateAllowedMethods() {
689: Set<Method> allowedMethods = getResponse().getAllowedMethods();
690: for (java.lang.reflect.Method classMethod : getClass()
691: .getMethods()) {
692: if (classMethod.getName().startsWith("allow")
693: && (classMethod.getParameterTypes().length == 0)) {
694: if ((Boolean) invoke(classMethod)) {
695: Method allowedMethod = Method.valueOf(classMethod
696: .getName().substring(5));
697: allowedMethods.add(allowedMethod);
698: }
699: }
700: }
701: }
702:
703: }
|