org.glassfish.jersey.server.model.Parameter Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jersey-all Show documentation
Show all versions of jersey-all Show documentation
jersey-all is a rebundled verison of Jersey as one OSGi bundle.
The newest version!
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2010-2015 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 org.glassfish.jersey.server.model;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.security.AccessController;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.ws.rs.BeanParam;
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.MatrixParam;
import javax.ws.rs.PathParam;
import javax.ws.rs.QueryParam;
import javax.ws.rs.container.Suspended;
import javax.ws.rs.core.Context;
import org.glassfish.jersey.internal.util.ReflectionHelper;
import org.glassfish.jersey.internal.util.collection.ClassTypePair;
import org.glassfish.jersey.server.Uri;
/**
* Method parameter model.
*
* @author Marek Potociar (marek.potociar at oracle.com)
*/
public class Parameter implements AnnotatedElement {
private static final Logger LOGGER = Logger.getLogger(Parameter.class.getName());
/**
* Parameter injection sources type.
*/
public enum Source {
/**
* Context parameter injection source.
*/
CONTEXT,
/**
* Cookie parameter injection source.
*/
COOKIE,
/**
* Entity parameter injection source.
*/
ENTITY,
/**
* Form parameter injection source.
*/
FORM,
/**
* Header parameter injection source.
*/
HEADER,
/**
* Uri parameter injection source.
*/
URI,
/**
* Matrix parameter injection source.
*/
MATRIX,
/**
* Path parameter injection source.
*/
PATH,
/**
* Query parameter injection source.
*/
QUERY,
/**
* Suspended async response injection source.
*/
SUSPENDED,
/**
* Bean param parameter injection source.
*/
BEAN_PARAM,
/**
* Unknown parameter injection source.
*/
UNKNOWN
}
private interface ParamAnnotationHelper {
public String getValueOf(T a);
public Parameter.Source getSource();
}
private static Map createParamAnnotationHelperMap() {
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(CookieParam.class, new ParamAnnotationHelper() {
@Override
public String getValueOf(CookieParam a) {
return a.value();
}
@Override
public Parameter.Source getSource() {
return Parameter.Source.COOKIE;
}
});
m.put(FormParam.class, new ParamAnnotationHelper() {
@Override
public String getValueOf(FormParam a) {
return a.value();
}
@Override
public Parameter.Source getSource() {
return Parameter.Source.FORM;
}
});
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(MatrixParam.class, new ParamAnnotationHelper() {
@Override
public String getValueOf(MatrixParam a) {
return a.value();
}
@Override
public Parameter.Source getSource() {
return Parameter.Source.MATRIX;
}
});
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(QueryParam.class, new ParamAnnotationHelper() {
@Override
public String getValueOf(QueryParam a) {
return a.value();
}
@Override
public Parameter.Source getSource() {
return Parameter.Source.QUERY;
}
});
m.put(Suspended.class, new ParamAnnotationHelper() {
@Override
public String getValueOf(Suspended a) {
return Suspended.class.getName();
}
@Override
public Parameter.Source getSource() {
return Parameter.Source.SUSPENDED;
}
});
m.put(Uri.class, new ParamAnnotationHelper() {
@Override
public String getValueOf(Uri a) {
return a.value();
}
@Override
public Parameter.Source getSource() {
return Parameter.Source.URI;
}
});
m.put(BeanParam.class, new ParamAnnotationHelper() {
@Override
public String getValueOf(BeanParam a) {
return null;
}
@Override
public Parameter.Source getSource() {
return Parameter.Source.BEAN_PARAM;
}
});
return Collections.unmodifiableMap(m);
}
private static final Map ANNOTATION_HELPER_MAP = createParamAnnotationHelperMap();
/**
* Create a parameter model.
*
* @param concreteClass concrete resource method handler implementation class.
* @param declaringClass declaring class of the method the parameter belongs to or field that this parameter represents.
* @param encodeByDefault flag indicating whether the parameter should be encoded by default or not. Note that a presence
* of {@link Encoded} annotation in the list of the parameter {@code annotations} will override any
* value set in the flag to {@code true}.
* @param rawType raw Java parameter type.
* @param type generic Java parameter type.
* @param annotations parameter annotations.
* @return new parameter model.
*/
@SuppressWarnings("unchecked")
public static Parameter create(
Class concreteClass,
Class declaringClass,
boolean encodeByDefault,
Class rawType,
Type type,
Annotation[] annotations) {
if (null == annotations) {
return null;
}
Annotation paramAnnotation = null;
Parameter.Source paramSource = null;
String paramName = null;
boolean paramEncoded = encodeByDefault;
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 (ANNOTATION_HELPER_MAP.containsKey(annotation.annotationType())) {
ParamAnnotationHelper helper = ANNOTATION_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 {
// Take latest unknown annotation, but don't override known annotation
if ((paramAnnotation == null) || (paramSource == Source.UNKNOWN)) {
paramAnnotation = annotation;
paramSource = Source.UNKNOWN;
paramName = getValue(annotation);
}
}
}
if (paramAnnotation == null) {
paramSource = Parameter.Source.ENTITY;
}
ClassTypePair ct = ReflectionHelper.resolveGenericType(
concreteClass, declaringClass, rawType, type);
if (paramSource == Source.BEAN_PARAM) {
return new BeanParameter(
annotations,
paramAnnotation,
paramName,
ct.rawClass(),
ct.type(),
paramEncoded,
paramDefault);
} else {
return new Parameter(
annotations,
paramAnnotation,
paramSource,
paramName,
ct.rawClass(),
ct.type(),
paramEncoded,
paramDefault);
}
}
private static List create(
Class concreteClass,
Class declaringClass,
boolean keepEncoded,
Class[] parameterTypes,
Type[] genericParameterTypes,
Annotation[][] parameterAnnotations) {
final List parameters = new ArrayList<>(parameterTypes.length);
for (int i = 0; i < parameterTypes.length; i++) {
final Parameter parameter = Parameter.create(
concreteClass,
declaringClass,
keepEncoded,
parameterTypes[i],
genericParameterTypes[i],
parameterAnnotations[i]);
if (null != parameter) {
parameters.add(parameter);
} else {
// TODO throw IllegalStateException instead?
return Collections.emptyList();
}
}
return parameters;
}
/**
* Create a list of parameter models for a given resource method handler
* injectable constructor.
*
* @param concreteClass concrete resource method handler implementation class.
* @param declaringClass class where the method has been declared.
* @param ctor injectable constructor of the resource method handler.
* @param keepEncoded set to {@code true} to disable automatic decoding
* of all the constructor parameters. (See {@link Encoded}.
* @return a list of constructor parameter models.
*/
public static List create(
Class concreteClass,
Class declaringClass,
Constructor ctor,
boolean keepEncoded) {
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;
}
return create(
concreteClass, declaringClass,
((null != ctor.getAnnotation(Encoded.class)) || keepEncoded),
parameterTypes,
genericParameterTypes,
ctor.getParameterAnnotations());
}
/**
* Create a list of parameter models for a given Java method handling a resource
* method, sub-resource method or a sub-resource locator.
*
* @param concreteClass concrete resource method handler implementation class.
* @param declaringClass the class declaring the handling Java method.
* @param javaMethod Java method handling a resource method, sub-resource
* method or a sub-resource locator.
* @param keepEncoded set to {@code true} to disable automatic decoding
* of all the method parameters. (See {@link Encoded}.
* @return a list of handling method parameter models.
*/
public static List create(
Class concreteClass,
Class declaringClass,
Method javaMethod,
boolean keepEncoded) {
AnnotatedMethod method = new AnnotatedMethod(javaMethod);
return create(
concreteClass, declaringClass,
((null != method.getAnnotation(Encoded.class)) || keepEncoded),
method.getParameterTypes(),
method.getGenericParameterTypes(),
method.getParameterAnnotations());
}
/**
* Create new parameter model by overriding {@link Parameter.Source source}
* of the original parameter model.
*
* @param original original parameter model.
* @param source new overriding parameter source.
* @return source-overridden copy of the original parameter.
*/
public static Parameter overrideSource(Parameter original, Parameter.Source source) {
return new Parameter(
original.annotations,
original.sourceAnnotation,
source,
source.name(),
original.rawType,
original.type,
original.encoded,
original.defaultValue);
}
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) {
if (LOGGER.isLoggable(Level.FINER)) {
LOGGER.log(Level.FINER,
String.format("Unable to get the %s annotation value property", a.getClass().getName()), ex);
}
}
return null;
}
// Instance
private final Annotation[] annotations;
private final Annotation sourceAnnotation;
private final Parameter.Source source;
private final String sourceName;
private final boolean encoded;
private final String defaultValue;
private final Class rawType;
private final Type type;
private Parameter(
Annotation[] markers,
Annotation marker,
Source source,
String sourceName,
Class rawType,
Type type,
boolean encoded,
String defaultValue) {
this.annotations = markers;
this.sourceAnnotation = marker;
this.source = source;
this.sourceName = sourceName;
this.rawType = rawType;
this.type = type;
this.encoded = encoded;
this.defaultValue = defaultValue;
}
/**
* Bean Parameter class represents a parameter annotated with {@link BeanParam} which in fact represents
* additional set of parameters.
*/
public static class BeanParameter extends Parameter {
private final Collection parameters;
private BeanParameter(final Annotation[] markers,
final Annotation marker,
final String sourceName,
final Class rawType,
final Type type, final boolean encoded, final String defaultValue) {
super(markers, marker, Source.BEAN_PARAM, sourceName, rawType, type, encoded, defaultValue);
final Collection parameters = new LinkedList<>();
for (Field field : AccessController.doPrivileged(ReflectionHelper.getDeclaredFieldsPA(rawType))) {
if (field.getDeclaredAnnotations().length > 0) {
Parameter beanParamParameter = Parameter.create(
rawType,
field.getDeclaringClass(),
field.isAnnotationPresent(Encoded.class),
field.getType(),
field.getGenericType(),
field.getAnnotations());
parameters.add(beanParamParameter);
}
}
for (Constructor constructor : AccessController
.doPrivileged(ReflectionHelper.getDeclaredConstructorsPA(rawType))) {
for (Parameter parameter : Parameter.create(rawType, rawType, constructor, false)) {
parameters.add(parameter);
}
}
this.parameters = Collections.unmodifiableCollection(parameters);
}
/**
* @return The transitively associated parameters through this {@link BeanParam} parameter.
*/
public Collection getParameters() {
return parameters;
}
}
/**
* Get the parameter source annotation.
*
* @return parameter source annotation.
*/
public Annotation getSourceAnnotation() {
return sourceAnnotation;
}
/**
* Get the parameter value source type.
*
* @return parameter value source type.
*/
public Parameter.Source getSource() {
return source;
}
/**
* Get the parameter source name, i.e. value of the parameter source annotation.
*
* @return parameter source name.
*/
public String getSourceName() {
return sourceName;
}
/**
* If {@code true}, the injected parameter value should remain encoded.
*
* @return {@code true} if the parameter value should remain encoded,
* {@code false} otherwise.
*/
public boolean isEncoded() {
return encoded;
}
/**
* Check if the parameter has a default value set.
*
* @return {@code true} if the default parameter value has been set,
* {@code false} otherwise.
*/
public boolean hasDefaultValue() {
return defaultValue != null;
}
/**
* Get the default parameter value.
*
* @return default parameter value or {@code null} if no default value has
* been set for the parameter.
*/
public String getDefaultValue() {
return defaultValue;
}
/**
* Get raw type information for the parameter.
*
* @return raw parameter type information.
*/
public Class getRawType() {
return rawType;
}
/**
* Get generic type information for the parameter.
*
* @return generic parameter type information.
*/
public Type getType() {
return type;
}
/**
* Check if the parameter is {@link ParamQualifier qualified}.
*
* @return {@code true} if the parameter is qualified, {@code false} otherwise.
*/
public boolean isQualified() {
for (Annotation a : getAnnotations()) {
if (a.annotationType().isAnnotationPresent(ParamQualifier.class)) {
return true;
}
}
return false;
}
@Override
public boolean isAnnotationPresent(Class annotationClass) {
return getAnnotation(annotationClass) != null;
}
@Override
public T getAnnotation(Class annotationClass) {
if (annotationClass == null) {
return null;
}
for (Annotation a : annotations) {
if (a.annotationType() == annotationClass) {
return annotationClass.cast(a);
}
}
return null;
}
@Override
public Annotation[] getAnnotations() {
return annotations.clone();
}
@Override
public Annotation[] getDeclaredAnnotations() {
return annotations.clone();
}
@Override
public String toString() {
return String.format("Parameter [type=%s, source=%s, defaultValue=%s]",
getRawType(), getSourceName(), getDefaultValue());
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Parameter parameter = (Parameter) o;
if (encoded != parameter.encoded) {
return false;
}
if (!Arrays.equals(annotations, parameter.annotations)) {
return false;
}
if (defaultValue != null ? !defaultValue.equals(parameter.defaultValue) : parameter.defaultValue != null) {
return false;
}
if (rawType != null ? !rawType.equals(parameter.rawType) : parameter.rawType != null) {
return false;
}
if (source != parameter.source) {
return false;
}
if (sourceAnnotation != null ? !sourceAnnotation.equals(parameter.sourceAnnotation) : parameter.sourceAnnotation
!= null) {
return false;
}
if (sourceName != null ? !sourceName.equals(parameter.sourceName) : parameter.sourceName != null) {
return false;
}
if (type != null ? !type.equals(parameter.type) : parameter.type != null) {
return false;
}
return true;
}
@Override
public int hashCode() {
int result = annotations != null ? Arrays.hashCode(annotations) : 0;
result = 31 * result + (sourceAnnotation != null ? sourceAnnotation.hashCode() : 0);
result = 31 * result + (source != null ? source.hashCode() : 0);
result = 31 * result + (sourceName != null ? sourceName.hashCode() : 0);
result = 31 * result + (encoded ? 1 : 0);
result = 31 * result + (defaultValue != null ? defaultValue.hashCode() : 0);
result = 31 * result + (rawType != null ? rawType.hashCode() : 0);
result = 31 * result + (type != null ? type.hashCode() : 0);
return result;
}
}