Package org.restlet.resource

Source Code of org.restlet.resource.ServerResource

/**
* Copyright 2005-2011 Noelios Technologies.
*
* The contents of this file are subject to the terms of one of the following
* open source licenses: LGPL 3.0 or LGPL 2.1 or CDDL 1.0 or EPL 1.0 (the
* "Licenses"). You can select the license that you prefer but you may not use
* this file except in compliance with one of these Licenses.
*
* You can obtain a copy of the LGPL 3.0 license at
* http://www.opensource.org/licenses/lgpl-3.0.html
*
* You can obtain a copy of the LGPL 2.1 license at
* http://www.opensource.org/licenses/lgpl-2.1.php
*
* You can obtain a copy of the CDDL 1.0 license at
* http://www.opensource.org/licenses/cddl1.php
*
* You can obtain a copy of the EPL 1.0 license at
* http://www.opensource.org/licenses/eclipse-1.0.php
*
* See the Licenses for the specific language governing permissions and
* limitations under the Licenses.
*
* Alternatively, you can obtain a royalty free commercial license with less
* limitations, transferable or non-transferable, directly at
* http://www.noelios.com/products/restlet-engine
*
* Restlet is a registered trademark of Noelios Technologies.
*/

package org.restlet.resource;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.logging.Level;

import org.restlet.Context;
import org.restlet.Request;
import org.restlet.Response;
import org.restlet.Restlet;
import org.restlet.Uniform;
import org.restlet.data.ChallengeRequest;
import org.restlet.data.ClientInfo;
import org.restlet.data.CookieSetting;
import org.restlet.data.Dimension;
import org.restlet.data.Method;
import org.restlet.data.Reference;
import org.restlet.data.ServerInfo;
import org.restlet.data.Status;
import org.restlet.engine.resource.AnnotationInfo;
import org.restlet.engine.resource.AnnotationUtils;
import org.restlet.engine.resource.VariantInfo;
import org.restlet.representation.Representation;
import org.restlet.representation.RepresentationInfo;
import org.restlet.representation.Variant;
import org.restlet.routing.Filter;
import org.restlet.routing.Router;
import org.restlet.service.MetadataService;
import org.restlet.util.Series;

/**
* Base class for server-side resources. It acts as a wrapper to a given call,
* including the incoming {@link Request} and the outgoing {@link Response}. <br>
* <br>
* It's life cycle is managed by a {@link Finder} created either explicitly or
* more likely implicitly when your {@link ServerResource} subclass is attached
* to a {@link Filter} or a {@link Router} via the {@link Filter#setNext(Class)}
* or {@link Router#attach(String, Class)} methods for example. After
* instantiation using the default constructor, the final
* {@link #init(Context, Request, Response)} method is invoked, setting the
* context, request and response. You can intercept this by overriding the
* {@link #doInit()} method. Then, if the response status is still a success,
* the {@link #handle()} method is invoked to actually handle the call. Finally,
* the final {@link #release()} method is invoked to do the necessary clean-up,
* which you can intercept by overriding the {@link #doRelease()} method. During
* this life cycle, if any exception is caught, then the
* {@link #doCatch(Throwable)} method is invoked.<br>
* <br>
* Note that when an annotated method manually sets the response entity, if this
* entity is available then it will be preserved and the result of the annotated
* method ignored.<br>
* <br>
* In addition, there are two ways to declare representation variants, one is
* based on the {@link #getVariants()} method and another one on the annotated
* methods. Both approaches can't however be used at the same time for now.<br>
* <br>
* Concurrency note: contrary to the {@link org.restlet.Uniform} class and its
* main {@link Restlet} subclass where a single instance can handle several
* calls concurrently, one instance of {@link ServerResource} is created for
* each call handled and accessed by only one thread at a time.
*
* @author Jerome Louvel
*/
public abstract class ServerResource extends UniformResource {
    /** Indicates if annotations are supported. */
    private volatile boolean annotated;

    /** Indicates if conditional handling is enabled. */
    private volatile boolean conditional;

    /** Indicates if the identified resource exists. */
    private volatile boolean existing;

    /** Indicates if content negotiation of response entities is enabled. */
    private volatile boolean negotiated;

    /** Modifiable list of variants. */
    private volatile List<Variant> variants;

    /**
     * Initializer block to ensure that the basic properties are initialized
     * consistently across constructors.
     */
    {
        this.annotated = true;
        this.conditional = true;
        this.existing = true;
        this.negotiated = true;
        this.variants = null;
    }

    /**
     * Default constructor. Note that the
     * {@link #init(Context, Request, Response)}() method will be invoked right
     * after the creation of the resource.
     */
    public ServerResource() {
    }

    /**
     * Ask the connector to abort the related network connection, for example
     * immediately closing the socket.
     */
    public void abort() {
        getResponse().abort();
    }

    /**
     * Asks the response to immediately commit making it ready to be sent back
     * to the client. Note that all server connectors don't necessarily support
     * this feature.
     */
    public void commit() {
        getResponse().commit();
    }

    /**
     * Deletes the resource and all its representations. This method is only
     * invoked if content negotiation has been disabled as indicated by the
     * {@link #isNegotiated()}, otherwise the {@link #delete(Variant)} method is
     * invoked.<br>
     * <br>
     * The default behavior is to set the response status to
     * {@link Status#CLIENT_ERROR_METHOD_NOT_ALLOWED}.
     *
     * @return The optional response entity.
     * @throws ResourceException
     * @see <a
     *      href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.7"
     *      >HTTP DELETE method</a>
     */
    protected Representation delete() throws ResourceException {
        Representation result = null;
        AnnotationInfo annotationInfo = getAnnotation(Method.DELETE);

        if (annotationInfo != null) {
            result = doHandle(annotationInfo, null);
        } else {
            doError(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED);
        }

        return result;
    }

