/** * 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.internal.wrappers; import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.logging.Logger; import javax.ws.rs.Consumes; import javax.ws.rs.Produces; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Context; import javax.ws.rs.core.Variant; import org.restlet.data.MediaType; import org.restlet.ext.jaxrs.internal.core.ThreadLocalizedContext; import org.restlet.ext.jaxrs.internal.exceptions.ConvertCookieParamException; import org.restlet.ext.jaxrs.internal.exceptions.ConvertHeaderParamException; import org.restlet.ext.jaxrs.internal.exceptions.ConvertMatrixParamException; import org.restlet.ext.jaxrs.internal.exceptions.ConvertPathParamException; import org.restlet.ext.jaxrs.internal.exceptions.ConvertQueryParamException; import org.restlet.ext.jaxrs.internal.exceptions.ConvertRepresentationException; import org.restlet.ext.jaxrs.internal.exceptions.IllegalMethodParamTypeException; import org.restlet.ext.jaxrs.internal.exceptions.IllegalPathOnMethodException; import org.restlet.ext.jaxrs.internal.exceptions.IllegalPathParamTypeException; import org.restlet.ext.jaxrs.internal.exceptions.MethodInvokeException; import org.restlet.ext.jaxrs.internal.exceptions.MissingAnnotationException; import org.restlet.ext.jaxrs.internal.exceptions.NoMessageBodyReaderException; import org.restlet.ext.jaxrs.internal.util.Converter; import org.restlet.ext.jaxrs.internal.util.SortedMetadata; import org.restlet.ext.jaxrs.internal.wrappers.provider.ExtensionBackwardMapping; import org.restlet.ext.jaxrs.internal.wrappers.provider.JaxRsProviders; /** * This class wraps JAX-RS resource methods and sub resource methods.<br> * It does not wrap sub resource locators; see {@link SubResourceLocator} * * @author Stephan Koops */ public class ResourceMethod extends AbstractMethodWrapper implements ResourceMethodOrLocator { // NICE a subset of MessageBodyReaders could be cached here. // . . . . . . . . . . (the concrete media type of the request could differ) // NICE check, which subset of MessageBodyWriters could be cached here. // . . (the media type is available via @Produces on the entity provider) /** * the Java method that should be referenced for annotations. This method * could be different from the method is called for executing, see section * 3.6 "Annotation Inheritance" of JSR-311-spec. * * @see AbstractMethodWrapper#executeMethod */ private final Method annotatedMethod; /** @see Consumes */ private final List<MediaType> consumedMimes; private final org.restlet.data.Method httpMethod; /** @see Produces */ private final List<MediaType> producedMimes; /** * Contains the list of supported {@link Variant}s (lazy initialized by * {@link #getSupportedVariants()}. */ private final Collection<Variant> supportedVariants; /** * Creates a wrapper for a resource method. * * @param executeMethod * the Java method to wrap. * @param annotatedMethod * the java method that contains the annotations for this method. * @param resourceClass * the wrapped class of the method. * @param httpMethod * the HTTP method of the Java method. It will be checked be the * {@link org.restlet.ext.jaxrs.JaxRsRestlet}, so avoiding double * work. It will be requested from the javaMethod. * @param tlContext * the {@link ThreadLocalizedContext} of the * {@link org.restlet.ext.jaxrs.JaxRsRestlet}. * @param jaxRsProviders * all entity providers * @param extensionBackwardMapping * the extension backward mapping * @param logger * @throws IllegalPathOnMethodException * @throws MissingAnnotationException * @throws IllegalArgumentException * if the annotated method is null * @throws IllegalMethodParamTypeException * if one of the method parameters annotated with @ * {@link Context} has a type that must not be annotated with * @{@link Context}. * @throws IllegalPathParamTypeException */ ResourceMethod(Method executeMethod, Method annotatedMethod, ResourceClass resourceClass, org.restlet.data.Method httpMethod, ThreadLocalizedContext tlContext, JaxRsProviders jaxRsProviders, ExtensionBackwardMapping extensionBackwardMapping, Logger logger) throws IllegalPathOnMethodException, IllegalArgumentException, MissingAnnotationException, IllegalMethodParamTypeException, IllegalPathParamTypeException { super(executeMethod, annotatedMethod, resourceClass, tlContext, jaxRsProviders, extensionBackwardMapping, true, logger); this.annotatedMethod = annotatedMethod; if (httpMethod != null) { this.httpMethod = httpMethod; } else { this.httpMethod = WrapperUtil.getHttpMethod(this.annotatedMethod); } this.consumedMimes = createConsumedMimes(); this.producedMimes = createProducedMimes(); this.supportedVariants = createSupportedVariants(); } /** * Creates the list of the consumed mimes from the * {@link AbstractMethodWrapper#annotatedMethod} and the * {@link AbstractMethodWrapper#executeMethod} to be stored in the final * instance variable {@link #consumedMimes}. */ private List<MediaType> createConsumedMimes() { Consumes consumes; consumes = this.annotatedMethod.getAnnotation(Consumes.class); if (consumes == null) { consumes = this.executeMethod.getDeclaringClass().getAnnotation( Consumes.class); } if (consumes != null) { return WrapperUtil.convertToMediaTypes(consumes.value()); } return Collections.singletonList(MediaType.ALL); } /** * Creates the list of the produced mimes from the * {@link AbstractMethodWrapper#annotatedMethod} and the * {@link AbstractMethodWrapper#executeMethod} to be stored in the final * instance variable {@link #producedMimes}. */ private List<MediaType> createProducedMimes() { Produces produces; produces = this.annotatedMethod.getAnnotation(Produces.class); if (produces == null) { produces = this.executeMethod.getDeclaringClass().getAnnotation( Produces.class); } if (produces != null) { return WrapperUtil.convertToMediaTypes(produces.value()); } return Collections.emptyList(); } /** * Creates the list of the supported variants from the * {@link #getProducedMimes()}to be stored in the final instance variable * {@link #supportedVariants}. */ private Collection<Variant> createSupportedVariants() { final Collection<Variant> supportedVariants = new ArrayList<Variant>(); for (final MediaType mediaType : getProducedMimes()) { javax.ws.rs.core.MediaType mt; mt = Converter.toJaxRsMediaType(mediaType); supportedVariants.add(new Variant(mt, null, null)); } return supportedVariants; } /** * Returns the array of the annotations on the Java method * * @return the array of the annotations on the Java method * @see Method#getAnnotations() */ public Annotation[] getAnnotations() { return this.annotatedMethod.getAnnotations(); } /** * @return Returns an unmodifiable List with the MediaTypes the given * resourceMethod consumes. If no consumeMime is given, this method * returns a List with MediaType.ALL. Will never return null. */ public List<MediaType> getConsumedMimes() { return this.consumedMimes; } /** * Returns the generic return type of the wrapped method. * * @return the generic return type of the wrapped method. * @see Method#getGenericReturnType() */ public Type getGenericReturnType() { return this.executeMethod.getGenericReturnType(); } /** * @return Returns the HTTP method supported by the wrapped java method. */ public org.restlet.data.Method getHttpMethod() { return this.httpMethod; } /** * @return Returns an unmodifiable List of MediaTypes the given Resource * Method. if the method is not annotated with {@link Produces}, * than the {@link Produces} of the Resource class is returned. If * no {@link Produces} can be found, an empty (also unmodifiable) * List will returned.<br> * This method never returns null. */ public List<MediaType> getProducedMimes() { return this.producedMimes; } /** * Returns the {@link Variant}s supported by this resource method. * * @return the {@link Variant}s supported by this resource method. */ public Collection<Variant> getSupportedVariants() { return this.supportedVariants; } /** * Invokes the method and returned the created representation for the * response. * * @param resourceObject * @return the unwrapped returned object by the wrapped method. * @throws MethodInvokeException * @throws InvocationTargetException * @throws NoMessageBodyReaderException * @throws MissingAnnotationException * @throws ConvertCookieParamException * @throws ConvertQueryParamException * @throws ConvertMatrixParamException * @throws ConvertPathParamException * @throws ConvertHeaderParamException * @throws ConvertRepresentationException * @throws WebApplicationException */ public Object invoke(ResourceObject resourceObject) throws MethodInvokeException, InvocationTargetException, ConvertRepresentationException, WebApplicationException { try { return internalInvoke(resourceObject); } catch (IllegalArgumentException e) { throw new MethodInvokeException("Could not invoke " + this.executeMethod, e); } catch (IllegalAccessException e) { throw new MethodInvokeException("Could not invoke " + this.executeMethod, e); } } /** * Check if this method supports the media type to produce for a request. * * @param accMediaTypess * The Media Types the client would accept, ordered by quality. * See {@link SortedMetadata} * @return Returns true, if the give MediaType is supported by the method, * or no MediaType is given for the method, otherweise false. */ public boolean isAcceptedMediaTypeSupported( SortedMetadata<MediaType> accMediaTypess) { if ((accMediaTypess == null) || accMediaTypess.isEmpty()) { return true; } final List<MediaType> prodMimes = getProducedMimes(); if (prodMimes.isEmpty()) { return true; } for (final MediaType producedMediaType : prodMimes) { for (final MediaType accMediaType : accMediaTypess) { if (accMediaType.isCompatible(producedMediaType)) { return true; } } } return false; } /** * @param resourceMethod * the resource method to check * @param givenMediaType * the MediaType of the request entity * @return Returns true, if the given MediaType is supported by the method, * or no MediaType is given for the method, otherweise false; */ public boolean isGivenMediaTypeSupported(MediaType givenMediaType) { if (givenMediaType == null) { return true; } for (final MediaType consumedMime : getConsumedMimes()) { if (consumedMime.includes(givenMediaType)) { return true; } } return false; } /** * * @param resourceMethod * @param requestedMethod * @return true, if the gien java method is annotated with a runtime * designator for the given requested Method. If the requested * method is null, than the method returns true, when the method is * annotated with any runtime desginator. * @see #annotatedWithMethodDesignator(Method) */ public boolean isHttpMethodSupported(org.restlet.data.Method requestedMethod) { return isHttpMethodSupported(requestedMethod, false); } /** * Checks, if this method suppors the given HTTP method. * * @param requestedMethod * the requested Method * @param alsoGet * if true, than this method returns also true, if this method is * GET. This functionality is needed for HEAD. * @return true, if this method supports the given HTTP method. Returns also * true, if alsoGet is true and this method is true. * @throws IllegalArgumentException */ public boolean isHttpMethodSupported( org.restlet.data.Method requestedMethod, boolean alsoGet) throws IllegalArgumentException { if (requestedMethod == null) { throw new IllegalArgumentException( "null is not a valid HTTP method"); } if (alsoGet && this.httpMethod.equals(org.restlet.data.Method.GET)) { return true; } return this.httpMethod.equals(requestedMethod); } @Override public String toString() { return this.getClass().getSimpleName() + "[" + this.executeMethod.toString() + ", " + this.httpMethod + "]"; } }