/**
* 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.engine.resource;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
import org.restlet.data.Form;
import org.restlet.data.Method;
import org.restlet.representation.Representation;
import org.restlet.resource.ServerResource;
import org.restlet.resource.Status;
import org.restlet.service.MetadataService;
// [excludes gwt]
/**
* Utilities to manipulate Restlet annotations.
*
* @author Jerome Louvel
*/
public class AnnotationUtils {
/** Annotation info cache. */
private static final ConcurrentMap<Class<?>, List<AnnotationInfo>> cache = new ConcurrentHashMap<Class<?>, List<AnnotationInfo>>();
/** Current instance. */
private static AnnotationUtils instance = new AnnotationUtils();
/** Returns the current instance of AnnotationUtils. */
public static AnnotationUtils getInstance() {
return instance;
}
/**
* Protected constructor.
*/
protected AnnotationUtils() {
}
/**
* Computes the annotation descriptors for the given class or interface.
*
* @param descriptors
* The annotation descriptors to update or null to create a new
* one.
* @param clazz
* The class or interface to introspect.
* @param initialClass
* The class or interface that runs the javaMethod.
* @return The annotation descriptors.
*/
private List<AnnotationInfo> addAnnotations(
List<AnnotationInfo> descriptors, Class<?> clazz,
Class<?> initialClass) {
List<AnnotationInfo> result = descriptors;
if (clazz != null && !ServerResource.class.equals(clazz)) {
// Add the annotation descriptor
if (result == null) {
result = new CopyOnWriteArrayList<AnnotationInfo>();
}
// Inspect the current class
addThrowableAnnotationDescriptors(result, clazz, initialClass);
if (clazz.isInterface()) {
for (java.lang.reflect.Method javaMethod : clazz.getMethods()) {
addMethodAnnotationDescriptors(result, clazz, initialClass,
javaMethod);
}
} else {
for (java.lang.reflect.Method javaMethod : clazz
.getDeclaredMethods()) {
addMethodAnnotationDescriptors(result, clazz, initialClass,
javaMethod);
}
}
// Inspect the implemented interfaces for annotations
Class<?>[] interfaces = clazz.getInterfaces();
if (interfaces != null) {
for (Class<?> interfaceClass : interfaces) {
result = addAnnotations(result, interfaceClass,
initialClass);
}
}
// Add the annotations from the super class.
addAnnotations(result, clazz.getSuperclass(), initialClass);
}
return result;
}
/**
* Computes the annotation descriptors for the given Java method.
*
* @param descriptors
* The annotation descriptors to update or null to create a new
* one.
* @param clazz
* The class or interface that hosts the javaMethod.
* @param initialClass
* The class or interface that runs the javaMethod.
* @param javaMethod
* The Java method to inspect.
* @return The annotation descriptors.
*/
private List<AnnotationInfo> addMethodAnnotationDescriptors(
List<AnnotationInfo> descriptors, Class<?> clazz,
Class<?> initialClass, java.lang.reflect.Method javaMethod) {
List<AnnotationInfo> result = descriptors;
for (Annotation annotation : javaMethod.getAnnotations()) {
Annotation methodAnnotation = annotation.annotationType()
.getAnnotation(org.restlet.engine.connector.Method.class);
Method restletMethod = getRestletMethod(annotation,
methodAnnotation);
if (restletMethod != null) {
String toString = annotation.toString();
int startIndex = annotation.annotationType().getCanonicalName()
.length() + 8;
int endIndex = toString.length() - 1;
String value = null;
if (endIndex > startIndex) {
value = toString.substring(startIndex, endIndex);
if ("".equals(value)) {
value = null;
}
}
if (result == null) {
result = new CopyOnWriteArrayList<AnnotationInfo>();
}
result.add(new MethodAnnotationInfo(initialClass,
restletMethod, javaMethod, value));
}
}
for (Class<?> exceptionClass : javaMethod.getExceptionTypes()) {
for (Annotation annotation : exceptionClass.getAnnotations()) {
org.restlet.resource.Status statusAnnotation = annotation
.annotationType().getAnnotation(
org.restlet.resource.Status.class);
if (statusAnnotation != null) {
int code = statusAnnotation.value();
boolean serializable = statusAnnotation.serialize();
if (result == null) {
result = new CopyOnWriteArrayList<AnnotationInfo>();
}
result.add(new ThrowableAnnotationInfo(initialClass, code,
serializable));
}
}
}
return result;
}
/**
* Computes the annotation descriptors for the given Java method.
*
* @param descriptors
* The annotation descriptors to update or null to create a new
* one.
* @param clazz
* The class or interface that hosts the javaMethod.
* @param initialClass
* The class or interface that runs the javaMethod.
* @return The annotation descriptors.
*/
private List<AnnotationInfo> addThrowableAnnotationDescriptors(
List<AnnotationInfo> descriptors, Class<?> clazz,
Class<?> initialClass) {
List<AnnotationInfo> result = descriptors;
Annotation annotation = clazz
.getAnnotation(org.restlet.resource.Status.class);
if (annotation != null) {
Status status = (Status) annotation;
result.add(new ThrowableAnnotationInfo(initialClass,
status.value(), status.serialize()));
}
return result;
}
/**
* Clears the annotation descriptors cache.
*/
public void clearCache() {
cache.clear();
}
/**
* Returns the annotation descriptors for the given resource class.
*
* @param clazz
* The resource class to introspect.
* @return The list of annotation descriptors.
*/
public synchronized List<AnnotationInfo> getAnnotations(Class<?> clazz) {
List<AnnotationInfo> result = cache.get(clazz);
if (result == null) {
// Inspect the class itself for annotations
result = addAnnotations(result, clazz, clazz);
// Put the list in the cache if no one was previously present
List<AnnotationInfo> prev = cache.putIfAbsent(clazz, result);
if (prev != null) {
// Reuse the previous entry
result = prev;
}
}
return result;
}
/**
* Returns the annotation descriptors for the given resource class.
*
* @param javaMethod
* The Java method.
* @return The list of annotation descriptors.
*/
public List<AnnotationInfo> getAnnotations(Class<?> clazz,
java.lang.reflect.Method javaMethod) {
return addMethodAnnotationDescriptors(null, clazz, clazz, javaMethod);
}
/**
* Returns the first annotation descriptor matching the given Java method.
*
* @param annotations
* The list of annotations.
* @param javaMethod
* The method to match.
* @return The annotation descriptor.
*/
public MethodAnnotationInfo getMethodAnnotation(
List<AnnotationInfo> annotations,
java.lang.reflect.Method javaMethod) {
if (annotations != null) {
for (AnnotationInfo annotationInfo : annotations) {
if (annotationInfo instanceof MethodAnnotationInfo
&& annotationInfo.getJavaMethod().equals(javaMethod)) {
return (MethodAnnotationInfo) annotationInfo;
}
}
}
return null;
}
/**
* Returns the first annotation descriptor matching the given Restlet
* method.
*
* @param annotations
* The list of annotations.
* @param restletMethod
* The method to match.
* @param query
* The query parameters.
* @param entity
* The request entity to match or null if no entity is provided.
* @param metadataService
* The metadata service to use.
* @param converterService
* The converter service to use.
* @return The annotation descriptor.
* @throws IOException
*/
public MethodAnnotationInfo getMethodAnnotation(
List<AnnotationInfo> annotations, Method restletMethod, Form query,
Representation entity, MetadataService metadataService,
org.restlet.service.ConverterService converterService)
throws IOException {
if (annotations != null) {
for (AnnotationInfo annotationInfo : annotations) {
if (annotationInfo instanceof MethodAnnotationInfo) {
if (((MethodAnnotationInfo) annotationInfo).isCompatible(
restletMethod, query, entity, metadataService,
converterService)) {
return (MethodAnnotationInfo) annotationInfo;
}
}
}
}
return null;
}
/**
* Returns an instance of {@link Method} according to the given annotations.
*
* @param annotation
* Java annotation.
* @param methodAnnotation
* Annotation that corresponds to a Restlet method.
* @return An instance of {@link Method} according to the given annotations.
*/
protected Method getRestletMethod(Annotation annotation,
Annotation methodAnnotation) {
return (methodAnnotation == null) ? null
: Method.valueOf(((org.restlet.engine.connector.Method) methodAnnotation)
.value());
}
/**
* Returns the status annotation descriptor if present or null.
*
* @param clazz
* The class with the status attached.
* @return The status annotation descriptor if present or null.
*/
public ThrowableAnnotationInfo getThrowableAnnotationInfo(Class<?> clazz) {
List<AnnotationInfo> annotationInfos = getAnnotations(clazz);
for (AnnotationInfo annotationInfo : annotationInfos) {
if (annotationInfo instanceof ThrowableAnnotationInfo) {
return (ThrowableAnnotationInfo) annotationInfo;
}
}
return null;
}
/**
* Returns the {@link Throwable} class matching the given error code if
* present or null.
*
* @param javaMethod
* The method that holds {@link Throwable}.
* @param errorCode
* The error code to match.
* @return The {@link Throwable} class matching the given error code if
* present or null.
*/
public ThrowableAnnotationInfo getThrowableAnnotationInfo(
java.lang.reflect.Method javaMethod, int errorCode) {
for (Class<?> clazz : javaMethod.getExceptionTypes()) {
ThrowableAnnotationInfo tai = getThrowableAnnotationInfo(clazz);
if (tai != null && tai.getStatus().getCode() == errorCode) {
return tai;
}
}
return null;
}
/**
* Returns the {@link Throwable} class matching the given error code if
* present or null.
*
* @param javaMethod
* The method that holds {@link Throwable}.
* @param errorCode
* The error code to match.
* @return The {@link Throwable} class matching the given error code if
* present or null.
*/
public Class<?> getThrowableClass(java.lang.reflect.Method javaMethod,
int errorCode) {
for (Class<?> clazz : javaMethod.getExceptionTypes()) {
ThrowableAnnotationInfo tai = getThrowableAnnotationInfo(clazz);
if (tai != null && tai.getStatus().getCode() == errorCode) {
return clazz;
}
}
return null;
}
}