    /**
     * Deletes the resource and all its representations. A variant parameter is
     * passed to indicate which representation should be returned if any.<br>
     * <br>
     * This method is only invoked if content negotiation has been enabled as
     * indicated by the {@link #isNegotiated()}, otherwise the {@link #delete()}
     * method is invoked.<br>
     * <br>
     * The default behavior is to set the response status to
     * {@link Status#CLIENT_ERROR_METHOD_NOT_ALLOWED}.
     *
     * @param variant
     *            The variant of the response entity.
     * @return The optional response entity.
     * @throws ResourceException
     * @see #get(Variant)
     * @see <a
     *      href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.7"
     *      >HTTP DELETE method</a>
     */
    protected Representation delete(Variant variant) throws ResourceException {
        Representation result = null;

        if (variant instanceof VariantInfo) {
            result = doHandle(((VariantInfo) variant).getAnnotationInfo(),
                    variant);
        } else {
            doError(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED);
        }

        return result;
    }

    /**
     * Describes the available variants to help client-side content negotiation.
     * Return null by default.
     *
     * @return The description of available variants.
     */
    protected Representation describeVariants() {
        Representation result = null;

        // The list of all variants is transmitted to the client
        // final ReferenceList refs = new ReferenceList(variants.size());
        // for (final Variant variant : variants) {
        // if (variant.getIdentifier() != null) {
        // refs.add(variant.getIdentifier());
        // }
        // }
        //
        // result = refs.getTextRepresentation();
        return result;
    }

    /**
     * Invoked when an error or an exception is caught during initialization,
     * handling or releasing. By default, updates the responses's status with
     * the result of
     * {@link org.restlet.service.StatusService#getStatus(Throwable, UniformResource)}
     * .
     *
     * @param throwable
     *            The caught error or exception.
     */
    protected void doCatch(Throwable throwable) {
        Level level = Level.INFO;
        Status status = getStatusService().getStatus(throwable, this);

        if (status.isServerError()) {
            level = Level.WARNING;
        } else if (status.isConnectorError()) {
            level = Level.INFO;
        } else if (status.isClientError()) {
            level = Level.FINE;
        }

        getLogger().log(level, "Exception or error caught in server resource",
                throwable);

        if (getResponse() != null) {
            getResponse().setStatus(status);
        }
    }

    /**
     * Handles a call by first verifying the optional request conditions and
     * continue the processing if possible. Note that in order to evaluate those
     * conditions, {@link #getInfo()} or {@link #getInfo(Variant)} methods might
     * be invoked.
     *
     * @return The response entity.
     * @throws ResourceException
     */
    protected Representation doConditionalHandle() throws ResourceException {
        Representation result = null;

        if (getConditions().hasSome()) {
            RepresentationInfo resultInfo = null;

            if (existing) {
                if (isNegotiated()) {
                    resultInfo = doGetInfo(getPreferredVariant(getVariants(Method.GET)));
                } else {
                    resultInfo = doGetInfo();
                }

                if (resultInfo == null) {
                    if ((getStatus() == null)
                            || (getStatus().isSuccess() && !Status.SUCCESS_NO_CONTENT
                                    .equals(getStatus()))) {
                        doError(Status.CLIENT_ERROR_NOT_FOUND);
                    } else {
                        // Keep the current status as the developer might prefer
                        // a special status like 'method not authorized'.
                    }
                } else {
                    Status status = getConditions().getStatus(getMethod(),
                            resultInfo);

                    if (status != null) {
                        if (status.isError()) {
                            doError(status);
                        } else {
                            setStatus(status);
                        }
                    }
                }
            } else {
                Status status = getConditions().getStatus(getMethod(),
                        resultInfo);

                if (status != null) {
                    if (status.isError()) {
                        doError(status);
                    } else {
                        setStatus(status);
                    }
                }
            }

            if ((Method.GET.equals(getMethod()) || Method.HEAD
                    .equals(getMethod()))
                    && resultInfo instanceof Representation) {
                result = (Representation) resultInfo;
            } else if ((getStatus() != null) && getStatus().isSuccess()) {
                // Conditions were passed successfully, continue the normal
                // processing.
                if (isNegotiated()) {
                    // Reset the list of variants, as the method differs.
                    getVariants().clear();
                    result = doNegotiatedHandle();
                } else {
                    result = doHandle();
                }
            }
        } else {
            if (isNegotiated()) {
                result = doNegotiatedHandle();
            } else {
                result = doHandle();
            }
        }

        return result;
    }

    /**
     * By default, it sets the status on the response.
     */
    @Override
    protected void doError(Status errorStatus) {
        setStatus(errorStatus);
    }

    /**
     * Returns a descriptor of the response entity returned by a
     * {@link Method#GET} call.
     *
     * @return The response entity.
     * @throws ResourceException
     */
    private RepresentationInfo doGetInfo() throws ResourceException {
        RepresentationInfo result = null;
        AnnotationInfo annotationInfo = getAnnotation(Method.GET);

        if (annotationInfo != null) {
            result = doHandle(annotationInfo, null);
        } else {
            result = getInfo();
        }

        return result;
    }

    /**
     * Returns a descriptor of the response entity returned by a negotiated
     * {@link Method#GET} call.
     *
     * @param variant
     *            The selected variant descriptor.
     * @return The response entity descriptor.
     * @throws ResourceException
     */
    private RepresentationInfo doGetInfo(Variant variant)
            throws ResourceException {
        RepresentationInfo result = null;

        if (variant != null) {
            if (variant instanceof VariantInfo) {
                result = doHandle(((VariantInfo) variant).getAnnotationInfo(),
                        variant);
            } else if (variant instanceof RepresentationInfo) {
                result = (RepresentationInfo) variant;
            } else {
                result = getInfo(variant);
            }
        } else {
            result = doGetInfo();
        }

        return result;
    }

