/** * 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.ext.jaxrs; import static org.restlet.ext.jaxrs.internal.util.AlgorithmUtil.addPathVarsToMap; import static org.restlet.ext.jaxrs.internal.util.AlgorithmUtil.getBestMethod; import static org.restlet.ext.jaxrs.internal.util.AlgorithmUtil.getFirstByNoOfLiteralCharsNoOfCapturingGroups; import static org.restlet.ext.jaxrs.internal.util.AlgorithmUtil.removeNotSupportedHttpMethod; import static org.restlet.ext.jaxrs.internal.util.Util.copyResponseHeaders; import static org.restlet.ext.jaxrs.internal.util.Util.getMediaType; import static org.restlet.ext.jaxrs.internal.util.Util.getSupportedCharSet; import static org.restlet.ext.jaxrs.internal.util.Util.sortByConcreteness; import java.lang.annotation.Annotation; import java.lang.reflect.GenericSignatureFormatError; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.ws.rs.Produces; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.GenericEntity; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response.ResponseBuilder; import org.restlet.Context; import org.restlet.Request; import org.restlet.Response; import org.restlet.Restlet; import org.restlet.data.MediaType; import org.restlet.data.Method; import org.restlet.data.Reference; import org.restlet.data.Status; import org.restlet.engine.Engine; import org.restlet.ext.jaxrs.internal.core.CallContext; import org.restlet.ext.jaxrs.internal.core.ThreadLocalizedContext; import org.restlet.ext.jaxrs.internal.exceptions.ConvertRepresentationException; import org.restlet.ext.jaxrs.internal.exceptions.ImplementationException; import org.restlet.ext.jaxrs.internal.exceptions.MethodInvokeException; import org.restlet.ext.jaxrs.internal.exceptions.MissingAnnotationException; import org.restlet.ext.jaxrs.internal.exceptions.RequestHandledException; import org.restlet.ext.jaxrs.internal.provider.BufferedReaderProvider; import org.restlet.ext.jaxrs.internal.provider.ByteArrayProvider; import org.restlet.ext.jaxrs.internal.provider.ConverterProvider; import org.restlet.ext.jaxrs.internal.provider.InputStreamProvider; import org.restlet.ext.jaxrs.internal.provider.ReaderProvider; import org.restlet.ext.jaxrs.internal.provider.SimpleXmlProvider; import org.restlet.ext.jaxrs.internal.provider.SourceProvider; import org.restlet.ext.jaxrs.internal.provider.StreamingOutputProvider; import org.restlet.ext.jaxrs.internal.provider.StringProvider; import org.restlet.ext.jaxrs.internal.provider.WebAppExcMapper; import org.restlet.ext.jaxrs.internal.provider.WwwFormFormProvider; import org.restlet.ext.jaxrs.internal.provider.WwwFormMmapProvider; import org.restlet.ext.jaxrs.internal.todo.NotYetImplementedException; import org.restlet.ext.jaxrs.internal.util.Converter; import org.restlet.ext.jaxrs.internal.util.ExceptionHandler; import org.restlet.ext.jaxrs.internal.util.JaxRsOutputRepresentation; import org.restlet.ext.jaxrs.internal.util.MatchingResult; import org.restlet.ext.jaxrs.internal.util.PathRegExp; import org.restlet.ext.jaxrs.internal.util.RemainingPath; import org.restlet.ext.jaxrs.internal.util.SortedMetadata; import org.restlet.ext.jaxrs.internal.util.Util; import org.restlet.ext.jaxrs.internal.util.WrappedRequestForHttpHeaders; import org.restlet.ext.jaxrs.internal.wrappers.AbstractMethodWrapper; import org.restlet.ext.jaxrs.internal.wrappers.ResourceClass; import org.restlet.ext.jaxrs.internal.wrappers.ResourceClasses; import org.restlet.ext.jaxrs.internal.wrappers.ResourceMethod; import org.restlet.ext.jaxrs.internal.wrappers.ResourceMethodOrLocator; import org.restlet.ext.jaxrs.internal.wrappers.ResourceObject; import org.restlet.ext.jaxrs.internal.wrappers.RootResourceClass; import org.restlet.ext.jaxrs.internal.wrappers.SubResourceLocator; import org.restlet.ext.jaxrs.internal.wrappers.provider.ExtensionBackwardMapping; import org.restlet.ext.jaxrs.internal.wrappers.provider.JaxRsProviders; import org.restlet.ext.jaxrs.internal.wrappers.provider.MessageBodyWriter; import org.restlet.ext.jaxrs.internal.wrappers.provider.MessageBodyWriterSubSet; import org.restlet.representation.EmptyRepresentation; import org.restlet.representation.Representation; import org.restlet.resource.ResourceException; import org.restlet.routing.Router; import org.restlet.service.MetadataService; /** * <p> * This class choose the JAX-RS resource class and method to use for a request * and handles the result from he resource method. Typically you should * instantiate a {@link JaxRsApplication} to run JAX-RS resource classes. * </p> * * Concurrency note: instances of this class or its subclasses can be invoked by * several threads at the same time and therefore must be thread-safe. You * should be especially careful when storing state in member variables. * * @author Stephan Koops */ public class JaxRsRestlet extends Restlet { /** * Structure to return the obtained {@link ResourceObject} and the * identified {@link ResourceMethod}. * * @author Stephan Koops */ class ResObjAndMeth { private ResourceMethod resourceMethod; private ResourceObject resourceObject; ResObjAndMeth(ResourceObject resourceObject, ResourceMethod resourceMethod) { this.resourceObject = resourceObject; this.resourceMethod = resourceMethod; } } /** * Structure to return the obtained {@link ResourceObject} and the remaining * path after identifying the object. * * @author Stephan Koops */ class ResObjAndRemPath { private ResourceObject resourceObject; private RemainingPath u; ResObjAndRemPath(ResourceObject resourceObject, RemainingPath u) { this.resourceObject = resourceObject; this.u = u; } } /** * Structure to return an instance of the identified * {@link RootResourceClass}, the matched URI path and the remaining path * after identifying the root resource class. * * @author Stephan Koops */ class RroRemPathAndMatchedPath { private String matchedUriPath; private ResourceObject rootResObj; private RemainingPath u; RroRemPathAndMatchedPath(ResourceObject rootResObj, RemainingPath u, String matchedUriPath) { this.rootResObj = rootResObj; this.u = u; this.matchedUriPath = matchedUriPath; } } private static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[0]; static { javax.ws.rs.ext.RuntimeDelegate .setInstance(new org.restlet.ext.jaxrs.internal.spi.RuntimeDelegateImpl()); } /** * Contains and handles the exceptions occurring while in resource objects * and providers, and also for the other cases where the runtime environment * should throw {@link WebApplicationException}. */ private final ExceptionHandler excHandler; private volatile ObjectFactory objectFactory; private final JaxRsProviders providers; private final ResourceClasses resourceClasses; /** * Contains the thread localized {@link CallContext}s. */ private final ThreadLocalizedContext tlContext = new ThreadLocalizedContext(); /** * Creates a new JaxRsRestlet with the given Context. Only the default * providers are loaded. * * @param context * the context from the parent, see * {@link Restlet#Restlet(Context)}. * @param metadataService * the metadata service of the {@link JaxRsApplication}. * @see #JaxRsRestlet(Context, MetadataService) */ public JaxRsRestlet(Context context, MetadataService metadataService) { super(context); final ExtensionBackwardMapping extensionBackwardMapping = new ExtensionBackwardMapping( metadataService); this.excHandler = new ExceptionHandler(getLogger()); this.providers = new JaxRsProviders(this.objectFactory, this.tlContext, extensionBackwardMapping, getLogger()); this.resourceClasses = new ResourceClasses(this.tlContext, this.providers, extensionBackwardMapping, getLogger()); this.loadDefaultProviders(); } /** * Will use the given JAX-RS root resource class.<br> * If the given class is not a valid root resource class, a warning is * logged and false is returned. * * @param jaxRsClass * A JAX-RS root resource class or provider class to add. If the * root resource class is already available in this JaxRsRestlet, * it is ignored for later calls of this method. * @return true if the class is added or was already included, or false if * the given class is not a valid class (a warning was logged). * @throws IllegalArgumentException * if the root resource class is null. */ public boolean addClass(Class<?> jaxRsClass) throws IllegalArgumentException { if (jaxRsClass == null) throw new IllegalArgumentException( "The JAX-RS class to add must not be null"); boolean used = false; if (Util.isRootResourceClass(jaxRsClass)) { used = resourceClasses.addRootClass(jaxRsClass); } if (Util.isProvider(jaxRsClass)) { if (providers.addClass(jaxRsClass)) used = true; } // @see Application#getClasses() // @see Application#getSingletons() if (!used) { getLogger() .warning( "The class " + jaxRsClass + " is neither a provider nor a root resource class"); } return used; } /** * Adds the provider object as default provider to this JaxRsRestlet. * * @param jaxRsProvider * The provider object or class name. * @return true, if the provider is ok and added, otherwise false. * @throws IllegalArgumentException * if null was given * @see {@link javax.ws.rs.ext.Provider} */ private boolean addDefaultProvider(Object jaxRsProvider) { try { return addSingleton(jaxRsProvider, true); } catch (GenericSignatureFormatError e) { getLogger().warning( "Unable to add default provider class : " + jaxRsProvider); } return false; } /** * Adds the provider object to this JaxRsRestlet. * * @param jaxRsProviderOrRootresourceObject * the JAX-RS provider or root resource object * @return true, if the provider is ok and added, otherwise false. * @throws IllegalArgumentException * if null was given * @see javax.ws.rs.ext.Provider */ public boolean addSingleton(Object jaxRsProviderOrRootresourceObject) throws IllegalArgumentException { return addSingleton(jaxRsProviderOrRootresourceObject, false); } /** * Adds the provider object to this JaxRsRestlet. * * @param jaxRsProvider * The provider object or class name. * @param defaultProvider * @return true, if the provider is ok and added, otherwise false. * @throws IllegalArgumentException * if null was given * @see {@link javax.ws.rs.ext.Provider} */ private boolean addSingleton(Object jaxRsProvider, boolean defaultProvider) throws IllegalArgumentException { if (jaxRsProvider instanceof String) { try { jaxRsProvider = Engine.loadClass((String) jaxRsProvider) .newInstance(); } catch (ClassNotFoundException e) { getLogger().fine( "Unable to load provider class : " + jaxRsProvider); jaxRsProvider = null; } catch (InstantiationException e) { getLogger().fine( "Unable to instantiate provider : " + jaxRsProvider); jaxRsProvider = null; } catch (IllegalAccessException e) { getLogger().fine( "Unable to access to provider : " + jaxRsProvider); jaxRsProvider = null; } catch (NoClassDefFoundError e) { getLogger().fine( "Unable to load provider class : " + jaxRsProvider); jaxRsProvider = null; } } else if (jaxRsProvider == null) throw new IllegalArgumentException( "The JAX-RS object to add must not be null"); else if (jaxRsProvider instanceof Class<?>) throw new IllegalArgumentException( "The JAX-RS object to add must not be a java.lang.Class"); boolean used = false; if (jaxRsProvider != null) { if (defaultProvider || Util.isProvider(jaxRsProvider.getClass())) { used = providers.addSingleton(jaxRsProvider, defaultProvider); } if (Util.isRootResourceClass(jaxRsProvider.getClass())) { throw new NotYetImplementedException( "only providers are allowed as singletons for now"); // used = ... } if (!used) { final String warning = ("The class " + jaxRsProvider.getClass() + " is neither a provider nor a root resource class"); getLogger().warning(warning); } } return used; } // now methods for the daily work /** * Sets the Restlet that is called, if no (root) resource class or method * could be found. * * @param notMatchedRestlet * the Restlet to call, if no (root) resource class or method * could be found. * @see #setNoRootResClHandler(Restlet) * @see #setNoResourceClHandler(Restlet) * @see #setNoResMethodHandler(Restlet) * @see Router#attachDefault(Restlet) */ public void attachDefault(Restlet notMatchedRestlet) { this.setNoRootResClHandler(notMatchedRestlet); this.setNoResourceClHandler(notMatchedRestlet); this.setNoResMethodHandler(notMatchedRestlet); } /** * Converts the given entity - returned by the resource method - to a * Restlet {@link Representation}. * * @param entity * the entity to convert. * @param resourceMethod * The resource method created the entity. Could be null, if an * exception is handled, e.g. a {@link WebApplicationException}. * @param jaxRsResponseMediaType * The MediaType of the JAX-RS {@link javax.ws.rs.core.Response}. * May be null. * @param jaxRsRespHeaders * The headers added to the {@link javax.ws.rs.core.Response} by * the {@link ResponseBuilder}. * @param accMediaTypes * the accepted media type from the current call context, or the * returned of the JAX-RS {@link javax.ws.rs.core.Response}. * @return the corresponding Restlet Representation. Returns * <code>null</code> only if null was given. * @throws WebApplicationException * @see AbstractMethodWrapper.EntityGetter */ private Representation convertToRepresentation(Object entity, ResourceMethod resourceMethod, MediaType jaxRsResponseMediaType, MultivaluedMap<String, Object> jaxRsRespHeaders, SortedMetadata<MediaType> accMediaTypes) throws ImplementationException { if (entity instanceof Representation) { Representation repr = (Representation) entity; // ensures that a supported character set is set repr.setCharacterSet(getSupportedCharSet(repr.getCharacterSet())); if (jaxRsResponseMediaType != null) { repr.setMediaType(Converter .getMediaTypeWithoutParams(jaxRsResponseMediaType)); } return repr; } Type genericReturnType; Class<? extends Object> entityClass; Annotation[] methodAnnotations; if (resourceMethod != null) // is default methodAnnotations = resourceMethod.getAnnotations(); else methodAnnotations = EMPTY_ANNOTATION_ARRAY; if (entity instanceof GenericEntity) { GenericEntity<?> genericEntity = (GenericEntity<?>) entity; genericReturnType = genericEntity.getType(); entityClass = genericEntity.getRawType(); entity = genericEntity.getEntity(); } else { entityClass = (entity != null) ? entity.getClass() : null; if (resourceMethod != null) // is default genericReturnType = resourceMethod.getGenericReturnType(); else genericReturnType = null; if (genericReturnType instanceof Class && ((Class<?>) genericReturnType) .isAssignableFrom(javax.ws.rs.core.Response.class)) { genericReturnType = entityClass; } } final MultivaluedMap<String, Object> httpResponseHeaders = new WrappedRequestForHttpHeaders( tlContext.get().getResponse(), jaxRsRespHeaders); final Representation repr; if (entity != null) { final MediaType respMediaType = determineMediaType( jaxRsResponseMediaType, resourceMethod, entityClass, genericReturnType); final MessageBodyWriterSubSet mbws; mbws = providers.writerSubSet(entityClass, genericReturnType); if (mbws.isEmpty()) throw excHandler.noMessageBodyWriter(entityClass, genericReturnType, methodAnnotations, null, null); final MessageBodyWriter mbw = mbws.getBestWriter(respMediaType, methodAnnotations, accMediaTypes); if (mbw == null) throw excHandler.noMessageBodyWriter(entityClass, genericReturnType, methodAnnotations, respMediaType, accMediaTypes); repr = new JaxRsOutputRepresentation<Object>(entity, genericReturnType, respMediaType, methodAnnotations, mbw, httpResponseHeaders); } else { // entity == null repr = new EmptyRepresentation(); repr.setMediaType(determineMediaType(jaxRsResponseMediaType, resourceMethod, entityClass, genericReturnType)); } repr.setCharacterSet(getSupportedCharSet(httpResponseHeaders)); return repr; } /** * @param o * @param subResourceLocator * @param callContext * @return * @throws WebApplicationException * @throws RequestHandledException */ private ResourceObject createSubResource(ResourceObject o, SubResourceLocator subResourceLocator, CallContext callContext) throws WebApplicationException, RequestHandledException { try { o = subResourceLocator.createSubResource(o, resourceClasses, getLogger()); } catch (WebApplicationException e) { throw e; } catch (RuntimeException e) { throw excHandler.runtimeExecption(e, subResourceLocator, callContext, "Could not create new instance of resource class"); } catch (MissingAnnotationException e) { throw excHandler.missingAnnotation(e, callContext, "Could not create new instance of resource class"); } catch (InstantiateException e) { throw excHandler.instantiateExecption(e, callContext, "Could not create new instance of resource class"); } catch (InvocationTargetException e) { throw handleInvocationTargetExc(e); } catch (ConvertRepresentationException e) { throw excHandler.convertRepresentationExc(e); } return o; } /** * Determines the MediaType for a response, see JAX-RS-Spec (2008-08-27), * section 3.8 "Determining the MediaType of Responses" * * @param jaxRsResponseMediaType * @param resourceMethod * The ResourceMethod that created the entity. * @param entityClass * needed, if neither the resource method nor the resource class * is annotated with @{@link Produces}. * @param genericReturnType * needed, if neither the resource method nor the resource class * is annotated with @{@link Produces}. * @param methodAnnotation * needed, if neither the resource method nor the resource class * is annotated with @{@link Produces}. * @param mbws * The {@link MessageBodyWriter}s, that support the class of the * returned entity object as generic type of the * {@link MessageBodyWriter}. * @return the determined {@link MediaType}. If no method is given, * "text/plain" is returned. * @throws WebApplicationException */ private MediaType determineMediaType(MediaType jaxRsResponseMediaType, ResourceMethod resourceMethod, Class<?> entityClass, Type genericReturnType) throws WebApplicationException { // In many cases it is not possible to statically determine the media // type of a response. The following algorithm is used to determine the // response media type, Mselected, at run time: // 1. If the method returns an instance of Response whose metadata // includes the response media type (Mspecified) then set Mselected = // Mspecified, finish. if (jaxRsResponseMediaType != null) return jaxRsResponseMediaType; if (resourceMethod == null) return MediaType.TEXT_PLAIN; CallContext callContext = tlContext.get(); // 2. Gather the set of producible media types P : // // (a) If the method is annotated with @Produces, set P = {V (method)} // where V (t) represents the values of @Produces on the specified // target t. // // (b) Else if the class is annotated with @Produces, set P = {V // (class)}. Collection<MediaType> p = resourceMethod.getProducedMimes(); // (c) Else set P = {V (writers)} where 'writers' is the set of // MessageBodyWriter that support the class of the returned entity // object. if (p.isEmpty()) { p = providers.writerSubSet(entityClass, genericReturnType) .getAllProducibleMediaTypes(); } // 3. If P = {}, set P = {'*/*'} if (p.isEmpty()) return MediaType.ALL; else p = sortByConcreteness(p); // 4. Obtain the acceptable media types A. If A = {}, set A = {'*/*'} SortedMetadata<MediaType> a = callContext.getAccMediaTypes(); if (a.isEmpty()) a = SortedMetadata.getMediaTypeAll(); // 5. Set M = {}. For each member of A,a: // (a) For each member of P,p: // (a.1) If a is compatible with p, add S(a,p) to M, where the function // S returns the most specific media type of the pair with the q-value // of a. List<MediaType> m = new ArrayList<MediaType>(); // First test equality (ideal) for (MediaType a1 : a) { for (MediaType p1 : p) { if (a1.equals(p1)) { m.add(a1); } } } // Otherwise test inclusion (good) for (MediaType a1 : a) { for (MediaType p1 : p) { if (a1.includes(p1)) { m.add(MediaType.getMostSpecific(a1, p1)); } } } // Finally test compatibility (most flexible) for (MediaType a1 : a) { for (MediaType p1 : p) { if (a1.isCompatible(p1)) { m.add(MediaType.getMostSpecific(a1, p1)); } } } // 6. If M = {} then generate a WebApplicationException with a not // acceptable response (HTTP 406 status) and no entity. The exception // MUST be processed as described in section 3.3.4. Finish. if (m.isEmpty()) excHandler.notAcceptableWhileDetermineMediaType(); // 7. Sort M in descending order, with a primary key of specificity (n/m // > n/* > */*) and secondary key of q-value. // TODO // 8. For each member of M,m: // (a) If m is a concrete type, set Mselected = m, finish. for (MediaType mediaType : m) if (mediaType.isConcrete()) return mediaType; // 9. If M contains '*/*' or 'application/*', set Mselected = // 'application/octet-stream', finish. if (m.contains(MediaType.ALL) || m.contains(MediaType.APPLICATION_ALL)) return MediaType.APPLICATION_OCTET_STREAM; // 10. Generate a WebApplicationException with a not acceptable response // (HTTP 406 status) and no entity. The exception MUST be processed as // described in section 3.3.4. Finish. throw excHandler.notAcceptableWhileDetermineMediaType(); } /** * Returns the Restlet that is called, if no resource method class could be * found. * * @return the Restlet that is called, if no resource method class could be * found. * @see #setNoResMethodHandler(Restlet) */ public Restlet getNoResMethodHandler() { return excHandler.getNoResMethodHandler(); } /** * Returns the Restlet that is called, if no resource class could be found. * * @return the Restlet that is called, if no resource class could be found. */ public Restlet getNoResourceClHandler() { return excHandler.getNoResourceClHandler(); } /** * Returns the Restlet that is called, if no root resource class could be * found. You could remove a given Restlet by set null here.<br> * If no Restlet is given here, status 404 will be returned. * * @return the Restlet that is called, if no root resource class could be * found. * @see #setNoRootResClHandler(Restlet) */ public Restlet getNoRootResClHandler() { return excHandler.getNoRootResClHandler(); } /** * Returns the ObjectFactory for root resource class and provider * instantiation, if given. * * @return the ObjectFactory for root resource class and provider * instantiation, if given. */ public ObjectFactory getObjectFactory() { return this.objectFactory; } /** * Returns an unmodifiable set with the attached root resource classes. * * @return an unmodifiable set with the attached root resource classes. */ public Set<Class<?>> getRootResourceClasses() { Set<Class<?>> rrcs = new HashSet<Class<?>>(); for (RootResourceClass rootResourceClass : this.resourceClasses.roots()) rrcs.add(rootResourceClass.getJaxRsClass()); return Collections.unmodifiableSet(rrcs); } /** * Returns a Collection with all root uris attached to this JaxRsRestlet. * * @return a Collection with all root uris attached to this JaxRsRestlet. */ public Collection<String> getRootUris() { List<String> uris = new ArrayList<String>(); for (RootResourceClass rrc : this.resourceClasses.roots()) uris.add(rrc.getPathRegExp().getPathTemplateEnc()); return Collections.unmodifiableCollection(uris); } /** * Handles a call by looking for the resource metod to call, call it and * return the result. * * @param request * The {@link Request} to handle. * @param response * The {@link Response} to update. */ @Override public void handle(Request request, Response response) { super.handle(request, response); ResourceObject resourceObject = null; final Reference baseRef = request.getResourceRef().getBaseRef(); request.setRootRef(new Reference(baseRef.toString())); // NICE Normally, the "rootRef" property is set by the VirtualHost, each // time a request is handled by one of its routes. // Email from Jerome, 2008-09-22 try { CallContext callContext; callContext = new CallContext(request, response); tlContext.set(callContext); try { ResObjAndMeth resObjAndMeth; resObjAndMeth = requestMatching(); callContext.setReadOnly(); ResourceMethod resourceMethod = resObjAndMeth.resourceMethod; resourceObject = resObjAndMeth.resourceObject; Object result = invokeMethod(resourceMethod, resourceObject); handleResult(result, resourceMethod); } catch (WebApplicationException e) { // the message of the Exception is not used in the // WebApplicationException jaxRsRespToRestletResp(this.providers.convert(e), null); return; } } catch (RequestHandledException e) { // Exception was handled and data were set into the Response. } finally { Representation entity = request.getEntity(); if (entity != null) entity.release(); } } /** * Handles the given Exception, catched by an invoke of a resource method or * a creation if a sub resource object. * * @param ite * @param methodName * @throws RequestHandledException * throws this message to exit the method and indicate, that the * request was handled. * @throws RequestHandledException */ private RequestHandledException handleInvocationTargetExc( InvocationTargetException ite) throws RequestHandledException { Throwable cause = ite.getCause(); if (cause instanceof ResourceException) { // avoid mapping to a JAX-RS Response and back to a Restlet Response Status status = ((ResourceException) cause).getStatus(); Response restletResponse = tlContext.get().getResponse(); restletResponse.setStatus(status); } else { javax.ws.rs.core.Response jaxRsResp = this.providers.convert(cause); jaxRsRespToRestletResp(jaxRsResp, null); } throw new RequestHandledException(); } /** * Sets the result of the resource method invocation into the response. Do * necessary converting. * * @param result * the object returned by the resource method * @param resourceMethod * the resource method; it is needed for the conversion. Could be * null, if an exception is handled, e.g. a * {@link WebApplicationException}. */ private void handleResult(Object result, ResourceMethod resourceMethod) { Response restletResponse = tlContext.get().getResponse(); if (result instanceof javax.ws.rs.core.Response) { jaxRsRespToRestletResp((javax.ws.rs.core.Response) result, resourceMethod); } else if (result instanceof ResponseBuilder) { String warning = "the method " + resourceMethod + " returnef a ResponseBuilder. You should " + "call responseBuilder.build() in the resource method"; getLogger().warning(warning); jaxRsRespToRestletResp(((ResponseBuilder) result).build(), resourceMethod); } else { if (result == null) // no representation restletResponse.setStatus(Status.SUCCESS_NO_CONTENT); else restletResponse.setStatus(Status.SUCCESS_OK); SortedMetadata<MediaType> accMediaTypes; accMediaTypes = tlContext.get().getAccMediaTypes(); restletResponse.setEntity(convertToRepresentation(result, resourceMethod, null, null, accMediaTypes)); } } /** * Identifies the method that will handle the request, see JAX-RS-Spec * (2008-04-16), section 3.7.2 "Request Matching", Part 3: Identify the * method that will handle the request:" * * @return Resource Object and Method, that handle the request. * @throws RequestHandledException * for example if the method was OPTIONS, but no special * Resource Method for OPTIONS is available. * @throws ResourceMethodNotFoundException */ private ResObjAndMeth identifyMethod(ResObjAndRemPath resObjAndRemPath, MediaType givenMediaType) throws RequestHandledException { CallContext callContext = tlContext.get(); org.restlet.data.Method httpMethod = callContext.getRequest() .getMethod(); // 3. Identify the method that will handle the request: // (a) ResourceObject resObj = resObjAndRemPath.resourceObject; RemainingPath u = resObjAndRemPath.u; // (a) 1 ResourceClass resourceClass = resObj.getResourceClass(); Collection<ResourceMethod> resourceMethods = resourceClass .getMethodsForPath(u); if (resourceMethods.isEmpty()) excHandler.resourceMethodNotFound();// NICE (resourceClass, u); // (a) 2: remove methods not support the given method boolean alsoGet = httpMethod.equals(Method.HEAD); removeNotSupportedHttpMethod(resourceMethods, httpMethod, alsoGet); if (resourceMethods.isEmpty()) { Set<Method> allowedMethods = resourceClass.getAllowedMethods(u); if (httpMethod.equals(Method.OPTIONS)) { callContext.getResponse().getAllowedMethods() .addAll(allowedMethods); throw new RequestHandledException(); } excHandler.methodNotAllowed(allowedMethods); } // (a) 3 if (givenMediaType != null) { Collection<ResourceMethod> supporting = resourceMethods; resourceMethods = new ArrayList<ResourceMethod>(); for (ResourceMethod resourceMethod : supporting) { if (resourceMethod.isGivenMediaTypeSupported(givenMediaType)) resourceMethods.add(resourceMethod); } if (resourceMethods.isEmpty()) excHandler.unsupportedMediaType(supporting); } // (a) 4 SortedMetadata<MediaType> accMediaTypes = callContext .getAccMediaTypes(); Collection<ResourceMethod> supporting = resourceMethods; resourceMethods = new ArrayList<ResourceMethod>(); for (ResourceMethod resourceMethod : supporting) { if (resourceMethod.isAcceptedMediaTypeSupported(accMediaTypes)) resourceMethods.add(resourceMethod); } if (resourceMethods.isEmpty()) { excHandler.noResourceMethodForAccMediaTypes(supporting); } // (b) and (c) ResourceMethod bestResourceMethod = getBestMethod(resourceMethods, givenMediaType, accMediaTypes, httpMethod); MatchingResult mr = bestResourceMethod.getPathRegExp().match(u); addPathVarsToMap(mr, callContext); String matchedUriPart = mr.getMatched(); if (matchedUriPart.length() > 0) { Object jaxRsResObj = resObj.getJaxRsResourceObject(); callContext.addForMatched(jaxRsResObj, matchedUriPart); } return new ResObjAndMeth(resObj, bestResourceMethod); } /** * Identifies the root resource class, see JAX-RS-Spec (2008-04-16), section * 3.7.2 "Request Matching", Part 1: "Identify the root resource class" * * @param u * the remaining path after the base ref * @return The identified root resource object, the remaining path after * identifying and the matched template parameters; see * {@link RroRemPathAndMatchedPath}. * @throws WebApplicationException * @throws RequestHandledException */ private RroRemPathAndMatchedPath identifyRootResource(RemainingPath u) throws WebApplicationException, RequestHandledException { // 1. Identify the root resource class: // (a) // c: Set<Class>: root resource classes // e: Set<RegExp> // Map<UriTemplateRegExp, Class> eAndCs = new HashMap(); Collection<RootResourceClass> eAndCs = new ArrayList<RootResourceClass>(); // (a) and (b) and (c) Filter E for (RootResourceClass rootResourceClass : this.resourceClasses.roots()) { // Map.Entry<UriTemplateRegExp, Class> eAndC = eAndCIter.next(); // UriTemplateRegExp regExp = eAndC.getKey(); // Class clazz = eAndC.getValue(); PathRegExp rrcPathRegExp = rootResourceClass.getPathRegExp(); MatchingResult matchingResult = rrcPathRegExp.match(u); if (matchingResult == null) continue; // doesn't match if (matchingResult.getFinalCapturingGroup().isEmptyOrSlash()) eAndCs.add(rootResourceClass); else if (rootResourceClass.hasSubResourceMethodsOrLocators()) eAndCs.add(rootResourceClass); } // (d) if (eAndCs.isEmpty()) excHandler.rootResourceNotFound(); // (e) and (f) RootResourceClass tClass = getFirstByNoOfLiteralCharsNoOfCapturingGroups(eAndCs); // (f) PathRegExp rMatch = tClass.getPathRegExp(); MatchingResult matchResult = rMatch.match(u); u = matchResult.getFinalCapturingGroup(); addPathVarsToMap(matchResult, tlContext.get()); ResourceObject o = instantiateRrc(tClass); return new RroRemPathAndMatchedPath(o, u, matchResult.getMatched()); } /** * Instantiates the root resource class and handles thrown exceptions. * * @param rrc * the root resource class to instantiate * @return the instance of the root resource * @throws WebApplicationException * if a WebApplicationException was thrown while creating the * instance. * @throws RequestHandledException * If an Exception was thrown and the request is already * handeled. */ private ResourceObject instantiateRrc(RootResourceClass rrc) throws WebApplicationException, RequestHandledException { ResourceObject o; try { o = rrc.getInstance(this.objectFactory); } catch (WebApplicationException e) { throw e; } catch (RuntimeException e) { throw excHandler.runtimeExecption(e, null, tlContext.get(), "Could not create new instance of root resource class"); } catch (InstantiateException e) { throw excHandler.instantiateExecption(e, tlContext.get(), "Could not create new instance of root resource class"); } catch (InvocationTargetException e) { throw handleInvocationTargetExc(e); } return o; } /** * Invokes the (sub) resource method. Handles / converts also occuring * exceptions. * * @param resourceMethod * the (sub) resource method to invoke * @param resourceObject * the resource object to invoke the method on. * @return the object returned by the (sub) resource method. * @throws RequestHandledException * if the request is already handled * @throws WebApplicationException * if a JAX-RS class throws an WebApplicationException */ private Object invokeMethod(ResourceMethod resourceMethod, ResourceObject resourceObject) throws WebApplicationException, RequestHandledException { Object result; try { result = resourceMethod.invoke(resourceObject); } catch (WebApplicationException e) { throw e; } catch (InvocationTargetException ite) { throw handleInvocationTargetExc(ite); } catch (RuntimeException e) { throw excHandler.runtimeExecption(e, resourceMethod, tlContext.get(), "Can not invoke the resource method"); } catch (MethodInvokeException e) { throw excHandler.methodInvokeException(e, tlContext.get(), "Can not invoke the resource method"); } catch (ConvertRepresentationException e) { throw excHandler.convertRepresentationExc(e); } return result; } /** * Converts the given JAX-RS {@link javax.ws.rs.core.Response} to a Restlet * {@link Response}. * * @param jaxRsResponse * The response returned by the resource method, perhaps as * attribute of a {@link WebApplicationException}. * @param resourceMethod * The resource method creating the response. Could be null, if * an exception is handled, e.g. a * {@link WebApplicationException}. */ private void jaxRsRespToRestletResp( javax.ws.rs.core.Response jaxRsResponse, ResourceMethod resourceMethod) { Response restletResponse = tlContext.get().getResponse(); restletResponse.setStatus(Status.valueOf(jaxRsResponse.getStatus())); MultivaluedMap<String, Object> httpHeaders = jaxRsResponse .getMetadata(); MediaType respMediaType = getMediaType(httpHeaders); Object jaxRsEntity = jaxRsResponse.getEntity(); SortedMetadata<MediaType> accMediaType; if (respMediaType != null) accMediaType = SortedMetadata.get(respMediaType); else accMediaType = tlContext.get().getAccMediaTypes(); restletResponse.setEntity(convertToRepresentation(jaxRsEntity, resourceMethod, respMediaType, httpHeaders, accMediaType)); copyResponseHeaders(httpHeaders, restletResponse); } private void loadDefaultProviders() { addDefaultProvider(new BufferedReaderProvider()); addDefaultProvider(new ByteArrayProvider()); // [ifndef android] instruction addDefaultProvider("org.restlet.ext.jaxrs.internal.provider.DataSourceProvider"); // not yet tested // [ifndef android] instruction addDefaultProvider("org.restlet.ext.jaxrs.internal.provider.FileUploadProvider"); // Fall-back on the Restlet converter service addDefaultProvider(new ConverterProvider()); // [ifndef gae] instruction addDefaultProvider(new org.restlet.ext.jaxrs.internal.provider.FileProvider()); addDefaultProvider(new InputStreamProvider()); // [ifndef android] instruction addDefaultProvider("org.restlet.ext.jaxrs.internal.provider.JaxbElementProvider"); // [ifndef android] instruction addDefaultProvider("org.restlet.ext.jaxrs.internal.provider.JaxbProvider"); // [ifndef android] instruction // not yet tested addDefaultProvider("org.restlet.ext.jaxrs.internal.provider.MultipartProvider"); addDefaultProvider(new SimpleXmlProvider()); addDefaultProvider(new ReaderProvider()); addDefaultProvider(new StreamingOutputProvider()); addDefaultProvider(new StringProvider()); addDefaultProvider(new WwwFormFormProvider()); addDefaultProvider(new WwwFormMmapProvider()); addDefaultProvider(new SourceProvider()); addDefaultProvider(new WebAppExcMapper()); addDefaultProvider("org.restlet.ext.jaxrs.internal.provider.JsonProvider"); } /** * Obtains the object that will handle the request, see JAX-RS-Spec * (2008-04-16), section 3.7.2 "Request Matching", Part 2: "btain the object * that will handle the request" * * @param rroRemPathAndMatchedPath * @throws WebApplicationException * @throws RequestHandledException * @throws RuntimeException */ private ResObjAndRemPath obtainObject( RroRemPathAndMatchedPath rroRemPathAndMatchedPath) throws WebApplicationException, RequestHandledException { ResourceObject o = rroRemPathAndMatchedPath.rootResObj; RemainingPath u = rroRemPathAndMatchedPath.u; ResourceClass resClass = o.getResourceClass(); CallContext callContext = tlContext.get(); callContext.addForMatched(o.getJaxRsResourceObject(), rroRemPathAndMatchedPath.matchedUriPath); // Part 2 for (;;) // (j) { // (a) If U is null or '/' go to step 3 if (u.isEmptyOrSlash()) { return new ResObjAndRemPath(o, u); } // (b) Set C = class ofO,E = {} Collection<ResourceMethodOrLocator> eWithMethod = new ArrayList<ResourceMethodOrLocator>(); // (c) and (d) Filter E: remove members do not match U or final // match not empty for (ResourceMethodOrLocator methodOrLocator : resClass .getResourceMethodsAndLocators()) { PathRegExp pathRegExp = methodOrLocator.getPathRegExp(); MatchingResult matchingResult = pathRegExp.match(u); if (matchingResult == null) continue; if (matchingResult.getFinalCapturingGroup().isEmptyOrSlash()) eWithMethod.add(methodOrLocator); // the following is added by Stephan (is not in spec 2008-03-06) else if (methodOrLocator instanceof SubResourceLocator) eWithMethod.add(methodOrLocator); } // (e) If E is empty -> HTTP 404 if (eWithMethod.isEmpty()) excHandler.resourceNotFound();// NICE (o.getClass(), u); // (f) and (g) sort E, use first member of E ResourceMethodOrLocator firstMeth = getFirstByNoOfLiteralCharsNoOfCapturingGroups(eWithMethod); PathRegExp rMatch = firstMeth.getPathRegExp(); MatchingResult matchingResult = rMatch.match(u); addPathVarsToMap(matchingResult, callContext); // (h) When Method is resource method if (firstMeth instanceof ResourceMethod) return new ResObjAndRemPath(o, u); String matchedUriPart = matchingResult.getMatched(); Object jaxRsResObj = o.getJaxRsResourceObject(); callContext.addForMatched(jaxRsResObj, matchedUriPart); // (g) and (i) u = matchingResult.getFinalCapturingGroup(); SubResourceLocator subResourceLocator = (SubResourceLocator) firstMeth; o = createSubResource(o, subResourceLocator, callContext); resClass = o.getResourceClass(); // (j) Go to step 2a (repeat for) } } /** * Implementation of algorithm in JAX-RS-Spec (2008-04-16), Section 3.7.2 * "Request Matching" * * @return (Sub)Resource Method * @throws RequestHandledException * @throws WebApplicationException */ private ResObjAndMeth requestMatching() throws RequestHandledException, WebApplicationException { Request restletRequest = tlContext.get().getRequest(); // Part 1 RemainingPath u = new RemainingPath(restletRequest.getResourceRef() .getRemainingPart()); RroRemPathAndMatchedPath rrm = identifyRootResource(u); // Part 2 ResObjAndRemPath resourceObjectAndPath = obtainObject(rrm); Representation entity = restletRequest.getEntity(); // Part 3 MediaType givenMediaType; if (entity != null) givenMediaType = entity.getMediaType(); else givenMediaType = null; ResObjAndMeth method = identifyMethod(resourceObjectAndPath, givenMediaType); return method; } /** * Sets the Restlet that will handle the {@link Request}s, if no resource * method could be found. * * @param noResMethodHandler * the noResMethodHandler to set * @see #getNoResMethodHandler() * @see #setNoResourceClHandler(Restlet) * @see #setNoRootResClHandler(Restlet) * @see #attachDefault(Restlet) */ public void setNoResMethodHandler(Restlet noResMethodHandler) { excHandler.setNoResMethodHandler(noResMethodHandler); } /** * Sets the Restlet that will handle the {@link Request}s, if no resource * class could be found. You could remove a given Restlet by set null here.<br> * If no Restlet is given here, status 404 will be returned. * * @param noResourceClHandler * the noResourceClHandler to set * @see #getNoResourceClHandler() * @see #setNoResMethodHandler(Restlet) * @see #setNoRootResClHandler(Restlet) * @see #attachDefault(Restlet) */ public void setNoResourceClHandler(Restlet noResourceClHandler) { excHandler.setNoResourceClHandler(noResourceClHandler); } /** * Sets the Restlet that is called, if no root resource class could be * found. You could remove a given Restlet by set null here.<br> * If no Restlet is given here, status 404 will be returned. * * @param noRootResClHandler * the Restlet to call, if no root resource class could be found. * @see #getNoRootResClHandler() * @see #setNoResourceClHandler(Restlet) * @see #setNoResMethodHandler(Restlet) * @see #attachDefault(Restlet) */ public void setNoRootResClHandler(Restlet noRootResClHandler) { excHandler.setNoRootResClHandler(noRootResClHandler); } /** * Sets the ObjectFactory for root resource class and provider * instantiation. * * @param objectFactory * the ObjectFactory for root resource class and provider * instantiation. */ public void setObjectFactory(ObjectFactory objectFactory) { this.objectFactory = objectFactory; this.providers.setObjectFactory(objectFactory); } @Override public void start() throws Exception { providers.initAll(); // Must be invoked as a last step super.start(); } }