Package org.restlet.resource

Source Code of org.restlet.resource.ClientResource

/**
* 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.util.ArrayList;
import java.util.List;
import java.util.logging.Level;

import org.restlet.Client;
import org.restlet.Context;
import org.restlet.Request;
import org.restlet.Response;
import org.restlet.Restlet;
import org.restlet.Uniform;
import org.restlet.data.ChallengeResponse;
import org.restlet.data.ChallengeScheme;
import org.restlet.data.ClientInfo;
import org.restlet.data.Conditions;
import org.restlet.data.Cookie;
import org.restlet.data.MediaType;
import org.restlet.data.Method;
import org.restlet.data.Protocol;
import org.restlet.data.Range;
import org.restlet.data.Reference;
import org.restlet.data.Status;
import org.restlet.representation.Representation;
import org.restlet.representation.StringRepresentation;
import org.restlet.representation.Variant;
import org.restlet.util.Series;

/**
* Client-side resource. Acts like a proxy of a target resource.<br>
* This class changes the semantics of the {@link UniformResource#getRequest()}
* and {@link UniformResource#getResponse()} methods. Since a clientResource may
* receive severals responses for a single request (in case of interim
* response), the {@link #getResponse()} method returns the last received
* response object. The Request object returned by the {@link #getRequest()} is
* actually a prototype which is cloned (except the representation) just before
* the {@link #handle()} method is called.<br>
* Users must be aware that by most representations can only be read or written
* once. Some others, such as {@link StringRepresentation} stored the entity in
* memory which can be read several times but has the drawback to consume
* memory.<br>
* Concurrency note: instances of the class are not designed to be shared among
* several threads. If thread-safety is necessary, consider using the
* lower-level {@link Client} class instead.
*
* @author Jerome Louvel
*/
public class ClientResource extends UniformResource {

    /**
     * Creates a client resource that proxy calls to the given Java interface
     * into Restlet method calls.
     *
     * @param <T>
     * @param context
     *            The context.
     * @param reference
     *            The target reference.
     * @param resourceInterface
     *            The annotated resource interface class to proxy.
     * @return The proxy instance.
     */
    public static <T> T create(Context context, Reference reference,
            Class<? extends T> resourceInterface) {
        ClientResource clientResource = new ClientResource(context, reference);
        return clientResource.wrap(resourceInterface);
    }

    /**
     * Creates a client resource that proxy calls to the given Java interface
     * into Restlet method calls.
     *
     * @param <T>
     * @param resourceInterface
     *            The annotated resource interface class to proxy.
     * @return The proxy instance.
     */
    public static <T> T create(Reference reference,
            Class<? extends T> resourceInterface) {
        return create(null, reference, resourceInterface);
    }

    /**
     * Creates a client resource that proxy calls to the given Java interface
     * into Restlet method calls.
     *
     * @param <T>
     * @param uri
     *            The target URI.
     * @param resourceInterface
     *            The annotated resource interface class to proxy.
     * @return The proxy instance.
     */
    public static <T> T create(String uri, Class<? extends T> resourceInterface) {
        return create(null, new Reference(uri), resourceInterface);
    }

    /** Indicates if redirections should be automatically followed. */
    private volatile boolean followingRedirects;

    /**
     * Indicates if maximum number of redirections that can be automatically
     * followed for a single call.
     */
    private volatile int maxRedirects;

    /** The next Restlet. */
    private volatile Uniform next;

    /** Indicates if the next Restlet has been created. */
    private volatile boolean nextCreated;

    /**
     * Indicates if transient or unknown size request entities should be
     * buffered before being sent.
     */
    private volatile boolean requestEntityBuffering;

    /**
     * Indicates if transient or unknown size response entities should be
     * buffered after being received.
     */
    private volatile boolean responseEntityBuffering;

    /** Number of retry attempts before reporting an error. */
    private volatile int retryAttempts;

    /** Delay in milliseconds between two retry attempts. */
    private volatile long retryDelay;

    /** Indicates if idempotent requests should be retried on error. */
    private volatile boolean retryOnError;

    /**
     * Empty constructor.
     */
    protected ClientResource() {
    }

    /**
     * Constructor.
     *
     * @param resource
     *            The client resource to copy.
     */
    public ClientResource(ClientResource resource) {
        Request request = new Request(resource.getRequest());
        Response response = new Response(request);
        this.next = resource.getNext();
        this.followingRedirects = resource.isFollowingRedirects();
        this.maxRedirects = resource.getMaxRedirects();
        this.retryOnError = resource.isRetryOnError();
        this.retryDelay = resource.getRetryDelay();
        this.retryAttempts = resource.getRetryAttempts();

        this.requestEntityBuffering = resource.isRequestEntityBuffering();
        this.responseEntityBuffering = resource.isResponseEntityBuffering();
        setApplication(resource.getApplication());
        init(resource.getContext(), request, response);
    }

    /**
     * Constructor.
     *
     * @param context
     *            The context.
     * @param uri
     *            The target URI.
     */
    public ClientResource(Context context, java.net.URI uri) {
        this(context, Method.GET, uri);
    }

    /**
     * Constructor.
     *
     * @param context
     *            The context.
     * @param method
     *            The method to call.
     * @param uri
     *            The target URI.
     */
    public ClientResource(Context context, Method method, java.net.URI uri) {
        this(context, method, new Reference(uri));
    }

    /**
     * Constructor.
     *
     * @param context
     *            The context.
     * @param method
     *            The method to call.
     * @param reference
     *            The target reference.
     */
    public ClientResource(Context context, Method method, Reference reference) {
        this(context, new Request(method, reference), new Response(null));
    }