    /**
     * Effectively handles a call without content negotiation of the response
     * entity. The default behavior is to dispatch the call to one of the
     * {@link #get()}, {@link #post(Representation)},
     * {@link #put(Representation)}, {@link #delete()}, {@link #head()} or
     * {@link #options()} methods.
     *
     * @return The response entity.
     * @throws ResourceException
     */
    protected Representation doHandle() throws ResourceException {
        Representation result = null;
        Method method = getMethod();

        if (method == null) {
            setStatus(Status.CLIENT_ERROR_BAD_REQUEST, "No method specified");
        } else {
            if (method.equals(Method.PUT)) {
                result = put(getRequestEntity());
            } else if (isExisting()) {
                if (method.equals(Method.GET)) {
                    result = get();
                } else if (method.equals(Method.POST)) {
                    result = post(getRequestEntity());
                } else if (method.equals(Method.DELETE)) {
                    result = delete();
                } else if (method.equals(Method.HEAD)) {
                    result = head();
                } else if (method.equals(Method.OPTIONS)) {
                    result = options();
                } else {
                    result = doHandle(method, getRequestEntity());
                }
            } else {
                doError(Status.CLIENT_ERROR_NOT_FOUND);
            }
        }

        return result;
    }

    /**
     * Effectively handles a call with content negotiation of the response
     * entity using an annotated method.
     *
     * @param annotationInfo
     *            The annotation descriptor.
     * @param variant
     *            The response variant expected (can be null).
     * @return The response entity.
     * @throws ResourceException
     */
    private Representation doHandle(AnnotationInfo annotationInfo,
            Variant variant) throws ResourceException {
        Representation result = null;
        Class<?>[] parameterTypes = annotationInfo.getJavaInputTypes();

        // Invoke the annotated method and get the resulting object.
        Object resultObject = null;
        try {
            if (parameterTypes.length > 0) {
                List<Object> parameters = new ArrayList<Object>();
                Object parameter = null;

                for (Class<?> parameterType : parameterTypes) {
                    if (Variant.class.equals(parameterType)) {
                        parameters.add(variant);
                    } else {
                        if (getRequestEntity() != null
                                && getRequestEntity().isAvailable()
                                && getRequestEntity().getSize() != 0) {
                            // Assume there is content to be read.
                            // NB: it does not handle the case where the size is
                            // unknown, but there is no content.
                            parameter = toObject(getRequestEntity(),
                                    parameterType);

                            if (parameter == null) {
                                throw new ResourceException(
                                        Status.CLIENT_ERROR_UNSUPPORTED_MEDIA_TYPE);
                            }
                        } else {
                            parameter = null;
                        }

                        parameters.add(parameter);
                    }
                }

                resultObject = annotationInfo.getJavaMethod().invoke(this,
                        parameters.toArray());
            } else {
                resultObject = annotationInfo.getJavaMethod().invoke(this);
            }
        } catch (IllegalArgumentException e) {
            throw new ResourceException(e);
        } catch (IllegalAccessException e) {
            throw new ResourceException(e);
        } catch (InvocationTargetException e) {
            if (e.getTargetException() instanceof ResourceException) {
                throw (ResourceException) e.getTargetException();
            }

            throw new ResourceException(e.getTargetException());
        }

        if (resultObject != null) {
            result = toRepresentation(resultObject, variant);
        }

        return result;
    }

    /**
     * Handles a call and checks the request's method and entity. If the method
     * is not supported, the response status is set to
     * {@link Status#CLIENT_ERROR_METHOD_NOT_ALLOWED}. If the request's entity
     * is no supported, the response status is set to
     * {@link Status#CLIENT_ERROR_UNSUPPORTED_MEDIA_TYPE}.
     *
     * @param method
     *            The request method.
     * @param entity
     *            The request entity (can be null, or unavailable).
     * @return The response entity.
     * @throws ResourceException
     */
    private Representation doHandle(Method method, Representation entity) {
        Representation result = null;

        if (getAnnotation(method) != null) {
            // We know the method is supported, let's check the entity.
            AnnotationInfo annotationInfo = getAnnotation(method, entity);

            if (annotationInfo != null) {
                result = doHandle(annotationInfo, null);
            } else {
                // The request entity is not supported.
                doError(Status.CLIENT_ERROR_UNSUPPORTED_MEDIA_TYPE);
            }
        } else {
            doError(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED);
        }
        return result;
    }

    /**
     * Effectively handles a call with content negotiation of the response
     * entity. The default behavior is to dispatch the call to one of the
     * {@link #get(Variant)}, {@link #post(Representation,Variant)},
     * {@link #put(Representation,Variant)}, {@link #delete(Variant)},
     * {@link #head(Variant)} or {@link #options(Variant)} methods.
     *
     * @param variant
     *            The response variant expected.
     * @return The response entity.
     * @throws ResourceException
     */
    protected Representation doHandle(Variant variant) throws ResourceException {
        Representation result = null;
        Method method = getMethod();

        if (method == null) {
            setStatus(Status.CLIENT_ERROR_BAD_REQUEST, "No method specified");
        } else {
            if (method.equals(Method.PUT)) {
                result = put(getRequestEntity(), variant);
            } else if (isExisting()) {
                if (method.equals(Method.GET)) {
                    if (variant instanceof Representation) {
                        result = (Representation) variant;
                    } else {
                        result = get(variant);
                    }
                } else if (method.equals(Method.POST)) {
                    result = post(getRequestEntity(), variant);
                } else if (method.equals(Method.DELETE)) {
                    result = delete(variant);
                } else if (method.equals(Method.HEAD)) {
                    if (variant instanceof Representation) {
                        result = (Representation) variant;
                    } else {
                        result = head(variant);
                    }
                } else if (method.equals(Method.OPTIONS)) {
                    if (variant instanceof Representation) {
                        result = (Representation) variant;
                    } else {
                        result = options(variant);
                    }
                } else if (variant instanceof VariantInfo) {
                    result = doHandle(
                            ((VariantInfo) variant).getAnnotationInfo(),
                            variant);
                } else {
                    doError(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED);
                }
            } else {
                doError(Status.CLIENT_ERROR_NOT_FOUND);
            }
        }

        return result;
    }

