/** * Copyright 2005-2014 Restlet * * The contents of this file are subject to the terms of one of the following * open source licenses: Apache 2.0 or 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 Apache 2.0 license at * http://www.opensource.org/licenses/apache-2.0 * * You can obtain a copy of the EPL 1.0 license at * http://www.opensource.org/licenses/eclipse-1.0 * * 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://restlet.com/products/restlet-framework * * Restlet is a registered trademark of Restlet S.A.S. */ package org.restlet.resource; import java.io.IOException; 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.Metadata; import org.restlet.data.Method; import org.restlet.data.Parameter; 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 Resource#getRequest()} and * {@link Resource#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 Resource { // [ifndef gwt] method /** * Creates a client resource that proxy calls to the given Java interface * into Restlet method calls. It basically creates a new instance of * {@link ClientResource} and invokes the {@link #wrap(Class)} method. * * @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); } // [ifndef gwt] method /** * Creates a client resource that proxy calls to the given Java interface * into Restlet method calls. It basically creates a new instance of * {@link ClientResource} and invokes the {@link #wrap(Class)} method. * * @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); } // [ifndef gwt] method /** * Creates a client resource that proxy calls to the given Java interface * into Restlet method calls. It basically creates a new instance of * {@link ClientResource} and invokes the {@link #wrap(Class)} method. * * @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); } // [ifndef gwt] member /** 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; // [ifndef gwt] member /** * Indicates if transient or unknown size request entities should be * buffered before being sent. */ private volatile boolean requestEntityBuffering; // [ifndef gwt] member /** * 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.maxRedirects = resource.getMaxRedirects(); this.retryOnError = resource.isRetryOnError(); this.retryDelay = resource.getRetryDelay(); this.retryAttempts = resource.getRetryAttempts(); // [ifndef gwt] this.followingRedirects = resource.isFollowingRedirects(); this.requestEntityBuffering = resource.isRequestEntityBuffering(); this.responseEntityBuffering = resource.isResponseEntityBuffering(); setApplication(resource.getApplication()); // [enddef] init(resource.getContext(), request, response); } // [ifndef gwt] method /** * Constructor. * * @param context * The context. * @param uri * The target URI. */ public ClientResource(Context context, java.net.URI uri) { this(context, Method.GET, uri); } // [ifndef gwt] method /** * 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. */ public ClientResource(Context context, Request request) { this(context, request, null); } /** * 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.maxRedirects = 10; this.retryOnError = true; this.retryDelay = 2000L; this.retryAttempts = 2; // [ifndef gwt] this.followingRedirects = true; this.requestEntityBuffering = false; this.responseEntityBuffering = false; // [enddef] 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); } // [ifndef gwt] method /** * Constructor. * * @param uri * The target URI. */ public ClientResource(java.net.URI uri) { this(Context.getCurrent(), null, uri); } // [ifndef gwt] method /** * 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. */ public ClientResource(Request request) { this(request, new Response(request)); } /** * 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(), Method.GET, uri); } /** * Updates the client preferences to accept the given metadata (media types, * character sets, etc.) with a 1.0 quality in addition to existing ones. * * @param metadata * The metadata to accept. * @see ClientInfo#accept(Metadata...) */ public void accept(Metadata... metadata) { getClientInfo().accept(metadata); } /** * Updates the client preferences to accept the given metadata (media types, * character sets, etc.) with a given quality in addition to existing ones. * * @param metadata * The metadata to accept. * @param quality * The quality to set. * @see ClientInfo#accept(Metadata, float) */ public void accept(Metadata metadata, float quality) { getClientInfo().accept(metadata, quality); } /** * Adds a parameter to the query component. The name and value are * automatically encoded if necessary. * * @param parameter * The parameter to add. * @return The updated reference. * @see Reference#addQueryParameter(Parameter) */ public Reference addQueryParameter(Parameter parameter) { return getReference().addQueryParameter(parameter); } /** * Adds a parameter to the query component. The name and value are * automatically encoded if necessary. * * @param name * The parameter name. * @param value * The optional parameter value. * @return The updated reference. * @see Reference#addQueryParameter(String, String) */ public Reference addQueryParameter(String name, String value) { return getReference().addQueryParameter(name, value); } /** * Adds several parameters to the query component. The name and value are * automatically encoded if necessary. * * @param parameters * The parameters to add. * @return The updated reference. * @see Reference#addQueryParameters(Iterable) */ public Reference addQueryParameters(Iterable<Parameter> parameters) { return getReference().addQueryParameters(parameters); } /** * Adds a segment at the end of the path. If the current path doesn't end * with a slash character, one is inserted before the new segment value. The * value is automatically encoded if necessary. * * @param value * The segment value to add. * @return The updated reference. * @see Reference#addSegment(String) */ public Reference addSegment(String value) { return getReference().addSegment(value); } /** * 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; // [ifndef gwt] // Prefer the outbound root result = getApplication().getOutboundRoot(); // [enddef] 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 // [ifdef gwt] uncomment // if (getReference().isRelative()) { // getReference().setBaseRef( // com.google.gwt.core.client.GWT.getHostPageBaseURL()); // setReference(getReference().getTargetRef()); // } // [enddef] Protocol rProtocol = getProtocol(); Reference rReference = getReference(); Protocol protocol = (rProtocol != null) ? rProtocol : (rReference != null) ? rReference.getSchemeProtocol() : null; if (protocol != null) { // [ifndef gwt] org.restlet.engine.util.TemplateDispatcher dispatcher = new org.restlet.engine.util.TemplateDispatcher(); dispatcher.setContext(getContext()); dispatcher.setNext(new Client(protocol)); result = dispatcher; // [enddef] // [ifdef gwt] uncomment // result = new Client(protocol); // [enddef] } } return result; } /** * Creates a new request by cloning the one wrapped by this class. * * @return The new response. * @see #getRequest() */ public Request createRequest() { return new Request(getRequest()); } /** * 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); } // [ifndef gwt] method /** * 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. This can be overridden to * provide a different behavior. */ @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); } // [ifndef gwt] method /** * 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 attribute value by looking up the given name in the response * attributes maps. The toString() method is then invoked on the attribute * value. * * @param name * The attribute name. * @return The response attribute value. */ public String getAttribute(String name) { Object value = getResponseAttributes().get(name); return (value == null) ? null : value.toString(); } /** * 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; } // [ifndef gwt] method /** * 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)); } // [ifndef gwt] method /** * 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; } // [ifndef gwt] method /** * 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 = handleOutbound(createRequest()); 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); } // [ifndef gwt] method /** * 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); } // [ifndef gwt] method /** * 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 { org.restlet.service.ConverterService cs = getConverterService(); ClientInfo clientInfo = getClientInfo(); if (clientInfo.getAcceptedMediaTypes().isEmpty()) { cs.updatePreferences(clientInfo.getAcceptedMediaTypes(), resultClass); } // Prepare the request by cloning the prototype request Request request = createRequest(); request.setMethod(method); request.setClientInfo(clientInfo); if (entity != null) { List<? extends Variant> entityVariants; try { entityVariants = cs.getVariants(entity.getClass(), null); request.setEntity(toRepresentation( entity, getConnegService().getPreferredVariant(entityVariants, request, getMetadataService()))); } catch (IOException e) { throw new ResourceException(e); } } else { request.setEntity(null); } // Actually handle the call Response response = handleOutbound(request); Representation responseEntity = handleInbound(response); return toObject(responseEntity, resultClass); } /** * 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) { // Prepare the request by cloning the prototype request Request request = createRequest(); request.setMethod(method); request.setEntity(entity); request.setClientInfo(clientInfo); // Actually handle the call Response response = handleOutbound(request); return handleInbound(response); } /** * 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)); } /** * 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) { // [ifndef gwt] // Check if request entity buffering must be done if (isRequestEntityBuffering()) { request.bufferEntity(); } // [enddef] // Actually handle the call next.handle(request, response); if (isRetryOnError() && response.getStatus().isRecoverableError() && request.getMethod().isIdempotent() && (retryAttempt < getRetryAttempts()) && ((request.getEntity() == null) || request.getEntity() .isAvailable())) { retry(request, response, references, retryAttempt, next); } // [ifndef gwt] else 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); } } // Check if response entity buffering must be done if (isResponseEntityBuffering()) { response.bufferEntity(); } // [enddef] } else { getLogger().log(Level.WARNING, "Request ignored as no next Restlet is available"); } } /** * Handles the inbound call. Note that only synchronous calls are processed. * * @param response * @return The response's entity, if any. */ public Representation handleInbound(Response response) { Representation result = null; // Verify that the request was synchronous if (response.getRequest().isSynchronous()) { if (response.getStatus().isError()) { doError(response.getStatus()); } else { result = (response == null) ? null : response.getEntity(); } } return result; } /** * Handles the outbound call by invoking the next handler. * * @param request * The request to handle. * @return The response created. * @see #getNext() */ public Response handleOutbound(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; } /** * 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); } // [ifndef gwt] method /** * Indicates if redirections are followed. * * @return True if redirections are followed. */ public boolean isFollowingRedirects() { return followingRedirects; } // [ifndef gwt] method /** * 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; } // [ifndef gwt] method /** * 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); } // [ifndef gwt] method /** * 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); } /** * Patches a resource with the given object as delta state. Automatically * serializes the object using the * {@link org.restlet.service.ConverterService}. * * @param entity * The object entity containing the patch. * @return The optional result entity. * @throws ResourceException * @see <a href="https://tools.ietf.org/html/rfc5789">HTTP PATCH method</a> */ public Representation patch(Object entity) throws ResourceException { try { return patch(toRepresentation(entity)); } catch (IOException e) { throw new ResourceException(e); } } // [ifndef gwt] method /** * Patches a resource with the given object as delta state. Automatically * serializes the object using the * {@link org.restlet.service.ConverterService}. * * @param entity * The object entity containing the patch. * @param resultClass * The class of the response entity. * @return The response object entity. * @throws ResourceException * @see <a href="https://tools.ietf.org/html/rfc5789">HTTP PATCH method</a> */ public <T> T patch(Object entity, Class<T> resultClass) throws ResourceException { return handle(Method.PATCH, entity, resultClass); } /** * Patches a resource with the given object as delta state. Automatically * serializes the object using the * {@link org.restlet.service.ConverterService}. * * @param entity * The object entity containing the patch. * @param mediaType * The media type of the representation to retrieve. * @return The response object entity. * @throws ResourceException * @see <a href="https://tools.ietf.org/html/rfc5789">HTTP PATCH method</a> */ public Representation patch(Object entity, MediaType mediaType) throws ResourceException { try { return handle(Method.PATCH, toRepresentation(entity), mediaType); } catch (IOException e) { throw new ResourceException(e); } } /** * Patches a resource with the given representation as delta state. If a * success status is not returned, then a resource exception is thrown. * * @param entity * The request entity containing the patch. * @return The optional result entity. * @throws ResourceException * @see <a href="https://tools.ietf.org/html/rfc5789">HTTP PATCH method</a> */ public Representation patch(Representation entity) throws ResourceException { return handle(Method.PATCH, entity); } /** * 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 { try { return post(toRepresentation(entity)); } catch (IOException e) { throw new ResourceException(e); } } // [ifndef gwt] method /** * 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 { try { return handle(Method.POST, toRepresentation(entity), mediaType); } catch (IOException e) { throw new ResourceException(e); } } /** * 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 { try { return put(toRepresentation(entity)); } catch (IOException e) { throw new ResourceException(e); } } // [ifndef gwt] method /** * 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 { try { return handle(Method.PUT, toRepresentation(entity), mediaType); } catch (IOException e) { throw new ResourceException(e); } } /** * 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) { // [ifndef gwt] try { Thread.sleep(getRetryDelay()); } catch (InterruptedException e) { getLogger().log(Level.FINE, "Retry delay sleep was interrupted", e); } // [enddef] // [ifdef gwt] uncomment // com.google.gwt.user.client.Timer timer = new // com.google.gwt.user.client.Timer() { // public void run() {} // }; // timer.schedule((int) getRetryDelay()); // [enddef] } // Retry the call handle(request, response, references, ++retryAttempt, next); } /** * Sets the request attribute value. * * @param name * The attribute name. * @param value * The attribute to set. */ public void setAttribute(String name, Object value) { getRequestAttributes().put(name, value); } /** * 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); } // [ifndef gwt] method /** * 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); } // [ifndef gwt] method /** * 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); } // [ifndef gwt] method /** * Sets the proxy authentication response sent by a client to an origin * server. * * @param challengeResponse * The proxy authentication response sent by a client to an * origin server. * @see Request#setProxyChallengeResponse(ChallengeResponse) */ public void setProxyChallengeResponse(ChallengeResponse challengeResponse) { getRequest().setProxyChallengeResponse(challengeResponse); } // [ifndef gwt] method /** * Sets the proxy 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 setProxyChallengeResponse(ChallengeScheme scheme, final String identifier, String secret) { setProxyChallengeResponse(new ChallengeResponse(scheme, identifier, secret)); } /** * 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); } // [ifndef gwt] method /** * 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; } // [ifndef gwt] method /** * 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; } // [ifndef gwt] method /** * Wraps the client resource to proxy calls to the given Java interface into * Restlet method calls. Use the {@link org.restlet.engine.Engine} * classloader in order to generate the proxy. * * @param <T> * @param resourceInterface * The annotated resource interface class to proxy. * @return The proxy instance. */ public <T> T wrap(Class<? extends T> resourceInterface) { return wrap(resourceInterface, org.restlet.engine.Engine.getInstance() .getClassLoader()); } // [ifndef gwt] method /** * 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. * @param classLoader * The classloader used to instantiate the dynamic proxy. * @return The proxy instance. */ @SuppressWarnings("unchecked") public <T> T wrap(Class<? extends T> resourceInterface, ClassLoader classLoader) { 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(classLoader, new Class<?>[] { ClientProxy.class, resourceInterface }, h); return result; } }