    /**
     * Constructor.
     *
     * @param context
     *            The context.
     * @param method
     *            The method to call.
     * @param uri
     *            The target URI.
     */
    public ClientResource(Context context, Method method, String uri) {
        this(context, method, new Reference(uri));
    }

    /**
     * Constructor.
     *
     * @param context
     *            The context.
     * @param reference
     *            The target reference.
     */
    public ClientResource(Context context, Reference reference) {
        this(context, Method.GET, reference);
    }

    /**
     * Constructor.
     *
     * @param context
     *            The current context.
     * @param request
     *            The handled request.
     * @param response
     *            The handled response.
     */
    public ClientResource(Context context, Request request, Response response) {
        if (context == null) {
            context = Context.getCurrent();
        }

        // Don't remove this line.
        // See other constructor ClientResource(Context, Method, Reference)
        response.setRequest(request);

        this.followingRedirects = true;
        this.maxRedirects = 10;
        this.retryOnError = true;
        this.retryDelay = 2000L;
        this.retryAttempts = 2;
        this.requestEntityBuffering = false;
        this.responseEntityBuffering = false;
        init(context, request, response);
    }

    /**
     * Constructor.
     *
     * @param context
     *            The context.
     * @param uri
     *            The target URI.
     */
    public ClientResource(Context context, String uri) {
        this(context, Method.GET, uri);
    }

    /**
     * Constructor.
     *
     * @param uri
     *            The target URI.
     */
    public ClientResource(java.net.URI uri) {
        this(Context.getCurrent(), null, uri);
    }

    /**
     * Constructor.
     *
     * @param method
     *            The method to call.
     * @param uri
     *            The target URI.
     */
    public ClientResource(Method method, java.net.URI uri) {
        this(Context.getCurrent(), method, uri);
    }

    /**
     * Constructor.
     *
     * @param method
     *            The method to call.
     * @param reference
     *            The target reference.
     */
    public ClientResource(Method method, Reference reference) {
        this(Context.getCurrent(), method, reference);
    }

    /**
     * Constructor.
     *
     * @param method
     *            The method to call.
     * @param uri
     *            The target URI.
     */
    public ClientResource(Method method, String uri) {
        this(Context.getCurrent(), method, uri);
    }

    /**
     * Constructor.
     *
     * @param reference
     *            The target reference.
     */
    public ClientResource(Reference reference) {
        this(Context.getCurrent(), null, reference);
    }

    /**
     * Constructor.
     *
     * @param request
     *            The handled request.
     * @param response
     *            The handled response.
     */
    public ClientResource(Request request, Response response) {
        this(Context.getCurrent(), request, response);
    }

    /**
     * Constructor.
     *
     * @param uri
     *            The target URI.
     */
    public ClientResource(String uri) {
        this(Context.getCurrent(), null, uri);
    }

    /**
     * Creates a next Restlet is no one is set. By default, it creates a new
     * {@link Client} based on the protocol of the resource's URI reference.
     *
     * @return The created next Restlet or null.
     */
    protected Uniform createNext() {
        Uniform result = null;

        // Prefer the outbound root
        result = getApplication().getOutboundRoot();

        if ((result == null) && (getContext() != null)) {
            // Try using directly the client dispatcher
            result = getContext().getClientDispatcher();
        }

        if (result == null) {
            // As a final option, try creating a client connector

            Protocol rProtocol = getProtocol();
            Reference rReference = getReference();
            Protocol protocol = (rProtocol != null) ? rProtocol
                    : (rReference != null) ? rReference.getSchemeProtocol()
                            : null;

            if (protocol != null) {
                result = new Client(protocol);
            }
        }

        return result;
    }

    /**
     * Creates a new request by cloning the given one.
     *
     * @param prototype
     *            The prototype request.
     * @return The new response.
     */
    public Request createRequest(Request prototype) {
        return new Request(prototype);
    }

    /**
     * Creates a new response for the given request.
     *
     * @param request
     *            The associated request.
     * @return The new response.
     */
    protected Response createResponse(Request request) {
        return new Response(request);
    }

    /**
     * Deletes the target resource and all its representations. If a success
     * status is not returned, then a resource exception is thrown.
     *
     * @return The optional response entity.
     * @see <a
     *      href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.7">HTTP
     *      DELETE method</a>
     */
    public Representation delete() throws ResourceException {
        return handle(Method.DELETE);
    }

    /**
     * Deletes the target resource and all its representations. If a success
     * status is not returned, then a resource exception is thrown.
     *
     * @param <T>
     *            The expected type for the response entity.
     * @param resultClass
     *            The expected class for the response entity object.
     * @return The response entity object.
     * @see <a
     *      href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.7">HTTP
     *      DELETE method</a>
     */
    public <T> T delete(Class<T> resultClass) throws ResourceException {
        return handle(Method.DELETE, resultClass);
    }

    /**
     * Deletes the target resource and all its representations. If a success
     * status is not returned, then a resource exception is thrown.
     *
     * @param mediaType
     *            The media type of the representation to retrieve.
     * @return The representation matching the given media type.
     * @throws ResourceException
     * @see <a
     *      href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.7">HTTP
     *      DELETE method</a>
     */
    public Representation delete(MediaType mediaType) throws ResourceException {
        return handle(Method.DELETE, mediaType);
    }

    /**
     * By default, it throws a new resource exception.
     */
    @Override
    public void doError(Status errorStatus) {
        throw new ResourceException(errorStatus);
    }