    /**
     * Effectively handles a call with content negotiation of the response
     * entity. The default behavior is to dispatch the call to call a matching
     * annotated method or one of the {@link #get(Variant)},
     * {@link #post(Representation,Variant)},
     * {@link #put(Representation,Variant)}, {@link #delete(Variant)},
     * {@link #head(Variant)} or {@link #options(Variant)} methods.<br>
     * <br>
     * If no acceptable variant is found, the
     * {@link Status#CLIENT_ERROR_NOT_ACCEPTABLE} status is set.
     *
     * @return The response entity.
     * @throws ResourceException
     */
    protected Representation doNegotiatedHandle() throws ResourceException {
        Representation result = null;

        if ((getVariants() != null) && (!getVariants().isEmpty())) {
            Variant preferredVariant = getClientInfo().getPreferredVariant(
                    getVariants(), getMetadataService());

            if (preferredVariant == null) {
                // No variant was found matching the client preferences
                doError(Status.CLIENT_ERROR_NOT_ACCEPTABLE);
                result = describeVariants();
            } else {
                // Update the variant dimensions used for content negotiation
                updateDimensions();
                result = doHandle(preferredVariant);
            }
        } else {
            // No variant declared for this method.
            result = doHandle();
        }

        return result;
    }

    /**
     * Returns a full representation. This method is only invoked if content
     * negotiation has been disabled as indicated by the {@link #isNegotiated()}
     * , otherwise the {@link #get(Variant)} method is invoked.<br>
     * <br>
     * The default behavior is to set the response status to
     * {@link Status#CLIENT_ERROR_METHOD_NOT_ALLOWED}.
     *
     * @return The resource's representation.
     * @throws ResourceException
     * @see <a
     *      href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.3">HTTP
     *      GET method</a>
     */
    protected Representation get() throws ResourceException {
        Representation result = null;
        AnnotationInfo annotationInfo = getAnnotation(Method.GET);

        if (annotationInfo != null) {
            result = doHandle(annotationInfo, null);
        } else {
            doError(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED);
        }

        return result;
    }

    /**
     * Returns a full representation for a given variant. A variant parameter is
     * passed to indicate which representation should be returned if any.<br>
     * <br>
     * This method is only invoked if content negotiation has been enabled as
     * indicated by the {@link #isNegotiated()}, otherwise the {@link #get()}
     * method is invoked.<br>
     * <br>
     * The default behavior is to set the response status to
     * {@link Status#CLIENT_ERROR_METHOD_NOT_ALLOWED}.<br>
     *
     * @param variant
     *            The variant whose full representation must be returned.
     * @return The resource's representation.
     * @see #get(Variant)
     * @throws ResourceException
     */
    protected Representation get(Variant variant) throws ResourceException {
        Representation result = null;

        if (variant instanceof VariantInfo) {
            result = doHandle(((VariantInfo) variant).getAnnotationInfo(),
                    variant);
        } else {
            doError(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED);
        }

        return result;
    }

    /**
     * Returns the first annotation descriptor matching the given method.
     *
     * @param method
     *            The method to match.
     * @return The annotation descriptor.
     */
    private AnnotationInfo getAnnotation(Method method) {
        return getAnnotation(method, null);
    }

    /**
     * Returns the first annotation descriptor matching the given method.
     *
     * @param method
     *            The method to match.
     * @param entity
     *            The request entity or null.
     * @return The annotation descriptor.
     */
    private AnnotationInfo getAnnotation(Method method, Representation entity) {
        if (isAnnotated()) {
            return AnnotationUtils.getAnnotation(getAnnotations(), method,
                    entity, getMetadataService(), getConverterService());
        }

        return null;
    }

    /**
     * Returns the annotation descriptors.
     *
     * @return The annotation descriptors.
     */
    private List<AnnotationInfo> getAnnotations() {
        return isAnnotated() ? AnnotationUtils.getAnnotations(getClass())
                : null;
    }

    /**
     * Returns information about the resource's representation. Those metadata
     * are important for conditional method processing. The advantage over the
     * complete {@link Representation} class is that it is much lighter to
     * create. This method is only invoked if content negotiation has been
     * disabled as indicated by the {@link #isNegotiated()}, otherwise the
     * {@link #getInfo(Variant)} method is invoked.<br>
     * <br>
     * The default behavior is to invoke the {@link #get()} method.
     *
     * @return Information about the resource's representation.
     * @throws ResourceException
     */
    protected RepresentationInfo getInfo() throws ResourceException {
        return get();
    }

    /**
     * Returns information about the resource's representation. Those metadata
     * are important for conditional method processing. The advantage over the
     * complete {@link Representation} class is that it is much lighter to
     * create. A variant parameter is passed to indicate which representation
     * should be returned if any.<br>
     * <br>
     * This method is only invoked if content negotiation has been enabled as
     * indicated by the {@link #isNegotiated()}, otherwise the
     * {@link #getInfo(Variant)} method is invoked.<br>
     * <br>
     * The default behavior is to invoke the {@link #get(Variant)} method.
     *
     * @param variant
     *            The variant whose representation information must be returned.
     * @return Information about the resource's representation.
     * @throws ResourceException
     */
    protected RepresentationInfo getInfo(Variant variant)
            throws ResourceException {
        return get(variant);
    }

