com.sun.jersey.server.impl.modelapi.annotation.IntrospectionModeller Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jersey-bundle Show documentation
Show all versions of jersey-bundle Show documentation
A bundle containing code of all jar-based modules that provide
JAX-RS and Jersey-related features. Such a bundle is *only intended* for
developers that do not use Maven's dependency system.
The bundle does not include code for contributes, tests and samples.
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2010-2013 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* http://glassfish.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package com.sun.jersey.server.impl.modelapi.annotation;
import com.sun.jersey.core.reflection.AnnotatedMethod;
import com.sun.jersey.core.reflection.MethodList;
import com.sun.jersey.core.header.MediaTypes;
import com.sun.jersey.api.model.AbstractField;
import com.sun.jersey.api.model.AbstractResource;
import com.sun.jersey.api.model.AbstractResourceConstructor;
import com.sun.jersey.api.model.AbstractResourceMethod;
import com.sun.jersey.api.model.AbstractSetterMethod;
import com.sun.jersey.api.model.AbstractSubResourceLocator;
import com.sun.jersey.api.model.AbstractSubResourceMethod;
import com.sun.jersey.api.model.Parameter;
import com.sun.jersey.api.model.Parameter.Source;
import com.sun.jersey.api.model.Parameterized;
import com.sun.jersey.api.model.PathValue;
import com.sun.jersey.core.reflection.ReflectionHelper;
import com.sun.jersey.impl.ImplMessages;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.security.AccessController;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.ws.rs.Consumes;
import javax.ws.rs.CookieParam;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.Encoded;
import javax.ws.rs.FormParam;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.HttpMethod;
import javax.ws.rs.MatrixParam;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
/**
*
* @author japod
*/
public class IntrospectionModeller {
private static final Logger LOGGER = Logger.getLogger(IntrospectionModeller.class.getName());
public static AbstractResource createResource(Class> resourceClass) {
final Class> annotatedResourceClass = getAnnotatedResourceClass(resourceClass);
final Path rPathAnnotation = annotatedResourceClass.getAnnotation(Path.class);
final boolean isRootResourceClass = (null != rPathAnnotation);
final boolean isEncodedAnotOnClass =
(null != annotatedResourceClass.getAnnotation(Encoded.class));
AbstractResource resource;
if (isRootResourceClass) {
resource = new AbstractResource(resourceClass,
new PathValue(rPathAnnotation.value()));
} else { // just a subresource class
resource = new AbstractResource(resourceClass);
}
workOutConstructorsList(resource, resourceClass.getConstructors(),
isEncodedAnotOnClass);
workOutFieldsList(resource, isEncodedAnotOnClass);
final MethodList methodList = new MethodList(resourceClass);
workOutSetterMethodsList(resource, methodList, isEncodedAnotOnClass);
final Consumes classScopeConsumesAnnotation =
annotatedResourceClass.getAnnotation(Consumes.class);
final Produces classScopeProducesAnnotation =
annotatedResourceClass.getAnnotation(Produces.class);
workOutResourceMethodsList(resource, methodList, isEncodedAnotOnClass,
classScopeConsumesAnnotation, classScopeProducesAnnotation);
workOutSubResourceMethodsList(resource, methodList, isEncodedAnotOnClass,
classScopeConsumesAnnotation, classScopeProducesAnnotation);
workOutSubResourceLocatorsList(resource, methodList, isEncodedAnotOnClass);
workOutPostConstructPreDestroy(resource);
if (LOGGER.isLoggable(Level.FINEST)) {
LOGGER.finest(ImplMessages.NEW_AR_CREATED_BY_INTROSPECTION_MODELER(
resource.toString()));
}
return resource;
}
private static Class getAnnotatedResourceClass(Class rc) {
if (rc.isAnnotationPresent(Path.class)) return rc;
for (Class i : rc.getInterfaces())
if (i.isAnnotationPresent(Path.class)) return i;
return rc;
}
private static void addConsumes(
AnnotatedMethod am,
AbstractResourceMethod resourceMethod,
Consumes consumeMimeAnnotation) {
// Override annotation is present in method
if (am.isAnnotationPresent(Consumes.class))
consumeMimeAnnotation = am.getAnnotation(Consumes.class);
resourceMethod.setAreInputTypesDeclared(consumeMimeAnnotation != null);
resourceMethod.getSupportedInputTypes().addAll(
MediaTypes.createMediaTypes(consumeMimeAnnotation));
}
private static void addProduces(
AnnotatedMethod am,
AbstractResourceMethod resourceMethod,
Produces produceMimeAnnotation) {
// Override annotation is present in method
if (am.isAnnotationPresent(Produces.class))
produceMimeAnnotation = am.getAnnotation(Produces.class);
resourceMethod.setAreOutputTypesDeclared(produceMimeAnnotation != null);
resourceMethod.getSupportedOutputTypes().addAll(
MediaTypes.createQualitySourceMediaTypes(produceMimeAnnotation));
}
private static void workOutConstructorsList(
AbstractResource resource,
Constructor[] ctorArray,
boolean isEncoded) {
if (null != ctorArray) {
for (Constructor ctor : ctorArray) {
final AbstractResourceConstructor aCtor =
new AbstractResourceConstructor(ctor);
processParameters(
resource.getResourceClass(),
ctor.getDeclaringClass(),
aCtor,
ctor,
isEncoded);
resource.getConstructors().add(aCtor);
}
}
}
private static void workOutFieldsList(
AbstractResource resource,
boolean isEncoded) {
Class c = resource.getResourceClass();
if (c.isInterface())
return;
while (c != Object.class) {
for (final Field f : c.getDeclaredFields()) {
if (f.getDeclaredAnnotations().length > 0) {
final AbstractField af = new AbstractField(f);
Parameter p = createParameter(
resource.getResourceClass(),
f.getDeclaringClass(),
isEncoded,
f.getType(),
f.getGenericType(),
f.getAnnotations());
if (null != p) {
af.getParameters().add(p);
resource.getFields().add(af);
}
}
}
c = c.getSuperclass();
}
}
private static void workOutPostConstructPreDestroy(AbstractResource resource) {
Class postConstruct = AccessController.doPrivileged(ReflectionHelper.classForNamePA("javax.annotation.PostConstruct"));
if (postConstruct == null)
return;
Class preDestroy = AccessController.doPrivileged(ReflectionHelper.classForNamePA("javax.annotation.PreDestroy"));
final MethodList methodList = new MethodList(resource.getResourceClass(), true);
HashSet names = new HashSet();
for (AnnotatedMethod m : methodList.
hasAnnotation(postConstruct).
hasNumParams(0).
hasReturnType(void.class)) {
Method method = m.getMethod();
// only add method if not hidden/overridden
if (names.add(method.getName())) {
AccessController.doPrivileged(ReflectionHelper.setAccessibleMethodPA(method));
resource.getPostConstructMethods().add(0, method);
}
}
names = new HashSet();
for (AnnotatedMethod m : methodList.
hasAnnotation(preDestroy).
hasNumParams(0).
hasReturnType(void.class)) {
Method method = m.getMethod();
// only add method if not hidden/overridden
if (names.add(method.getName())) {
AccessController.doPrivileged(ReflectionHelper.setAccessibleMethodPA(method));
resource.getPreDestroyMethods().add(method);
}
}
}
private static void workOutSetterMethodsList(
AbstractResource resource,
MethodList methodList,
boolean isEncoded) {
for (AnnotatedMethod m : methodList.
hasNotMetaAnnotation(HttpMethod.class).
hasNotAnnotation(Path.class).
hasNumParams(1).
hasReturnType(void.class).
nameStartsWith("set")) {
final AbstractSetterMethod asm = new AbstractSetterMethod(resource, m.getMethod(), m.getAnnotations());
Parameter p = createParameter(
resource.getResourceClass(),
m.getMethod().getDeclaringClass(),
isEncoded,
m.getParameterTypes()[0],
m.getGenericParameterTypes()[0],
m.getAnnotations());
if (null != p) {
asm.getParameters().add(p);
resource.getSetterMethods().add(asm);
}
}
}
private static void workOutResourceMethodsList(
AbstractResource resource,
MethodList methodList,
boolean isEncoded,
Consumes classScopeConsumesAnnotation,
Produces classScopeProducesAnnotation) {
for (AnnotatedMethod m : methodList.hasMetaAnnotation(HttpMethod.class).
hasNotAnnotation(Path.class)) {
final ReflectionHelper.ClassTypePair ct = getGenericReturnType(resource.getResourceClass(), m.getMethod());
final AbstractResourceMethod resourceMethod = new AbstractResourceMethod(
resource,
m.getMethod(),
ct.c, ct.t,
m.getMetaMethodAnnotations(HttpMethod.class).get(0).value(),
m.getAnnotations());
addConsumes(m, resourceMethod, classScopeConsumesAnnotation);
addProduces(m, resourceMethod, classScopeProducesAnnotation);
processParameters(
resourceMethod.getResource().getResourceClass(),
resourceMethod.getMethod().getDeclaringClass(),
resourceMethod, m, isEncoded);
resource.getResourceMethods().add(resourceMethod);
}
}
private static ReflectionHelper.ClassTypePair getGenericReturnType(
Class concreteClass,
Method m) {
return getGenericType(concreteClass, m.getDeclaringClass(), m.getReturnType(), m.getGenericReturnType());
}
private static void workOutSubResourceMethodsList(
AbstractResource resource,
MethodList methodList,
boolean isEncoded,
Consumes classScopeConsumesAnnotation,
Produces classScopeProducesAnnotation) {
for (AnnotatedMethod m : methodList.hasMetaAnnotation(HttpMethod.class).hasAnnotation(Path.class)) {
final Path mPathAnnotation = m.getAnnotation(Path.class);
final PathValue pv = new PathValue(mPathAnnotation.value());
final boolean emptySegmentCase = "/".equals(pv.getValue()) || "".equals(pv.getValue());
if (!emptySegmentCase) {
final ReflectionHelper.ClassTypePair ct = getGenericReturnType(resource.getResourceClass(), m.getMethod());
final AbstractSubResourceMethod abstractSubResourceMethod = new AbstractSubResourceMethod(
resource,
m.getMethod(),
ct.c, ct.t,
pv,
m.getMetaMethodAnnotations(HttpMethod.class).get(0).value(),
m.getAnnotations());
addConsumes(m, abstractSubResourceMethod, classScopeConsumesAnnotation);
addProduces(m, abstractSubResourceMethod, classScopeProducesAnnotation);
processParameters(
abstractSubResourceMethod.getResource().getResourceClass(),
abstractSubResourceMethod.getMethod().getDeclaringClass(),
abstractSubResourceMethod, m, isEncoded);
resource.getSubResourceMethods().add(abstractSubResourceMethod);
} else { // treat the sub-resource method as a resource method
final ReflectionHelper.ClassTypePair ct = getGenericReturnType(resource.getResourceClass(), m.getMethod());
final AbstractResourceMethod abstractResourceMethod = new AbstractResourceMethod(
resource,
m.getMethod(),
ct.c, ct.t,
m.getMetaMethodAnnotations(HttpMethod.class).get(0).value(),
m.getAnnotations());
addConsumes(m, abstractResourceMethod, classScopeConsumesAnnotation);
addProduces(m, abstractResourceMethod, classScopeProducesAnnotation);
processParameters(
abstractResourceMethod.getResource().getResourceClass(),
abstractResourceMethod.getMethod().getDeclaringClass(),
abstractResourceMethod, m, isEncoded);
resource.getResourceMethods().add(abstractResourceMethod);
}
}
}
private static void workOutSubResourceLocatorsList(
AbstractResource resource,
MethodList methodList,
boolean isEncoded) {
for (AnnotatedMethod m : methodList.hasNotMetaAnnotation(HttpMethod.class).
hasAnnotation(Path.class)) {
final Path mPathAnnotation = m.getAnnotation(Path.class);
final AbstractSubResourceLocator subResourceLocator = new AbstractSubResourceLocator(
resource,
m.getMethod(),
new PathValue(
mPathAnnotation.value()),
m.getAnnotations());
processParameters(
subResourceLocator.getResource().getResourceClass(),
subResourceLocator.getMethod().getDeclaringClass(),
subResourceLocator, m, isEncoded);
resource.getSubResourceLocators().add(subResourceLocator);
}
}
private static void processParameters(
Class concreteClass,
Class declaringClass,
Parameterized parametrized,
Constructor ctor,
boolean isEncoded) {
Class[] parameterTypes = ctor.getParameterTypes();
Type[] genericParameterTypes = ctor.getGenericParameterTypes();
// Workaround bug http://bugs.sun.com/view_bug.do?bug_id=5087240
if (parameterTypes.length != genericParameterTypes.length) {
Type[] _genericParameterTypes = new Type[parameterTypes.length];
_genericParameterTypes[0] = parameterTypes[0];
System.arraycopy(genericParameterTypes, 0, _genericParameterTypes, 1, genericParameterTypes.length);
genericParameterTypes = _genericParameterTypes;
}
processParameters(
concreteClass, declaringClass,
parametrized,
((null != ctor.getAnnotation(Encoded.class)) || isEncoded),
parameterTypes,
genericParameterTypes,
ctor.getParameterAnnotations());
}
private static void processParameters(
Class concreteClass,
Class declaringClass,
Parameterized parametrized,
AnnotatedMethod method,
boolean isEncoded) {
processParameters(
concreteClass, declaringClass,
parametrized,
((null != method.getAnnotation(Encoded.class)) || isEncoded),
method.getParameterTypes(),
method.getGenericParameterTypes(),
method.getParameterAnnotations());
}
private static void processParameters(
Class concreteClass,
Class declaringClass,
Parameterized parametrized,
boolean isEncoded,
Class[] parameterTypes,
Type[] genericParameterTypes,
Annotation[][] parameterAnnotations) {
for (int i = 0; i < parameterTypes.length; i++) {
Parameter parameter = createParameter(
concreteClass, declaringClass,
isEncoded, parameterTypes[i],
genericParameterTypes[i],
parameterAnnotations[i]);
if (null != parameter) {
parametrized.getParameters().add(parameter);
} else {
// clean up the parameters
parametrized.getParameters().removeAll(parametrized.getParameters());
break;
}
}
}
private static interface ParamAnnotationHelper {
public String getValueOf(T a);
public Parameter.Source getSource();
}
private static Map createParamAnotHelperMap() {
Map m = new WeakHashMap();
m.put(Context.class, new ParamAnnotationHelper() {
@Override
public String getValueOf(Context a) {
return null;
}
@Override
public Parameter.Source getSource() {
return Parameter.Source.CONTEXT;
}
});
m.put(HeaderParam.class, new ParamAnnotationHelper() {
@Override
public String getValueOf(HeaderParam a) {
return a.value();
}
@Override
public Parameter.Source getSource() {
return Parameter.Source.HEADER;
}
});
m.put(CookieParam.class, new ParamAnnotationHelper() {
@Override
public String getValueOf(CookieParam a) {
return a.value();
}
@Override
public Parameter.Source getSource() {
return Parameter.Source.COOKIE;
}
});
m.put(MatrixParam.class, new ParamAnnotationHelper() {
@Override
public String getValueOf(MatrixParam a) {
return a.value();
}
@Override
public Parameter.Source getSource() {
return Parameter.Source.MATRIX;
}
});
m.put(QueryParam.class, new ParamAnnotationHelper() {
@Override
public String getValueOf(QueryParam a) {
return a.value();
}
@Override
public Parameter.Source getSource() {
return Parameter.Source.QUERY;
}
});
m.put(PathParam.class, new ParamAnnotationHelper() {
@Override
public String getValueOf(PathParam a) {
return a.value();
}
@Override
public Parameter.Source getSource() {
return Parameter.Source.PATH;
}
});
m.put(FormParam.class, new ParamAnnotationHelper() {
@Override
public String getValueOf(FormParam a) {
return a.value();
}
@Override
public Parameter.Source getSource() {
return Parameter.Source.FORM;
}
});
return Collections.unmodifiableMap(m);
}
private final static Map ANOT_HELPER_MAP =
createParamAnotHelperMap();
@SuppressWarnings("unchecked")
private static Parameter createParameter(
Class concreteClass,
Class declaringClass,
boolean isEncoded,
Class> paramClass,
Type paramType,
Annotation[] annotations) {
if (null == annotations) {
return null;
}
Annotation paramAnnotation = null;
Parameter.Source paramSource = null;
String paramName = null;
boolean paramEncoded = isEncoded;
String paramDefault = null;
/**
* Create a parameter from the list of annotations.
* Unknown annotated parameters are also supported, and in such a
* cases the last unrecognized annotation is taken to be that
* associated with the parameter.
*/
for (Annotation annotation : annotations) {
if (ANOT_HELPER_MAP.containsKey(annotation.annotationType())) {
ParamAnnotationHelper helper = ANOT_HELPER_MAP.get(annotation.annotationType());
paramAnnotation = annotation;
paramSource = helper.getSource();
paramName = helper.getValueOf(annotation);
} else if (Encoded.class == annotation.annotationType()) {
paramEncoded = true;
} else if (DefaultValue.class == annotation.annotationType()) {
paramDefault = ((DefaultValue) annotation).value();
} else {
// lets only clear things down if we've not found a ANOT_HELPER_MAP annotation already
if (paramAnnotation == null) {
paramAnnotation = annotation;
paramSource = Source.UNKNOWN;
paramName = getValue(annotation);
}
}
}
if (paramAnnotation == null) {
paramSource = Parameter.Source.ENTITY;
}
ReflectionHelper.ClassTypePair ct = getGenericType(concreteClass, declaringClass, paramClass, paramType);
paramType = ct.t;
paramClass = ct.c;
return new Parameter(
annotations, paramAnnotation,
paramSource,
paramName, paramType, paramClass,
paramEncoded, paramDefault);
}
private static String getValue(Annotation a) {
try {
Method m = a.annotationType().getMethod("value");
if (m.getReturnType() != String.class)
return null;
return (String)m.invoke(a);
} catch (Exception ex) {
}
return null;
}
private static ReflectionHelper.ClassTypePair getGenericType(
final Class concreteClass,
final Class declaringClass,
final Class c,
final Type t) {
if (t instanceof TypeVariable) {
ReflectionHelper.ClassTypePair ct = ReflectionHelper.resolveTypeVariable(
concreteClass,
declaringClass,
(TypeVariable)t);
if (ct != null) {
return ct;
}
} else if (t instanceof ParameterizedType) {
final ParameterizedType pt = (ParameterizedType)t;
final Type[] ptts = pt.getActualTypeArguments();
boolean modified = false;
for (int i = 0; i < ptts.length; i++) {
ReflectionHelper.ClassTypePair ct =
getGenericType(concreteClass, declaringClass, (Class)pt.getRawType(), ptts[i]);
if (ct.t != ptts[i]) {
ptts[i] = ct.t;
modified = true;
}
}
if (modified) {
ParameterizedType rpt = new ParameterizedType() {
@Override
public Type[] getActualTypeArguments() {
return ptts.clone();
}
@Override
public Type getRawType() {
return pt.getRawType();
}
@Override
public Type getOwnerType() {
return pt.getOwnerType();
}
};
return new ReflectionHelper.ClassTypePair((Class)pt.getRawType(), rpt);
}
} else if (t instanceof GenericArrayType) {
GenericArrayType gat = (GenericArrayType)t;
final ReflectionHelper.ClassTypePair ct =
getGenericType(concreteClass, declaringClass, null, gat.getGenericComponentType());
if (gat.getGenericComponentType() != ct.t) {
try {
Class ac = ReflectionHelper.getArrayClass(ct.c);
return new ReflectionHelper.ClassTypePair(ac, ac);
} catch (Exception e) {
}
}
}
return new ReflectionHelper.ClassTypePair(c, t);
}
}