    /**
     * Releases the resource by stopping any connector automatically created and
     * associated to the "next" property (see {@link #getNext()} method.
     */
    @Override
    protected void doRelease() throws ResourceException {
        if ((getNext() != null) && this.nextCreated) {
            if (getNext() instanceof Restlet) {
                try {
                    ((Restlet) getNext()).stop();
                } catch (Exception e) {
                    throw new ResourceException(e);
                }
            }

            setNext(null);
        }
    }

    /**
     * Attempts to {@link #release()} the resource.
     */
    @Override
    protected void finalize() throws Throwable {
        release();
    }

    /**
     * Represents the resource using content negotiation to select the best
     * variant based on the client preferences. Note that the client preferences
     * will be automatically adjusted, but only for this request. If you want to
     * change them once for all, you can use the {@link #getClientInfo()}
     * method.<br>
     * <br>
     * If a success status is not returned, then a resource exception is thrown.
     *
     * @return The best representation.
     * @throws ResourceException
     * @see <a
     *      href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.3">HTTP
     *      GET method</a>
     */
    public Representation get() throws ResourceException {
        return handle(Method.GET);
    }

    /**
     * Represents the resource in the given object class. Note that the client
     * preferences will be automatically adjusted, but only for this request. If
     * you want to change them once for all, you can use the
     * {@link #getClientInfo()} method.<br>
     * <br>
     * If a success status is not returned, then a resource exception is thrown.
     *
     * @param <T>
     *            The expected type for the response entity.
     * @param resultClass
     *            The expected class for the response entity object.
     * @return The response entity object.
     * @throws ResourceException
     * @see <a
     *      href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.3">HTTP
     *      GET method</a>
     */
    public <T> T get(Class<T> resultClass) throws ResourceException {
        return handle(Method.GET, resultClass);
    }

    /**
     * Represents the resource using a given media type. Note that the client
     * preferences will be automatically adjusted, but only for this request. If
     * you want to change them once for all, you can use the
     * {@link #getClientInfo()} method.<br>
     * <br>
     * If a success status is not returned, then a resource exception is thrown.
     *
     * @param mediaType
     *            The media type of the representation to retrieve.
     * @return The representation matching the given media type.
     * @throws ResourceException
     * @see <a
     *      href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.3">HTTP
     *      GET method</a>
     */
    public Representation get(MediaType mediaType) throws ResourceException {
        return handle(Method.GET, mediaType);
    }

    /**
     * Returns the child resource defined by its URI relatively to the current
     * resource. The child resource is defined in the sense of hierarchical
     * URIs. If the resource URI is not hierarchical, then an exception is
     * thrown.
     *
     * @param relativeRef
     *            The URI reference of the child resource relatively to the
     *            current resource seen as the parent resource.
     * @return The child resource.
     * @throws ResourceException
     */
    public ClientResource getChild(Reference relativeRef)
            throws ResourceException {
        ClientResource result = null;

        if ((relativeRef != null) && relativeRef.isRelative()) {
            result = new ClientResource(this);
            result.setReference(new Reference(getReference().getTargetRef(),
                    relativeRef).getTargetRef());
        } else {
            doError(Status.CLIENT_ERROR_BAD_REQUEST,
                    "The child URI is not relative.");
        }

        return result;
    }

    /**
     * Wraps the child client resource to proxy calls to the given Java
     * interface into Restlet method calls. The child resource is defined in the
     * sense of hierarchical URIs. If the resource URI is not hierarchical, then
     * an exception is thrown.
     *
     * @param <T>
     * @param relativeRef
     *            The URI reference of the child resource relatively to the
     *            current resource seen as the parent resource.
     * @param resourceInterface
     *            The annotated resource interface class to proxy.
     * @return The proxy instance.
     */
    public <T> T getChild(Reference relativeRef,
            Class<? extends T> resourceInterface) throws ResourceException {
        T result = null;
        ClientResource childResource = getChild(relativeRef);

        if (childResource != null) {
            result = childResource.wrap(resourceInterface);
        }

        return result;
    }

    /**
     * Returns the child resource defined by its URI relatively to the current
     * resource. The child resource is defined in the sense of hierarchical
     * URIs. If the resource URI is not hierarchical, then an exception is
     * thrown.
     *
     * @param relativeUri
     *            The URI of the child resource relatively to the current
     *            resource seen as the parent resource.
     * @return The child resource.
     * @throws ResourceException
     */
    public ClientResource getChild(String relativeUri) throws ResourceException {
        return getChild(new Reference(relativeUri));
    }

    /**
     * Wraps the child client resource to proxy calls to the given Java
     * interface into Restlet method calls. The child resource is defined in the
     * sense of hierarchical URIs. If the resource URI is not hierarchical, then
     * an exception is thrown.
     *
     * @param <T>
     * @param relativeUri
     *            The URI of the child resource relatively to the current
     *            resource seen as the parent resource.
     * @param resourceInterface
     *            The annotated resource interface class to proxy.
     * @return The proxy instance.
     */
    public <T> T getChild(String relativeUri,
            Class<? extends T> resourceInterface) throws ResourceException {
        return getChild(new Reference(relativeUri), resourceInterface);
    }

    /**
     * Returns the maximum number of redirections that can be automatically
     * followed for a single call. Default value is 10.
     *
     * @return The maximum number of redirections that can be automatically
     *         followed for a single call.
     */
    public int getMaxRedirects() {
        return maxRedirects;
    }

    /**
     * Returns the next Restlet. By default, it is the client dispatcher if a
     * context is available.
     *
     * @return The next Restlet or null.
     */
    public Uniform getNext() {
        Uniform result = this.next;

        if (result == null) {
            synchronized (this) {
                if (result == null) {
                    result = createNext();

                    if (result != null) {
                        setNext(result);
                        this.nextCreated = true;
                    }
                }
            }
        }

        return result;
    }