    /**
     * Returns the callback invoked after sending the response.
     *
     * @return The callback invoked after sending the response.
     */
    public Uniform getOnSent() {
        return getResponse().getOnSent();
    }

    /**
     * Returns the preferred variant among a list of available variants. The
     * selection is based on the client preferences using the
     * {@link ClientInfo#getPreferredVariant(List, MetadataService)} method.
     *
     * @param variants
     *            The available variants.
     * @return The preferred variant.
     */
    protected Variant getPreferredVariant(List<Variant> variants) {
        Variant result = null;

        // If variants were found, select the best matching one
        if ((variants != null) && (!variants.isEmpty())) {
            result = getClientInfo().getPreferredVariant(
                    variants,
                    (getApplication() == null) ? null : getApplication()
                            .getMetadataService());
        }

        return result;
    }

    /**
     * Returns a modifiable list of exposed variants for the current request
     * method. You can declare variants manually by updating the result list ,
     * by overriding this method. By default, the variants will be provided
     * based on annotated methods.
     *
     * @return The modifiable list of variants.
     */
    public List<Variant> getVariants() {
        return getVariants(getMethod());
    }

    /**
     * Returns a modifiable list of exposed variants for the given method. You
     * can declare variants manually by updating the result list , by overriding
     * this method. By default, the variants will be provided based on annotated
     * methods.
     *
     * @param method
     *            The method.
     * @return The modifiable list of variants.
     */
    protected List<Variant> getVariants(Method method) {
        List<Variant> result = this.variants;

        if (result == null) {
            result = new ArrayList<Variant>();

            // Add annotation-based variants in priority
            if (isAnnotated() && hasAnnotations()) {
                List<Variant> annoVariants = null;
                method = (Method.HEAD.equals(method)) ? Method.GET : method;

                for (AnnotationInfo annotationInfo : getAnnotations()) {
                    if (annotationInfo.isCompatible(method, getRequestEntity(),
                            getMetadataService(), getConverterService())) {
                        annoVariants = annotationInfo.getResponseVariants(
                                getMetadataService(), getConverterService());

                        if (annoVariants != null) {
                            for (Variant v : annoVariants) {
                                result.add(new VariantInfo(v, annotationInfo));
                            }
                        }
                    }
                }
            }

            this.variants = result;
        }

        return result;
    }

    /**
     * Handles any call to this resource. The default implementation check the
     * {@link #isConditional()} and {@link #isNegotiated()} method to determine
     * which one of the {@link #doConditionalHandle()},
     * {@link #doNegotiatedHandle()} and {@link #doHandle()} methods should be
     * invoked. It also catches any {@link ResourceException} thrown and updates
     * the response status using the
     * {@link #setStatus(Status, Throwable, String)} method.<br>
     * <br>
     * After handling, if the status is set to
     * {@link Status#CLIENT_ERROR_METHOD_NOT_ALLOWED}, then
     * {@link #updateAllowedMethods()} is invoked to give the resource a chance
     * to inform the client about the allowed methods.
     *
     * @return The response entity.
     */
    @Override
    public Representation handle() {
        Representation result = null;

        // If the resource is not available after initialization and if this a
        // retrieval method, then return a "not found" response.
        if (!isExisting() && getMethod().isSafe()) {
            doError(Status.CLIENT_ERROR_NOT_FOUND);
        } else {
            try {
                if (isConditional()) {
                    result = doConditionalHandle();
                } else if (isNegotiated()) {
                    result = doNegotiatedHandle();
                } else {
                    result = doHandle();
                }

                if (!getResponse().isEntityAvailable()) {
                    // If the user manually set the entity, keep it
                    getResponse().setEntity(result);
                }

                if (Status.CLIENT_ERROR_METHOD_NOT_ALLOWED.equals(getStatus())) {
                    updateAllowedMethods();
                } else if (Method.GET.equals(getMethod())
                        && Status.SUCCESS_OK.equals(getStatus())
                        && (getResponseEntity() == null || !getResponseEntity()
                                .isAvailable())) {
                    getLogger()
                            .fine("A response with a 200 (Ok) status should have an entity. Changing the status to 204 (No content).");
                    setStatus(Status.SUCCESS_NO_CONTENT);
                }
            } catch (Throwable t) {
                doCatch(t);
            }
        }

        return result;
    }

    /**
     * Indicates if annotations were defined on this resource.
     *
     * @return True if annotations were defined on this resource.
     */
    private boolean hasAnnotations() {
        return (getAnnotations() != null) && (!getAnnotations().isEmpty());
    }

    /**
     * Returns a representation whose metadata will be returned to the client.
     * This method is only invoked if content negotiation has been disabled as
     * indicated by the {@link #isNegotiated()}, otherwise the
     * {@link #head(Variant)} method is invoked.<br>
     * <br>
     * The default behavior is to set the response status to
     * {@link Status#CLIENT_ERROR_METHOD_NOT_ALLOWED}.
     *
     * @return The resource's representation.
     * @throws ResourceException
     * @see <a
     *      href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.3">HTTP
     *      GET method</a>
     */
    protected Representation head() throws ResourceException {
        return get();
    }

    /**
     * Returns a representation whose metadata will be returned to the client. A
     * variant parameter is passed to indicate which representation should be
     * returned if any.<br>
     * <br>
     * This method is only invoked if content negotiation has been enabled as
     * indicated by the {@link #isNegotiated()}, otherwise the {@link #head()}
     * method is invoked.<br>
     * <br>
     * The default implementation directly returns the variant if it is already
     * an instance of {@link Representation}. In other cases, you need to
     * override this method in order to provide your own implementation. *
     *
     * @param variant
     *            The variant whose full representation must be returned.
     * @return The resource's representation.
     * @see #get(Variant)
     * @throws ResourceException
     */
    protected Representation head(Variant variant) throws ResourceException {
        return get(variant);
    }

