/**
* 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.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.restlet.Context;
import org.restlet.Request;
import org.restlet.Response;
import org.restlet.Restlet;
import org.restlet.data.CacheDirective;
import org.restlet.data.ChallengeRequest;
import org.restlet.data.ChallengeResponse;
import org.restlet.data.ClientInfo;
import org.restlet.data.Conditions;
import org.restlet.data.Cookie;
import org.restlet.data.CookieSetting;
import org.restlet.data.Dimension;
import org.restlet.data.Form;
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.ServerInfo;
import org.restlet.data.Status;
import org.restlet.representation.Representation;
import org.restlet.representation.Variant;
import org.restlet.service.MetadataService;
import org.restlet.util.Series;
/**
* Base resource class exposing the uniform REST interface. Intended conceptual
* target of a hypertext reference. An uniform resource encapsulates a
* {@link Context}, a {@link Request} and a {@link Response}, corresponding to a
* specific target resource.<br>
* <br>
* It also defines a precise life cycle. First, the instance is created and the
* {@link #init(Context, Request, Response)} method is invoked. If you need to
* do some additional initialization, you should just override the
* {@link #doInit()} method.<br>
* <br>
* Then, the abstract {@link #handle()} method can be invoked. For concrete
* behavior, see the {@link ClientResource} and {@link ServerResource}
* subclasses. Note that the state of the resource can be changed several times
* and the {@link #handle()} method called more than once, but always by the
* same thread.<br>
* <br>
* Finally, the final {@link #release()} method can be called to clean-up the
* resource, with a chance for the developer to do some additional clean-up by
* overriding the {@link #doRelease()} method.<br>
* <br>
* Note also that throwable raised such as {@link Error} and {@link Exception}
* can be caught in a single point by overriding the {@link #doCatch(Throwable)}
* method.<br>
* <br>
* "The central feature that distinguishes the REST architectural style from
* other network-based styles is its emphasis on a uniform interface between
* components. By applying the software engineering principle of generality to
* the component interface, the overall system architecture is simplified and
* the visibility of interactions is improved. Implementations are decoupled
* from the services they provide, which encourages independent evolvability."
* Roy T. Fielding<br>
* <br>
* Concurrency note: contrary to the {@link org.restlet.Uniform} class and its
* main {@link Restlet} subclass where a single instance can handle several
* calls concurrently, one instance of {@link Resource} is created for each call
* handled and accessed by only one thread at a time.
*
* @see <a
* href="http://roy.gbiv.com/pubs/dissertation/rest_arch_style.htm#sec_5_1_5">Source
* dissertation</a>
* @author Jerome Louvel
*/
public abstract class Resource {
/**
* Converts the given {@link String} value into a {@link Boolean} or null.
*
* @param value
* The value to convert or null.
* @return The converted {@link Boolean} value or null.
*/
public static Boolean toBoolean(String value) {
return (value != null) ? Boolean.valueOf(value) : null;
}
/**
* Converts the given {@link String} value into a {@link Byte} or null.
*
* @param value
* The value to convert or null.
* @return The converted {@link Byte} value or null.
*/
public static Byte toByte(String value) {
return (value != null) ? Byte.valueOf(value) : null;
}
/**
* Converts the given {@link String} value into an {@link Double} or null.
*
* @param value
* The value to convert or null.
* @return The converted {@link Double} value or null.
*/
public static Double toDouble(String value) {
return (value != null) ? Double.valueOf(value) : null;
}
/**
* Converts the given {@link String} value into a {@link Float} or null.
*
* @param value
* The value to convert or null.
* @return The converted {@link Float} value or null.
*/
public static Float toFloat(String value) {
return (value != null) ? Float.valueOf(value) : null;
}
/**
* Converts the given {@link String} value into an {@link Integer} or null.
*
* @param value
* The value to convert or null.
* @return The converted {@link Integer} value or null.
*/
public static Integer toInteger(String value) {
return (value != null) ? Integer.valueOf(value) : null;
}
/**
* Converts the given {@link String} value into an {@link Long} or null.
*
* @param value
* The value to convert or null.
* @return The converted {@link Long} value or null.
*/
public static Long toLong(String value) {
return (value != null) ? Long.valueOf(value) : null;
}
/**
* Converts the given {@link String} value into a {@link Short} or null.
*
* @param value
* The value to convert or null.
* @return The converted {@link Short} value or null.
*/
public static Short toShort(String value) {
return (value != null) ? Short.valueOf(value) : null;
}
// [ifndef gwt] member
/** The parent application. */
private volatile org.restlet.Application application;
/** The parent context. */
private volatile Context context;
/** The handled request. */
private volatile Request request;
/** The handled response. */
private volatile Response response;
/**
* Invoked when a {@link Throwable} is caught during initialization,
* handling or releasing.
*
* @param throwable
* The caught error or exception.
*/
protected void doCatch(Throwable throwable) {
getLogger().log(Level.INFO, "Exception or error caught in resource",
throwable);
}
/**
* Invoked when an error response status is received.
*
* @param errorStatus
* The error status received.
*/
protected void doError(Status errorStatus) {
}
/**
* Invoked when an error response status is received.
*
* @param errorStatus
* The error status received.
* @param errorMessage
* The custom error message.
*/
protected final void doError(Status errorStatus, String errorMessage) {
doError(new Status(errorStatus, errorMessage));
}
/**
* Set-up method that can be overridden in order to initialize the state of
* the resource. By default it does nothing.
*
* @see #init(Context, Request, Response)
*/
protected void doInit() throws ResourceException {
}
/**
* Clean-up method that can be overridden in order to release the state of
* the resource. By default it does nothing.
*
* @see #release()
*/
protected void doRelease() throws ResourceException {
}
/**
* Returns the set of methods allowed for the current client by the
* resource. The result can vary based on the client's user agent,
* authentication and authorization data provided by the client.
*
* @return The set of allowed methods.
*/
public Set<Method> getAllowedMethods() {
return getResponse() == null ? null : getResponse().getAllowedMethods();
}
// [ifndef gwt] method
/**
* Returns the parent application. If it wasn't set, it attempts to retrieve
* the current one via {@link org.restlet.Application#getCurrent()} if it
* exists, or instantiates a new one as a last resort.
*
* @return The parent application if it exists, or a new one.
*/
public org.restlet.Application getApplication() {
org.restlet.Application result = this.application;
if (result == null) {
result = org.restlet.Application.getCurrent();
if (result == null) {
result = new org.restlet.Application(getContext());
}
this.application = result;
}
return result;
}
/**
* Returns the attribute value by looking up the given name in the request
* or response attributes maps. This is typically used for variables that
* are declared in the URI template used to route the call to this resource.
*
* @param name
* The attribute name.
* @return The matching request or response attribute value.
*/
public abstract String getAttribute(String name);
/**
* Returns the list of authentication requests sent by an origin server to a
* client. If none is available, an empty list is returned.
*
* @return The list of authentication requests.
* @see Response#getChallengeRequests()
*/
public List<ChallengeRequest> getChallengeRequests() {
return getResponse() == null ? null : getResponse()
.getChallengeRequests();
}
/**
* Returns the authentication response sent by a client to an origin server.
*
* @return The authentication response sent by a client to an origin server.
* @see Request#getChallengeResponse()
*/
public ChallengeResponse getChallengeResponse() {
return getRequest() == null ? null : getRequest()
.getChallengeResponse();
}
/**
* Returns the client-specific information. Creates a new instance if no one
* has been set.
*
* @return The client-specific information.
* @see Request#getClientInfo()
*/
public ClientInfo getClientInfo() {
return getRequest() == null ? null : getRequest().getClientInfo();
}
/**
* Returns the modifiable conditions applying to this request. Creates a new
* instance if no one has been set.
*
* @return The conditions applying to this call.
* @see Request#getConditions()
*/
public Conditions getConditions() {
return getRequest() == null ? null : getRequest().getConditions();
}
// [ifndef gwt] method
/**
* Returns the application's content negotiation service or create a new
* one.
*
* @return The content negotiation service.
*/
public org.restlet.service.ConnegService getConnegService() {
org.restlet.service.ConnegService result = null;
// [ifndef gwt] instruction
result = getApplication().getConnegService();
if (result == null) {
result = new org.restlet.service.ConnegService();
}
return result;
}
/**
* Returns the current context.
*
* @return The current context.
*/
public Context getContext() {
return context;
}
// [ifndef gwt] method
/**
* Returns the application's converter service or create a new one.
*
* @return The converter service.
*/
public org.restlet.service.ConverterService getConverterService() {
org.restlet.service.ConverterService result = null;
// [ifndef gwt] instruction
result = getApplication().getConverterService();
if (result == null) {
result = new org.restlet.service.ConverterService();
}
return result;
}
/**
* Returns the modifiable series of cookies provided by the client. Creates
* a new instance if no one has been set.
*
* @return The cookies provided by the client.
* @see Request#getCookies()
*/
public Series<Cookie> getCookies() {
return getRequest() == null ? null : getRequest().getCookies();
}
/**
* Returns the modifiable series of cookie settings provided by the server.
* Creates a new instance if no one has been set.
*
* @return The cookie settings provided by the server.
* @see Response#getCookieSettings()
*/
public Series<CookieSetting> getCookieSettings() {
return getResponse() == null ? null : getResponse().getCookieSettings();
}
/**
* Returns the modifiable set of selecting dimensions on which the response
* entity may vary. If some server-side content negotiation is done, this
* set should be properly updated, other it can be left empty. Creates a new
* instance if no one has been set.
*
* @return The set of dimensions on which the response entity may vary.
* @see Response#getDimensions()
*/
public Set<Dimension> getDimensions() {
return getResponse() == null ? null : getResponse().getDimensions();
}
/**
* Returns the host reference. This may be different from the resourceRef's
* host, for example for URNs and other URIs that don't contain host
* information.
*
* @return The host reference.
* @see Request#getHostRef()
*/
public Reference getHostRef() {
return getRequest() == null ? null : getRequest().getHostRef();
}
/**
* Returns the reference that the client should follow for redirections or
* resource creations.
*
* @return The redirection reference.
* @see Response#getLocationRef()
*/
public Reference getLocationRef() {
return getResponse() == null ? null : getResponse().getLocationRef();
}
/**
* Returns the logger.
*
* @return The logger.
*/
public Logger getLogger() {
return getContext() != null ? getContext().getLogger() : Context
.getCurrentLogger();
}
/**
* Returns the resource reference's optional matrix.
*
* @return The resource reference's optional matrix.
* @see Reference#getMatrixAsForm()
*/
public Form getMatrix() {
return getReference() == null ? null : getReference().getMatrixAsForm();
}
/**
* Returns the first value of the matrix parameter given its name if
* existing, or null.
*
* @param name
* The matrix parameter name.
* @return The first value of the matrix parameter.
*/
public String getMatrixValue(String name) {
String result = null;
Form matrix = getMatrix();
if (matrix != null) {
result = matrix.getFirstValue(name);
}
return result;
}
/**
* Returns the maximum number of intermediaries.
*
* @return The maximum number of intermediaries.
*/
public int getMaxForwards() {
return getRequest() == null ? null : getRequest().getMaxForwards();
}
/**
* Returns the application's metadata service or create a new one.
*
* @return The metadata service.
*/
public MetadataService getMetadataService() {
MetadataService result = null;
// [ifndef gwt] instruction
result = getApplication().getMetadataService();
if (result == null) {
result = new MetadataService();
}
return result;
}
/**
* Returns the method.
*
* @return The method.
* @see Request#getMethod()
*/
public Method getMethod() {
return getRequest() == null ? null : getRequest().getMethod();
}
/**
* Returns the original reference as requested by the client. Note that this
* property is not used during request routing.
*
* @return The original reference.
* @see Request#getOriginalRef()
*/
public Reference getOriginalRef() {
return getRequest() == null ? null : getRequest().getOriginalRef();
}
/**
* Returns the protocol by first returning the resourceRef.schemeProtocol
* property if it is set, or the baseRef.schemeProtocol property otherwise.
*
* @return The protocol or null if not available.
* @see Request#getProtocol()
*/
public Protocol getProtocol() {
return getRequest() == null ? null : getRequest().getProtocol();
}
/**
* Returns the list of proxy authentication requests sent by an origin
* server to a client. If none is available, an empty list is returned.
*
* @return The list of proxy authentication requests.
* @see Response#getProxyChallengeRequests()
*/
public List<ChallengeRequest> getProxyChallengeRequests() {
return getResponse() == null ? null : getResponse()
.getProxyChallengeRequests();
}
// [ifndef gwt] method
/**
* Returns the proxy authentication response sent by a client to an origin
* server.
*
* @return The proxy authentication response sent by a client to an origin
* server.
* @see Request#getProxyChallengeResponse()
*/
public ChallengeResponse getProxyChallengeResponse() {
return getRequest() == null ? null : getRequest()
.getProxyChallengeResponse();
}
/**
* Returns the resource reference's optional query. Note that modifications
* to the returned {@link Form} object aren't reported to the underlying
* reference.
*
* @return The resource reference's optional query.
* @see Reference#getQueryAsForm()
*/
public Form getQuery() {
return getReference() == null ? null : getReference().getQueryAsForm();
}
/**
* Returns the first value of the query parameter given its name if
* existing, or null.
*
* @param name
* The query parameter name.
* @return The first value of the query parameter.
*/
public String getQueryValue(String name) {
String result = null;
Form query = getQuery();
if (query != null) {
result = query.getFirstValue(name);
}
return result;
}
/**
* Returns the ranges to return from the target resource's representation.
*
* @return The ranges to return.
* @see Request#getRanges()
*/
public List<Range> getRanges() {
return getRequest() == null ? null : getRequest().getRanges();
}
/**
* Returns the URI reference.
*
* @return The URI reference.
*/
public Reference getReference() {
return getRequest() == null ? null : getRequest().getResourceRef();
}
/**
* Returns the referrer reference if available.
*
* @return The referrer reference.
*/
public Reference getReferrerRef() {
return getRequest() == null ? null : getRequest().getReferrerRef();
}
/**
* Returns the handled request.
*
* @return The handled request.
*/
public Request getRequest() {
return request;
}
/**
* Returns the request attributes.
*
* @return The request attributes.
* @see Request#getAttributes()
*/
public Map<String, Object> getRequestAttributes() {
return getRequest() == null ? null : getRequest().getAttributes();
}
/**
* Returns the request cache directives. Note that when used with HTTP
* connectors, this property maps to the "Cache-Control" header.
*
* @return The cache directives.
*/
public List<CacheDirective> getRequestCacheDirectives() {
return getRequest() == null ? null : getRequest().getCacheDirectives();
}
/**
* Returns the request entity representation.
*
* @return The request entity representation.
*/
public Representation getRequestEntity() {
return getRequest() == null ? null : getRequest().getEntity();
}
/**
* Returns the handled response.
*
* @return The handled response.
*/
public Response getResponse() {
return response;
}
/**
* Returns the response attributes.
*
* @return The response attributes.
* @see Response#getAttributes()
*/
public Map<String, Object> getResponseAttributes() {
return getResponse() == null ? null : getResponse().getAttributes();
}
/**
* Returns the response cache directives. Note that when used with HTTP
* connectors, this property maps to the "Cache-Control" header.
*
* @return The cache directives.
*/
public List<CacheDirective> getResponseCacheDirectives() {
return getResponse() == null ? null : getResponse()
.getCacheDirectives();
}
/**
* Returns the response entity representation.
*
* @return The response entity representation.
*/
public Representation getResponseEntity() {
return getResponse() == null ? null : getResponse().getEntity();
}
/**
* Returns the application root reference.
*
* @return The application root reference.
* @see Request#getRootRef()
*/
public Reference getRootRef() {
return getRequest() == null ? null : getRequest().getRootRef();
}
/**
* Returns the server-specific information. Creates a new instance if no one
* has been set.
*
* @return The server-specific information.
* @see Response#getServerInfo()
*/
public ServerInfo getServerInfo() {
return getResponse() == null ? null : getResponse().getServerInfo();
}
/**
* Returns the status.
*
* @return The status.
* @see Response#getStatus()
*/
public Status getStatus() {
return getResponse() == null ? null : getResponse().getStatus();
}
// [ifndef gwt] method
/**
* Returns the application's status service or create a new one.
*
* @return The status service.
*/
public org.restlet.service.StatusService getStatusService() {
org.restlet.service.StatusService result = null;
// [ifndef gwt] instruction
result = getApplication().getStatusService();
if (result == null) {
result = new org.restlet.service.StatusService();
}
return result;
}
/**
* Handles the call composed of the current context, request and response.
*
* @return The optional response entity.
*/
public abstract Representation handle();
/**
* Initialization method setting the environment of the current resource
* instance. It the calls the {@link #doInit()} method that can be
* overridden.
*
* @param context
* The current context.
* @param request
* The handled request.
* @param response
* The handled response.
*/
public void init(Context context, Request request, Response response) {
this.context = context;
this.request = request;
this.response = response;
try {
doInit();
} catch (Throwable t) {
doCatch(t);
}
}
/**
* Indicates if the message was or will be exchanged confidentially, for
* example via a SSL-secured connection.
*
* @return True if the message is confidential.
* @see Request#isConfidential()
*/
public boolean isConfidential() {
return getRequest() == null ? null : getRequest().isConfidential();
}
/**
* Indicates if the call is loggable
*
* @return True if the call is loggable
*/
public boolean isLoggable() {
return getRequest() == null ? null : getRequest().isLoggable();
}
/**
* Releases the resource by calling {@link #doRelease()}.
*/
public final void release() {
try {
doRelease();
} catch (Throwable t) {
doCatch(t);
}
}
// [ifndef gwt] method
/**
* Sets the parent application.
*
* @param application
* The parent application.
*/
public void setApplication(org.restlet.Application application) {
this.application = application;
}
/**
* Sets the request or response attribute value.
*
* @param name
* The attribute name.
* @param value
* The attribute to set.
*/
public abstract void setAttribute(String name, Object value);
/**
* Sets the query value for the named parameter. If no query is defined, it
* creates one. If the same parameter exists, it replaces it altogether.
*
* @param name
* The query parameter name.
* @param value
* The query parameter value.
*/
public void setQueryValue(String name, String value) {
Form query = getQuery();
if (query == null) {
query = new Form();
}
query.set(name, value);
try {
getReference().setQuery(query.encode());
} catch (IOException e) {
getLogger().fine("Unable to set the query value");
}
}
/**
* Sets the handled request.
*
* @param request
* The handled request.
*/
public void setRequest(Request request) {
this.request = request;
}
/**
* Sets the handled response.
*
* @param response
* The handled response.
*/
public void setResponse(Response response) {
this.response = response;
}
// [ifndef gwt] method
/**
* Converts a representation into a Java object. Leverages the
* {@link org.restlet.service.ConverterService}.
*
* @param <T>
* The expected class of the Java object.
* @param source
* The source representation to convert.
* @param target
* The target class of the Java object.
* @return The converted Java object.
* @throws ResourceException
*/
public <T> T toObject(Representation source, Class<T> target)
throws ResourceException {
T result = null;
if (source != null) {
try {
org.restlet.service.ConverterService cs = getConverterService();
result = cs.toObject(source, target, this);
} catch (ResourceException e) {
throw e;
} catch (Exception e) {
throw new ResourceException(
Status.CLIENT_ERROR_UNPROCESSABLE_ENTITY, e);
}
}
return result;
}
/**
* Converts an object into a representation based on the default converter
* service variant.
*
* @param source
* The object to convert.
* @return The wrapper representation.
* @throws IOException
*/
public Representation toRepresentation(Object source) throws IOException {
return toRepresentation(source, (Variant) null);
}
/**
* Converts an object into a representation based on a given media type.
*
* @param source
* The object to convert.
* @param target
* The target representation media type.
* @return The wrapper representation.
* @throws IOException
*/
public Representation toRepresentation(Object source, MediaType target)
throws IOException {
return toRepresentation(source, new Variant(target));
}
/**
* Converts an object into a representation based on client preferences.
*
* @param source
* The object to convert.
* @param target
* The target representation variant.
* @return The wrapper representation.
* @throws IOException
*/
public Representation toRepresentation(Object source, Variant target)
throws IOException {
Representation result = null;
if (source != null) {
// [ifndef gwt]
org.restlet.service.ConverterService cs = getConverterService();
result = cs.toRepresentation(source, target, this);
// [enddef]
// [ifdef gwt] uncomment
// if (source instanceof Representation) {
// result = (Representation) source;
// } else {
// getLogger()
// .log(Level.WARNING,
// "The entity has been omitted since the conversion of an instance of "
// + source.getClass().getName()
// + " to an instance of "
// + Representation.class.getName()
// + " is not supported."
// + " Either provide a regular representation"
// + " or use an annotated interface"
// + " or use the json or xml extensions.");
// }
// [enddef]
}
return result;
}
@Override
public String toString() {
return (getRequest() == null ? "" : getRequest().toString())
+ (getResponse() == null ? "" : " => "
+ getResponse().toString());
}
}