    /**
     * Returns the callback invoked on response reception. If the value is not
     * null, then the associated request will be executed asynchronously.
     *
     * @return The callback invoked on response reception.
     */
    public Uniform getOnResponse() {
        return getRequest().getOnResponse();
    }

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

    /**
     * Returns the parent resource. The parent resource is defined in the sense
     * of hierarchical URIs. If the resource URI is not hierarchical, then an
     * exception is thrown.
     *
     * @return The parent resource.
     */
    public ClientResource getParent() throws ResourceException {
        ClientResource result = null;

        if (getReference().isHierarchical()) {
            result = new ClientResource(this);
            result.setReference(getReference().getParentRef());
        } else {
            doError(Status.CLIENT_ERROR_BAD_REQUEST,
                    "The resource URI is not hierarchical.");
        }

        return result;
    }

    /**
     * Wraps the parent client resource to proxy calls to the given Java
     * interface into Restlet method calls. The parent resource is defined in
     * the sense of hierarchical URIs. If the resource URI is not hierarchical,
     * then an exception is thrown.
     *
     * @param <T>
     * @param resourceInterface
     *            The annotated resource interface class to proxy.
     * @return The proxy instance.
     */
    public <T> T getParent(Class<? extends T> resourceInterface)
            throws ResourceException {
        T result = null;

        ClientResource parentResource = getParent();
        if (parentResource != null) {
            result = parentResource.wrap(resourceInterface);
        }

        return result;
    }

    /**
     * Returns the number of retry attempts before reporting an error. Default
     * value is 2.
     *
     * @return The number of retry attempts before reporting an error.
     */
    public int getRetryAttempts() {
        return retryAttempts;
    }

    /**
     * Returns the delay in milliseconds between two retry attempts. Default
     * value is 2 seconds.
     *
     * @return The delay in milliseconds between two retry attempts.
     */
    public long getRetryDelay() {
        return retryDelay;
    }

    /**
     * Handles the call by invoking the next handler. The prototype request is
     * retrieved via {@link #getRequest()} and cloned and the response is set as
     * the latest with {@link #setResponse(Response)}. If necessary the
     * {@link #setNext(Uniform)} method is called as well with a {@link Client}
     * instance matching the request protocol.
     *
     * @return The optional response entity.
     * @see #getNext()
     */
    @Override
    public Representation handle() {
        Response response = handle(new Request(getRequest()));
        return (response == null) ? null : response.getEntity();
    }

    /**
     * Handles the call by cloning the prototype request, setting the method and
     * entity.
     *
     * @param method
     *            The request method to use.
     * @return The optional response entity.
     */
    protected Representation handle(Method method) {
        return handle(method, (Representation) null);
    }

    /**
     * Handles the call by cloning the prototype request, setting the method and
     * entity.
     *
     * @param <T>
     *            The expected type for the response entity.
     * @param method
     *            The request method to use.
     * @param resultClass
     *            The expected class for the response entity object.
     * @return The response entity object.
     * @throws ResourceException
     */
    protected <T> T handle(Method method, Class<T> resultClass)
            throws ResourceException {
        return handle(method, null, resultClass);
    }

    /**
     * Handles the call by cloning the prototype request, setting the method and
     * entity.
     *
     * @param method
     *            The request method to use.
     * @param mediaType
     *            The preferred result media type.
     * @return The optional response entity.
     */
    protected Representation handle(Method method, MediaType mediaType) {
        return handle(method, (Representation) null, mediaType);
    }

    /**
     * Handles an object entity. Automatically serializes the object using the
     * {@link org.restlet.service.ConverterService}.
     *
     * @param method
     *            The request method to use.
     * @param entity
     *            The object entity to post.
     * @param resultClass
     *            The class of the response entity.
     * @return The response object entity.
     * @throws ResourceException
     */
    protected <T> T handle(Method method, Object entity, Class<T> resultClass)
            throws ResourceException {
        T result = null;
        org.restlet.service.ConverterService cs = getConverterService();
        List<? extends Variant> variants = cs.getVariants(resultClass, null);
        ClientInfo clientInfo = getClientInfo();

        if (clientInfo.getAcceptedMediaTypes().isEmpty()) {
            cs.updatePreferences(clientInfo.getAcceptedMediaTypes(),
                    resultClass);
        }

        result = toObject(
                handle(method,
                        (entity == null) ? null : toRepresentation(entity,
                                clientInfo.getPreferredVariant(variants,
                                        getMetadataService())), clientInfo),
                resultClass);
        return result;
    }

    /**
     * Handles the call by cloning the prototype request, setting the method and
     * entity.
     *
     * @param method
     *            The request method to use.
     * @param entity
     *            The request entity to set.
     * @return The optional response entity.
     */
    protected Representation handle(Method method, Representation entity) {
        return handle(method, entity, getClientInfo());
    }

    /**
     * Handles the call by cloning the prototype request, setting the method and
     * entity.
     *
     * @param method
     *            The request method to use.
     * @param entity
     *            The request entity to set.
     * @param clientInfo
     *            The client preferences.
     * @return The optional response entity.
     */
    protected Representation handle(Method method, Representation entity,
            ClientInfo clientInfo) {
        Representation result = null;

        // Prepare the request by cloning the prototype request
        Request request = createRequest(getRequest());
        request.setMethod(method);
        request.setEntity(entity);
        request.setClientInfo(clientInfo);

        // Actually handle the call
        Response response = handle(request);

        if (response.getStatus().isError()) {
            doError(response.getStatus());
        } else {
            result = (response == null) ? null : response.getEntity();
        }

        return result;
    }