    /**
     * Indicates if annotations are supported. The default value is true.
     *
     * @return True if annotations are supported.
     */
    public boolean isAnnotated() {
        return annotated;
    }

    /**
     * Indicates if the response should be automatically committed. When
     * processing a request on the server-side, setting this property to 'false'
     * let you ask to the server connector to wait before sending the response
     * back to the client when the initial calling thread returns. This will let
     * you do further updates to the response and manually calling
     * {@link #commit()} later on, using another thread.
     *
     * @return True if the response should be automatically committed.
     */
    public boolean isAutoCommitting() {
        return getResponse().isAutoCommitting();
    }

    /**
     * Indicates if the response has already been committed.
     *
     * @return True if the response has already been committed.
     */
    public boolean isCommitted() {
        return getResponse().isCommitted();
    }

    /**
     * Indicates if conditional handling is enabled. The default value is true.
     *
     * @return True if conditional handling is enabled.
     */
    public boolean isConditional() {
        return conditional;
    }

    /**
     * Indicates if the identified resource exists. The default value is true.
     *
     * @return True if the identified resource exists.
     */
    public boolean isExisting() {
        return existing;
    }

    /**
     * Indicates if the authenticated client user associated to the current
     * request is in the given role name.
     *
     * @param roleName
     *            The role name to test.
     * @return True if the authenticated subject is in the given role.
     */
    public boolean isInRole(String roleName) {
        return getClientInfo().getRoles().contains(
                getApplication().getRole(roleName));
    }

    /**
     * Indicates if content negotiation of response entities is enabled. The
     * default value is true.
     *
     * @return True if content negotiation of response entities is enabled.
     */
    public boolean isNegotiated() {
        return this.negotiated;
    }

    /**
     * Indicates the communication options available for this resource. This
     * method is only invoked if content negotiation has been disabled as
     * indicated by the {@link #isNegotiated()}, otherwise the
     * {@link #options(Variant)} method is invoked.<br>
     * <br>
     * The default behavior is to set the response status to
     * {@link Status#CLIENT_ERROR_METHOD_NOT_ALLOWED}.
     *
     * @return The optional response entity.
     */
    protected Representation options() throws ResourceException {
        Representation result = null;
        AnnotationInfo annotationInfo = getAnnotation(Method.OPTIONS);

        // Updates the list of allowed methods
        updateAllowedMethods();

        if (annotationInfo != null) {
            result = doHandle(annotationInfo, null);
        } else {
            doError(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED);
        }

        return result;
    }

    /**
     * Indicates the communication options available for this resource. A
     * variant parameter is passed to indicate which representation should be
     * returned if any.<br>
     * <br>
     * This method is only invoked if content negotiation has been enabled as
     * indicated by the {@link #isNegotiated()}, otherwise the
     * {@link #options()} method is invoked.<br>
     * <br>
     * The default behavior is to set the response status to
     * {@link Status#CLIENT_ERROR_METHOD_NOT_ALLOWED}.<br>
     *
     * @param variant
     *            The variant of the response entity.
     * @return The optional response entity.
     * @see #get(Variant)
     */
    protected Representation options(Variant variant) throws ResourceException {
        Representation result = null;

        // Updates the list of allowed methods
        updateAllowedMethods();

        if (variant instanceof VariantInfo) {
            result = doHandle(((VariantInfo) variant).getAnnotationInfo(),
                    variant);
        } else {
            doError(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED);
        }

        return result;
    }

    /**
     * Posts a representation to the resource at the target URI reference. This
     * method is only invoked if content negotiation has been disabled as
     * indicated by the {@link #isNegotiated()}, otherwise the
     * {@link #post(Representation, Variant)} method is invoked.<br>
     * <br>
     * The default behavior is to set the response status to
     * {@link Status#CLIENT_ERROR_METHOD_NOT_ALLOWED}.
     *
     * @param entity
     *            The posted entity.
     * @return The optional response entity.
     * @throws ResourceException
     * @see #get(Variant)
     * @see <a
     *      href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.5">HTTP
     *      POST method</a>
     */
    protected Representation post(Representation entity)
            throws ResourceException {
        return doHandle(Method.POST, entity);
    }

    /**
     * Posts a representation to the resource at the target URI reference. A
     * variant parameter is passed to indicate which representation should be
     * returned if any.<br>
     * <br>
     * This method is only invoked if content negotiation has been enabled as
     * indicated by the {@link #isNegotiated()}, otherwise the
     * {@link #post(Representation)} method is invoked.<br>
     * <br>
     * The default behavior is to set the response status to
     * {@link Status#CLIENT_ERROR_METHOD_NOT_ALLOWED}.<br>
     *
     * @param entity
     *            The posted entity.
     * @param variant
     *            The variant of the response entity.
     * @return The optional result entity.
     * @throws ResourceException
     * @see <a
     *      href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.5"
     *      >HTTP POST method</a>
     */
    protected Representation post(Representation entity, Variant variant)
            throws ResourceException {
        Representation result = null;

        if (variant instanceof VariantInfo) {
            result = doHandle(((VariantInfo) variant).getAnnotationInfo(),
                    variant);
        } else {
            doError(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED);
        }

        return result;
    }

