Package org.restlet.ext.jaxrs

Source Code of org.restlet.ext.jaxrs.JaxRsRestlet

/**
* 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.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.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 ResourceObject resourceObject;

        private ResourceMethod resourceMethod;

        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 ResourceObject rootResObj;

        private RemainingPath u;

        private String matchedUriPath;

        RroRemPathAndMatchedPath(ResourceObject rootResObj, RemainingPath u,
                String matchedUriPath) {
            this.rootResObj = rootResObj;
            this.u = u;
            this.matchedUriPath = matchedUriPath;
        }
    }

    static {
        javax.ws.rs.ext.RuntimeDelegate
                .setInstance(new org.restlet.ext.jaxrs.internal.spi.RuntimeDelegateImpl());
    }

    private static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[0];

    private final JaxRsProviders providers;

    private final ResourceClasses resourceClasses;

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

    /**
     * Contains the thread localized {@link CallContext}s.
     */
    private final ThreadLocalizedContext tlContext = new ThreadLocalizedContext();

    private volatile ObjectFactory objectFactory;

    /**
     * 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) {
            final String warning = ("The class " + jaxRsClass + " is neither a povider nor a root resource class");
            getLogger().warning(warning);
        }
        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 &#64;{@link Produces}.
     * @param genericReturnType
     *            needed, if neither the resource method nor the resource class
     *            is annotated with &#64;{@link Produces}.
     * @param methodAnnotation
     *            needed, if neither the resource method nor the resource class
     *            is annotated with &#64;{@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 {
        // 1. if the Response contains a MediaType, use it.
        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) + (b)
        Collection<MediaType> p = resourceMethod.getProducedMimes();
        // 2. (c)
        if (p.isEmpty()) {
            p = providers.writerSubSet(entityClass, genericReturnType)
                    .getAllProducibleMediaTypes();
            // 3.
            if (p.isEmpty())
                // '*/*', in conjunction with 8.:
                return MediaType.APPLICATION_OCTET_STREAM;
        }
        // 4. Obtain the acceptable media types A.
        SortedMetadata<MediaType> a = callContext.getAccMediaTypes();
        // 4. If A = {}, set A = {'*/*'}
        if (a.isEmpty())
            a = SortedMetadata.getMediaTypeAll();
        // 5. Sort P and A (A is already sorted)
        List<MediaType> pSorted = sortByConcreteness(p);
        // 6.
        List<MediaType> m = new ArrayList<MediaType>();
        for (MediaType acc : a)
            for (MediaType prod : pSorted)
                if (prod.isCompatible(acc))
                    m.add(MediaType.getMostSpecific(prod, acc));
        // 7.
        if (m.isEmpty())
            excHandler.notAcceptableWhileDetermineMediaType();
        // 8.
        for (MediaType mediaType : m)
            if (mediaType.isConcrete())
                return mediaType;
        // 9.
        if (m.contains(MediaType.ALL) || m.contains(MediaType.APPLICATION_ALL))
            return MediaType.APPLICATION_OCTET_STREAM;
        // 10.
        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());
        addDefaultProvider("org.restlet.ext.jaxrs.internal.provider.DataSourceProvider");

        // not yet tested
        addDefaultProvider("org.restlet.ext.jaxrs.internal.provider.FileUploadProvider");
        // Fall-back on the Restlet converter service
        addDefaultProvider(new ConverterProvider());

        addDefaultProvider(new org.restlet.ext.jaxrs.internal.provider.FileProvider());
        addDefaultProvider(new InputStreamProvider());
        addDefaultProvider("org.restlet.ext.jaxrs.internal.provider.JaxbElementProvider");
        addDefaultProvider("org.restlet.ext.jaxrs.internal.provider.JaxbProvider");

        // not yet tested
        addDefaultProvider("org.restlet.ext.jaxrs.internal.provider.MultipartProvider");

        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();
        super.start();
    }
}
TOP

Related Classes of org.restlet.ext.jaxrs.JaxRsRestlet

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.