    /**
     * Handles the call by cloning the prototype request, setting the method and
     * entity.
     *
     * @param method
     *            The request method to use.
     * @param entity
     *            The request entity to set.
     * @param mediaType
     *            The preferred result media type.
     * @return The optional response entity.
     */
    protected Representation handle(Method method, Representation entity,
            MediaType mediaType) {
        return handle(method, entity, new ClientInfo(mediaType));
    }

    /**
     * Handles the call by invoking the next handler. Then a new response is
     * created and the {@link #handle(Request, Response, List, int, Uniform)}
     * method is invoked and the response set as the latest response with
     * {@link #setResponse(Response)}.
     *
     * @param request
     *            The request to handle.
     * @return The response created.
     * @see #getNext()
     */
    public Response handle(Request request) {
        Response response = createResponse(request);
        Uniform next = getNext();

        if (next != null) {
            // Effectively handle the call
            handle(request, response, null, 0, next);

            // Update the last received response.
            setResponse(response);
        } else {
            getLogger()
                    .warning(
                            "Unable to process the call for a client resource. No next Restlet has been provided.");
        }

        return response;
    }

    /**
     * Handle the call and follow redirection for safe methods.
     *
     * @param request
     *            The request to send.
     * @param response
     *            The response to update.
     * @param references
     *            The references that caused a redirection to prevent infinite
     *            loops.
     * @param retryAttempt
     *            The number of remaining attempts.
     * @param next
     *            The next handler handling the call.
     */
    protected void handle(Request request, Response response,
            List<Reference> references, int retryAttempt, Uniform next) {
        if (next != null) {
            // Check if request entity buffering must be done
            Representation entity = request.getEntity();

            if (isRequestEntityBuffering()
                    && (entity != null)
                    && (entity.isTransient() || (entity.getSize() == Representation.UNKNOWN_SIZE))
                    && entity.isAvailable()) {
                request.setEntity(new org.restlet.engine.io.BufferingRepresentation(
                        entity));
            }

            // Actually handle the call
            next.handle(request, response);

            // Check for redirections
            if (isFollowingRedirects() && response.getStatus().isRedirection()
                    && (response.getLocationRef() != null)) {
                boolean doRedirection = false;

                if (request.getMethod().isSafe()) {
                    doRedirection = true;
                } else {
                    if (Status.REDIRECTION_SEE_OTHER.equals(response
                            .getStatus())) {
                        // The user agent is redirected using the GET method
                        request.setMethod(Method.GET);
                        request.setEntity(null);
                        doRedirection = true;
                    } else if (Status.REDIRECTION_USE_PROXY.equals(response
                            .getStatus())) {
                        doRedirection = true;
                    }
                }

                if (doRedirection) {
                    redirect(request, response, references, retryAttempt, next);
                } else {
                    getLogger().fine(
                            "Unable to redirect the client call after a response"
                                    + response);
                }
            } else if (isRetryOnError()
                    && response.getStatus().isRecoverableError()
                    && request.getMethod().isIdempotent()
                    && (retryAttempt < getRetryAttempts())
                    && ((request.getEntity() == null) || request.getEntity()
                            .isAvailable())) {
                retry(request, response, references, retryAttempt, next);
            }

            // Check if response entity buffering must be done
            entity = response.getEntity();

            if (isResponseEntityBuffering()
                    && (entity != null)
                    && (entity.isTransient() || (entity.getSize() == Representation.UNKNOWN_SIZE))
                    && entity.isAvailable()) {
                response.setEntity(new org.restlet.engine.io.BufferingRepresentation(
                        entity));
            }
        } else {
            getLogger().log(Level.WARNING,
                    "Request ignored as no next Restlet is available");
        }
    }

    /**
     * Indicates if there is a next Restlet.
     *
     * @return True if there is a next Restlet.
     */
    public boolean hasNext() {
        return getNext() != null;
    }

    /**
     * Represents the resource using content negotiation to select the best
     * variant based on the client preferences. This method is identical to
     * {@link #get()} but doesn't return the actual content of the
     * representation, only its metadata.<br>
     * <br>
     * Note that the client preferences will be automatically adjusted, but only
     * for this request. If you want to change them once for all, you can use
     * the {@link #getClientInfo()} method.<br>
     * <br>
     * If a success status is not returned, then a resource exception is thrown.
     *
     * @return The best representation.
     * @throws ResourceException
     * @see <a
     *      href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.4">HTTP
     *      HEAD method</a>
     */
    public Representation head() throws ResourceException {
        return handle(Method.HEAD);
    }

    /**
     * Represents the resource using a given media type. This method is
     * identical to {@link #get(MediaType)} but doesn't return the actual
     * content of the representation, only its metadata.<br>
     * <br>
     * Note that the client preferences will be automatically adjusted, but only
     * for this request. If you want to change them once for all, you can use
     * the {@link #getClientInfo()} method.<br>
     * <br>
     * If a success status is not returned, then a resource exception is thrown.
     *
     * @param mediaType
     *            The media type of the representation to retrieve.
     * @return The representation matching the given media type.
     * @throws ResourceException
     * @see <a
     *      href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.4">HTTP
     *      HEAD method</a>
     */
    public Representation head(MediaType mediaType) throws ResourceException {
        return handle(Method.HEAD, mediaType);
    }

    /**
     * Indicates if redirections are followed.
     *
     * @return True if redirections are followed.
     */
    public boolean isFollowingRedirects() {
        return followingRedirects;
    }