    /**
     * Creates or updates a resource with the given representation as new state
     * to be stored. This method is only invoked if content negotiation has been
     * disabled as indicated by the {@link #isNegotiated()}, otherwise the
     * {@link #put(Representation, Variant)} method is invoked.<br>
     * <br>
     * The default behavior is to set the response status to
     * {@link Status#CLIENT_ERROR_METHOD_NOT_ALLOWED}.
     *
     * @param entity
     *            The representation to store.
     * @return The optional result entity.
     * @throws ResourceException
     * @see <a
     *      href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.6">HTTP
     *      PUT method</a>
     */
    protected Representation put(Representation entity)
            throws ResourceException {
        return doHandle(Method.PUT, entity);
    }

    /**
     * Creates or updates a resource with the given representation as new state
     * to be stored. A variant parameter is passed to indicate which
     * representation should be returned if any.<br>
     * <br>
     * This method is only invoked if content negotiation has been enabled as
     * indicated by the {@link #isNegotiated()}, otherwise the
     * {@link #put(Representation)} method is invoked.<br>
     * <br>
     * The default behavior is to set the response status to
     * {@link Status#CLIENT_ERROR_METHOD_NOT_ALLOWED}.<br>
     *
     * @param representation
     *            The representation to store.
     * @param variant
     *            The variant of the response entity.
     * @return The optional result entity.
     * @throws ResourceException
     * @see #get(Variant)
     * @see <a
     *      href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.6"
     *      >HTTP PUT method</a>
     */
    protected Representation put(Representation representation, Variant variant)
            throws ResourceException {
        Representation result = null;

        if (variant instanceof VariantInfo) {
            result = doHandle(((VariantInfo) variant).getAnnotationInfo(),
                    variant);
        } else {
            doError(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED);
        }

        return result;
    }

    /**
     * Permanently redirects the client to a target URI. The client is expected
     * to reuse the same method for the new request.
     *
     * @param targetRef
     *            The target URI reference.
     */
    public void redirectPermanent(Reference targetRef) {
        if (getResponse() != null) {
            getResponse().redirectPermanent(targetRef);
        }
    }

    /**
     * Permanently redirects the client to a target URI. The client is expected
     * to reuse the same method for the new request.<br>
     * <br>
     * If you pass a relative target URI, it will be resolved with the current
     * base reference of the request's resource reference (see
     * {@link Request#getResourceRef()} and {@link Reference#getBaseRef()}.
     *
     * @param targetUri
     *            The target URI.
     */
    public void redirectPermanent(String targetUri) {
        if (getResponse() != null) {
            getResponse().redirectPermanent(targetUri);
        }
    }

    /**
     * Redirects the client to a different URI that SHOULD be retrieved using a
     * GET method on that resource. This method exists primarily to allow the
     * output of a POST-activated script to redirect the user agent to a
     * selected resource. The new URI is not a substitute reference for the
     * originally requested resource.
     *
     * @param targetRef
     *            The target reference.
     */
    public void redirectSeeOther(Reference targetRef) {
        if (getResponse() != null) {
            getResponse().redirectSeeOther(targetRef);
        }
    }

    /**
     * Redirects the client to a different URI that SHOULD be retrieved using a
     * GET method on that resource. This method exists primarily to allow the
     * output of a POST-activated script to redirect the user agent to a
     * selected resource. The new URI is not a substitute reference for the
     * originally requested resource.<br>
     * <br>
     * If you pass a relative target URI, it will be resolved with the current
     * base reference of the request's resource reference (see
     * {@link Request#getResourceRef()} and {@link Reference#getBaseRef()}.
     *
     * @param targetUri
     *            The target URI.
     */
    public void redirectSeeOther(String targetUri) {
        if (getResponse() != null) {
            getResponse().redirectSeeOther(targetUri);
        }
    }

    /**
     * Temporarily redirects the client to a target URI. The client is expected
     * to reuse the same method for the new request.
     *
     * @param targetRef
     *            The target reference.
     */
    public void redirectTemporary(Reference targetRef) {
        if (getResponse() != null) {
            getResponse().redirectTemporary(targetRef);
        }
    }

    /**
     * Temporarily redirects the client to a target URI. The client is expected
     * to reuse the same method for the new request.<br>
     * <br>
     * If you pass a relative target URI, it will be resolved with the current
     * base reference of the request's resource reference (see
     * {@link Request#getResourceRef()} and {@link Reference#getBaseRef()}.
     *
     * @param targetUri
     *            The target URI.
     */
    public void redirectTemporary(String targetUri) {
        if (getResponse() != null) {
            getResponse().redirectTemporary(targetUri);
        }
    }

    /**
     * Sets the set of methods allowed on the requested resource. The set
     * instance set must be thread-safe (use {@link CopyOnWriteArraySet} for
     * example.
     *
     * @param allowedMethods
     *            The set of methods allowed on the requested resource.
     * @see Response#setAllowedMethods(Set)
     */
    public void setAllowedMethods(Set<Method> allowedMethods) {
        if (getResponse() != null) {
            getResponse().setAllowedMethods(allowedMethods);
        }
    }

    /**
     * Indicates if annotations are supported. The default value is true.
     *
     * @param annotated
     *            Indicates if annotations are supported.
     */
    public void setAnnotated(boolean annotated) {
        this.annotated = annotated;
    }

    /**
     * Indicates if the response should be automatically committed.
     *
     * @param autoCommitting
     *            True if the response should be automatically committed
     */
    public void setAutoCommitting(boolean autoCommitting) {
        getResponse().setAutoCommitting(autoCommitting);
    }

    /**
     * Sets the list of authentication requests sent by an origin server to a
     * client. The list instance set must be thread-safe (use
     * {@link CopyOnWriteArrayList} for example.
     *
     * @param requests
     *            The list of authentication requests sent by an origin server
     *            to a client.
     * @see Response#setChallengeRequests(List)
     */
    public void setChallengeRequests(List<ChallengeRequest> requests) {
        if (getResponse() != null) {
            getResponse().setChallengeRequests(requests);
        }
    }

