/**
* 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;
}
}