    /**
     * Indicates if transient or unknown size response entities should be
     * buffered after being received. This is useful to increase the chance of
     * being able to resubmit a failed request due to network error, or to
     * prevent chunked encoding from being used an HTTP connector.
     *
     * @return True if transient response entities should be buffered after
     *         being received.
     */
    public boolean isRequestEntityBuffering() {
        return requestEntityBuffering;
    }

    /**
     * Indicates if transient or unknown size response entities should be
     * buffered after being received. This is useful to be able to
     * systematically reuse and process a response entity several times after
     * retrieval.
     *
     * @return True if transient response entities should be buffered after
     *         being received.
     */
    public boolean isResponseEntityBuffering() {
        return responseEntityBuffering;
    }

    /**
     * Indicates if idempotent requests should be retried on error. Default
     * value is true.
     *
     * @return True if idempotent requests should be retried on error.
     */
    public boolean isRetryOnError() {
        return retryOnError;
    }

    /**
     * Describes the resource using content negotiation to select the best
     * variant based on the client preferences. If a success status is not
     * returned, then a resource exception is thrown.
     *
     * @return The best description.
     * @throws ResourceException
     * @see <a
     *      href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.2">HTTP
     *      OPTIONS method</a>
     */
    public Representation options() throws ResourceException {
        return handle(Method.OPTIONS);
    }

    /**
     * Describes the resource using a given media type. If a success status is
     * not returned, then a resource exception is thrown.
     *
     * @param <T>
     *            The expected type for the response entity.
     * @param resultClass
     *            The expected class for the response entity object.
     * @return The response entity object.
     * @throws ResourceException
     * @see <a
     *      href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.2">HTTP
     *      OPTIONS method</a>
     */
    public <T> T options(Class<T> resultClass) throws ResourceException {
        return handle(Method.OPTIONS, resultClass);
    }

    /**
     * Describes the resource using a given media type. If a success status is
     * not returned, then a resource exception is thrown.
     *
     * @param mediaType
     *            The media type of the representation to retrieve.
     * @return The matched description or null.
     * @throws ResourceException
     * @see <a
     *      href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.2">HTTP
     *      OPTIONS method</a>
     */
    public Representation options(MediaType mediaType) throws ResourceException {
        return handle(Method.OPTIONS, mediaType);
    }

    /**
     * Posts an object entity. Automatically serializes the object using the
     * {@link org.restlet.service.ConverterService}.
     *
     * @param entity
     *            The object entity to post.
     * @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>
     */
    public Representation post(Object entity) throws ResourceException {
        return post(toRepresentation(entity, null));
    }

    /**
     * Posts an object entity. Automatically serializes the object using the
     * {@link org.restlet.service.ConverterService}.
     *
     * @param entity
     *            The object entity to post.
     * @param resultClass
     *            The class of the response entity.
     * @return The response object entity.
     * @throws ResourceException
     * @see <a
     *      href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.5">HTTP
     *      POST method</a>
     */
    public <T> T post(Object entity, Class<T> resultClass)
            throws ResourceException {
        return handle(Method.POST, entity, resultClass);
    }

    /**
     * Posts an object entity. Automatically serializes the object using the
     * {@link org.restlet.service.ConverterService}.
     *
     * @param entity
     *            The object entity to post.
     * @param mediaType
     *            The media type of the representation to retrieve.
     * @return The response object entity.
     * @throws ResourceException
     * @see <a
     *      href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.5">HTTP
     *      POST method</a>
     */
    public Representation post(Object entity, MediaType mediaType)
            throws ResourceException {
        return handle(Method.POST, toRepresentation(entity, null), mediaType);
    }

    /**
     * Posts a representation. If a success status is not returned, then a
     * resource exception is thrown.
     *
     * @param entity
     *            The posted 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>
     */
    public Representation post(Representation entity) throws ResourceException {
        return handle(Method.POST, entity);
    }

    /**
     * Puts an object entity. Automatically serializes the object using the
     * {@link org.restlet.service.ConverterService}.
     *
     * @param entity
     *            The object entity to put.
     * @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>
     */
    public Representation put(Object entity) throws ResourceException {
        return put(toRepresentation(entity, null));
    }

    /**
     * Puts an object entity. Automatically serializes the object using the
     * {@link org.restlet.service.ConverterService}.
     *
     * @param entity
     *            The object entity to put.
     * @param resultClass
     *            The class of the response entity.
     * @return The response object entity.
     * @throws ResourceException
     * @see <a
     *      href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.6">HTTP
     *      PUT method</a>
     */
    public <T> T put(Object entity, Class<T> resultClass)
            throws ResourceException {
        return handle(Method.PUT, entity, resultClass);
    }

    /**
     * Puts an object entity. Automatically serializes the object using the
     * {@link org.restlet.service.ConverterService}.
     *
     * @param entity
     *            The object entity to post.
     * @param mediaType
     *            The media type of the representation to retrieve.
     * @return The response object entity.
     * @throws ResourceException
     * @see <a
     *      href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.6">HTTP
     *      PUT method</a>
     */
    public Representation put(Object entity, MediaType mediaType)
            throws ResourceException {
        return handle(Method.PUT, toRepresentation(entity, null), mediaType);
    }

    /**
     * Creates or updates a resource with the given representation as new state
     * to be stored. If a success status is not returned, then a resource
     * exception is thrown.
     *
     * @param entity
     *            The request entity 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>
     */
    public Representation put(Representation entity) throws ResourceException {
        return handle(Method.PUT, entity);
    }