    /**
     * Indicates if the response has already been committed.
     *
     * @param committed
     *            True if the response has already been committed.
     */
    public void setCommitted(boolean committed) {
        getResponse().setCommitted(committed);
    }

    /**
     * Indicates if conditional handling is enabled. The default value is true.
     *
     * @param conditional
     *            True if conditional handling is enabled.
     */
    public void setConditional(boolean conditional) {
        this.conditional = conditional;
    }

    /**
     * Sets the cookie settings provided by the server.
     *
     * @param cookieSettings
     *            The cookie settings provided by the server.
     * @see Response#setCookieSettings(Series)
     */
    public void setCookieSettings(Series<CookieSetting> cookieSettings) {
        if (getResponse() != null) {
            getResponse().setCookieSettings(cookieSettings);
        }
    }

    /**
     * Sets the set of dimensions on which the response entity may vary. The set
     * instance set must be thread-safe (use {@link CopyOnWriteArraySet} for
     * example.
     *
     * @param dimensions
     *            The set of dimensions on which the response entity may vary.
     * @see Response#setDimensions(Set)
     */
    public void setDimensions(Set<Dimension> dimensions) {
        if (getResponse() != null) {
            getResponse().setDimensions(dimensions);
        }
    }

    /**
     * Indicates if the identified resource exists. The default value is true.
     *
     * @param exists
     *            Indicates if the identified resource exists.
     */
    public void setExisting(boolean exists) {
        this.existing = exists;
    }

    /**
     * Sets the reference that the client should follow for redirections or
     * resource creations.
     *
     * @param locationRef
     *            The reference to set.
     * @see Response#setLocationRef(Reference)
     */
    public void setLocationRef(Reference locationRef) {
        if (getResponse() != null) {
            getResponse().setLocationRef(locationRef);
        }
    }

    /**
     * Sets the reference that the client should follow for redirections or
     * resource creations. If you pass a relative location URI, it will be
     * resolved with the current base reference of the request's resource
     * reference (see {@link Request#getResourceRef()} and
     * {@link Reference#getBaseRef()}.
     *
     * @param locationUri
     *            The URI to set.
     * @see Response#setLocationRef(String)
     */
    public void setLocationRef(String locationUri) {
        if (getResponse() != null) {
            getResponse().setLocationRef(locationUri);
        }
    }

    /**
     * Indicates if content negotiation of response entities is enabled. The
     * default value is true.
     *
     * @param negotiateContent
     *            True if content negotiation of response entities is enabled.
     */
    public void setNegotiated(boolean negotiateContent) {
        this.negotiated = negotiateContent;
    }

    /**
     * Sets the callback invoked after sending the response.
     *
     * @param onSentCallback
     *            The callback invoked after sending the response.
     */
    public void setOnSent(Uniform onSentCallback) {
        getResponse().setOnSent(onSentCallback);
    }

    /**
     * Sets the server-specific information.
     *
     * @param serverInfo
     *            The server-specific information.
     * @see Response#setServerInfo(ServerInfo)
     */
    public void setServerInfo(ServerInfo serverInfo) {
        if (getResponse() != null) {
            getResponse().setServerInfo(serverInfo);
        }
    }

    /**
     * Sets the status.
     *
     * @param status
     *            The status to set.
     * @see Response#setStatus(Status)
     */
    public void setStatus(Status status) {
        if (getResponse() != null) {
            getResponse().setStatus(status);
        }
    }

    /**
     * Sets the status.
     *
     * @param status
     *            The status to set.
     * @param message
     *            The status message.
     * @see Response#setStatus(Status, String)
     */
    public void setStatus(Status status, String message) {
        if (getResponse() != null) {
            getResponse().setStatus(status, message);
        }
    }

    /**
     * Sets the status.
     *
     * @param status
     *            The status to set.
     * @param throwable
     *            The related error or exception.
     * @see Response#setStatus(Status, Throwable)
     */
    public void setStatus(Status status, Throwable throwable) {
        if (getResponse() != null) {
            getResponse().setStatus(status, throwable);
        }
    }

    /**
     * Sets the status.
     *
     * @param status
     *            The status to set.
     * @param throwable
     *            The related error or exception.
     * @param message
     *            The status message.
     * @see Response#setStatus(Status, Throwable, String)
     */
    public void setStatus(Status status, Throwable throwable, String message) {
        if (getResponse() != null) {
            getResponse().setStatus(status, throwable, message);
        }
    }

    /**
     * Invoked when the list of allowed methods needs to be updated. The
     * {@link #getAllowedMethods()} or the {@link #setAllowedMethods(Set)}
     * methods should be used. The default implementation lists the annotated
     * methods.
     */
    public void updateAllowedMethods() {
        getAllowedMethods().clear();
        List<AnnotationInfo> annotations = getAnnotations();

        if (annotations != null) {
            for (AnnotationInfo annotationInfo : annotations) {
                if (!getAllowedMethods().contains(
                        annotationInfo.getRestletMethod())) {
                    getAllowedMethods().add(annotationInfo.getRestletMethod());
                }
            }
        }
    }

    /**
     * Update the dimensions that were used for content negotiation. By default,
     * it adds the {@link Dimension#CHARACTER_SET}, {@link Dimension#ENCODING},
     * {@link Dimension#LANGUAGE}and {@link Dimension#MEDIA_TYPE} constants.
     */
    protected void updateDimensions() {
        getDimensions().add(Dimension.CHARACTER_SET);
        getDimensions().add(Dimension.ENCODING);
        getDimensions().add(Dimension.LANGUAGE);
        getDimensions().add(Dimension.MEDIA_TYPE);
    }

}
TOP

Related Classes of org.restlet.resource.ServerResource

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.