    /**
     * Effectively redirects a client call. By default, it checks for infinite
     * loops and unavailable entities, the references list is updated and the
     * {@link #handle(Request, Response, List, int, Uniform)} method invoked.
     *
     * @param request
     *            The request to send.
     * @param response
     *            The response to update.
     * @param references
     *            The references that caused a redirection to prevent infinite
     *            loops.
     * @param retryAttempt
     *            The number of remaining attempts.
     * @param next
     *            The next handler handling the call.
     */
    protected void redirect(Request request, Response response,
            List<Reference> references, int retryAttempt, Uniform next) {
        Reference newTargetRef = response.getLocationRef();

        if ((references != null) && references.contains(newTargetRef)) {
            getLogger().warning(
                    "Infinite redirection loop detected with URI: "
                            + newTargetRef);
        } else if (request.getEntity() != null && !request.isEntityAvailable()) {
            getLogger()
                    .warning(
                            "Unable to follow the redirection because the request entity isn't available anymore.");
        } else {
            if (references == null) {
                references = new ArrayList<Reference>();
            }

            if (references.size() >= getMaxRedirects()) {
                getLogger()
                        .warning(
                                "Unable to follow the redirection because the request the maximum number of redirections for a single call has been reached.");
            } else {
                // Add to the list of redirection reference
                // to prevent infinite loops
                references.add(request.getResourceRef());
                request.setResourceRef(newTargetRef);
                handle(request, response, references, 0, next);
            }
        }
    }

    /**
     * Effectively retries a failed client call. By default, it sleeps before
     * the retry attempt and increments the number of retries.
     *
     * @param request
     *            The request to send.
     * @param response
     *            The response to update.
     * @param references
     *            The references that caused a redirection to prevent infinite
     *            loops.
     * @param retryAttempt
     *            The number of remaining attempts.
     * @param next
     *            The next handler handling the call.
     */
    protected void retry(Request request, Response response,
            List<Reference> references, int retryAttempt, Uniform next) {
        getLogger().log(
                Level.INFO,
                "A recoverable error was detected ("
                        + response.getStatus().getCode()
                        + "), attempting again in " + getRetryDelay() + " ms.");

        // Wait before attempting again
        if (getRetryDelay() > 0) {
            try {
                Thread.sleep(getRetryDelay());
            } catch (InterruptedException e) {
                getLogger().log(Level.FINE,
                        "Retry delay sleep was interrupted", e);
            }
        }

        // Retry the call
        handle(request, response, references, ++retryAttempt, next);
    }

    /**
     * Sets the authentication response sent by a client to an origin server.
     *
     * @param challengeResponse
     *            The authentication response sent by a client to an origin
     *            server.
     * @see Request#setChallengeResponse(ChallengeResponse)
     */
    public void setChallengeResponse(ChallengeResponse challengeResponse) {
        getRequest().setChallengeResponse(challengeResponse);
    }

    /**
     * Sets the authentication response sent by a client to an origin server
     * given a scheme, identifier and secret.
     *
     * @param scheme
     *            The challenge scheme.
     * @param identifier
     *            The user identifier, such as a login name or an access key.
     * @param secret
     *            The user secret, such as a password or a secret key.
     */
    public void setChallengeResponse(ChallengeScheme scheme,
            final String identifier, String secret) {
        setChallengeResponse(new ChallengeResponse(scheme, identifier, secret));
    }

    /**
     * Sets the client-specific information.
     *
     * @param clientInfo
     *            The client-specific information.
     * @see Request#setClientInfo(ClientInfo)
     */
    public void setClientInfo(ClientInfo clientInfo) {
        getRequest().setClientInfo(clientInfo);
    }

    /**
     * Sets the conditions applying to this request.
     *
     * @param conditions
     *            The conditions applying to this request.
     * @see Request#setConditions(Conditions)
     */
    public void setConditions(Conditions conditions) {
        getRequest().setConditions(conditions);
    }

    /**
     * Sets the cookies provided by the client.
     *
     * @param cookies
     *            The cookies provided by the client.
     * @see Request#setCookies(Series)
     */
    public void setCookies(Series<Cookie> cookies) {
        getRequest().setCookies(cookies);
    }

    /**
     * Indicates if transient entities should be buffered after being received
     * or before being sent.
     *
     * @param entityBuffering
     *            True if transient entities should be buffered.
     * @see ClientResource#setRequestEntityBuffering(boolean)
     * @see #setResponseEntityBuffering(boolean)
     */
    public void setEntityBuffering(boolean entityBuffering) {
        setRequestEntityBuffering(entityBuffering);
        setResponseEntityBuffering(entityBuffering);
    }

    /**
     * Indicates if redirections are followed.
     *
     * @param followingRedirects
     *            True if redirections are followed.
     */
    public void setFollowingRedirects(boolean followingRedirects) {
        this.followingRedirects = followingRedirects;
    }

    /**
     * Sets the host reference.
     *
     * @param hostRef
     *            The host reference.
     * @see Request#setHostRef(Reference)
     */
    public void setHostRef(Reference hostRef) {
        getRequest().setHostRef(hostRef);
    }

    /**
     * Sets the host reference using an URI string.
     *
     * @param hostUri
     *            The host URI.
     * @see Request#setHostRef(String)
     */
    public void setHostRef(String hostUri) {
        getRequest().setHostRef(hostUri);
    }

    /**
     * Indicates if the call is loggable
     *
     * @param loggable
     *            True if the call is loggable
     */
    public void setLoggable(boolean loggable) {
        getRequest().setLoggable(loggable);
    }

    /**
     * Sets the maximum number of redirections that can be automatically
     * followed for a single call.
     *
     * @param maxRedirects
     *            The maximum number of redirections that can be automatically
     *            followed for a single call.
     */
    public void setMaxRedirects(int maxRedirects) {
        this.maxRedirects = maxRedirects;
    }

    /**
     * Sets the method called.
     *
     * @param method
     *            The method called.
     * @see Request#setMethod(Method)
     */
    public void setMethod(Method method) {
        getRequest().setMethod(method);
    }

    /**
     * Sets the next handler such as a Restlet or a Filter.
     *
     * In addition, this method will set the context of the next Restlet if it
     * is null by passing a reference to its own context.
     *
     * @param next
     *            The next handler.
     */
    public void setNext(org.restlet.Uniform next) {
        if (next instanceof Restlet) {
            Restlet nextRestlet = (Restlet) next;

            if (nextRestlet.getContext() == null) {
                nextRestlet.setContext(getContext());
            }
        }

        this.next = next;

        // If true, it must be updated after calling this method
        this.nextCreated = false;
    }

    /**
     * Sets the callback invoked on response reception. If the value is not
     * null, then the associated request will be executed asynchronously.
     *
     * @param onResponseCallback
     *            The callback invoked on response reception.
     */
    public void setOnResponse(Uniform onResponseCallback) {
        getRequest().setOnResponse(onResponseCallback);
    }

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

    /**
     * Sets the original reference requested by the client.
     *
     * @param originalRef
     *            The original reference.
     * @see Request#setOriginalRef(Reference)
     */
    public void setOriginalRef(Reference originalRef) {
        getRequest().setOriginalRef(originalRef);
    }

    /**
     * Sets the protocol used or to be used.
     *
     * @param protocol
     *            The protocol used or to be used.
     */
    public void setProtocol(Protocol protocol) {
        getRequest().setProtocol(protocol);
    }

    /**
     * Sets the ranges to return from the target resource's representation.
     *
     * @param ranges
     *            The ranges.
     * @see Request#setRanges(List)
     */
    public void setRanges(List<Range> ranges) {
        getRequest().setRanges(ranges);
    }

    /**
     * Sets the resource's reference. If the reference is relative, it will be
     * resolved as an absolute reference. Also, the context's base reference
     * will be reset. Finally, the reference will be normalized to ensure a
     * consistent handling of the call.
     *
     * @param reference
     *            The resource reference.
     * @see Request#setResourceRef(Reference)
     */
    public void setReference(Reference reference) {
        getRequest().setResourceRef(reference);
    }

    /**
     * Sets the resource's reference using an URI string. Note that the URI can
     * be either absolute or relative to the context's base reference.
     *
     * @param uri
     *            The resource URI.
     * @see Request#setResourceRef(String)
     */
    public void setReference(String uri) {
        getRequest().setResourceRef(uri);
    }

    /**
     * Sets the referrer reference if available.
     *
     * @param referrerRef
     *            The referrer reference.
     * @see Request#setReferrerRef(Reference)
     */
    public void setReferrerRef(Reference referrerRef) {
        getRequest().setReferrerRef(referrerRef);
    }

    /**
     * Sets the referrer reference if available using an URI string.
     *
     * @param referrerUri
     *            The referrer URI.
     * @see Request#setReferrerRef(String)
     */
    public void setReferrerRef(String referrerUri) {
        getRequest().setReferrerRef(referrerUri);
    }

    /**
     * Indicates if transient or unknown size response entities should be
     * buffered after being received. This is useful to increase the chance of
     * being able to resubmit a failed request due to network error, or to
     * prevent chunked encoding from being used an HTTP connector.
     *
     * @param requestEntityBuffering
     *            True if transient request entities should be buffered after
     *            being received.
     */
    public void setRequestEntityBuffering(boolean requestEntityBuffering) {
        this.requestEntityBuffering = requestEntityBuffering;
    }

    /**
     * Indicates if transient or unknown size response entities should be
     * buffered after being received. This is useful to be able to
     * systematically reuse and process a response entity several times after
     * retrieval.
     *
     * @param responseEntityBuffering
     *            True if transient response entities should be buffered after
     *            being received.
     */
    public void setResponseEntityBuffering(boolean responseEntityBuffering) {
        this.responseEntityBuffering = responseEntityBuffering;
    }

    /**
     * Sets the number of retry attempts before reporting an error.
     *
     * @param retryAttempts
     *            The number of retry attempts before reporting an error.
     */
    public void setRetryAttempts(int retryAttempts) {
        this.retryAttempts = retryAttempts;
    }

    /**
     * Sets the delay in milliseconds between two retry attempts. The default
     * value is two seconds.
     *
     * @param retryDelay
     *            The delay in milliseconds between two retry attempts.
     */
    public void setRetryDelay(long retryDelay) {
        this.retryDelay = retryDelay;
    }

    /**
     * Indicates if idempotent requests should be retried on error.
     *
     * @param retryOnError
     *            True if idempotent requests should be retried on error.
     */
    public void setRetryOnError(boolean retryOnError) {
        this.retryOnError = retryOnError;
    }

    /**
     * Wraps the client resource to proxy calls to the given Java interface into
     * Restlet method calls.
     *
     * @param <T>
     * @param resourceInterface
     *            The annotated resource interface class to proxy.
     * @return The proxy instance.
     */
    @SuppressWarnings("unchecked")
    public <T> T wrap(Class<? extends T> resourceInterface) {
        T result = null;

        // Create the client resource proxy
        java.lang.reflect.InvocationHandler h = new org.restlet.engine.resource.ClientInvocationHandler<T>(
                this, resourceInterface);

        // Instantiate our dynamic proxy
        result = (T) java.lang.reflect.Proxy.newProxyInstance(
                org.restlet.engine.Engine.getInstance().getClassLoader(),
                new Class<?>[] { ClientProxy.class, resourceInterface }, h);

        return result;
    }
}
TOP

Related Classes of org.restlet.resource.ClientResource

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.