/**
* 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.params;
import java.lang.annotation.Annotation;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.logging.Logger;
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.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Cookie;
import javax.ws.rs.core.PathSegment;
import javax.ws.rs.core.Response.ResponseBuilder;
import javax.ws.rs.core.UriInfo;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.reflect.ConstructorUtils;
import org.apache.commons.lang.reflect.MethodUtils;
import org.restlet.data.Form;
import org.restlet.data.Header;
import org.restlet.data.Parameter;
import org.restlet.data.Reference;
import org.restlet.engine.converter.ConverterHelper;
import org.restlet.engine.converter.ConverterUtils;
import org.restlet.engine.resource.VariantInfo;
import org.restlet.ext.jaxrs.internal.core.CallContext;
import org.restlet.ext.jaxrs.internal.core.PathSegmentImpl;
import org.restlet.ext.jaxrs.internal.core.ThreadLocalizedContext;
import org.restlet.ext.jaxrs.internal.core.ThreadLocalizedUriInfo;
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.ConvertParameterException;
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.IllegalPathParamTypeException;
import org.restlet.ext.jaxrs.internal.exceptions.IllegalTypeException;
import org.restlet.ext.jaxrs.internal.exceptions.MissingAnnotationException;
import org.restlet.ext.jaxrs.internal.todo.NotYetImplementedException;
import org.restlet.ext.jaxrs.internal.util.Converter;
import org.restlet.ext.jaxrs.internal.util.Util;
import org.restlet.ext.jaxrs.internal.wrappers.WrapperUtil;
import org.restlet.ext.jaxrs.internal.wrappers.provider.ExtensionBackwardMapping;
import org.restlet.ext.jaxrs.internal.wrappers.provider.JaxRsProviders;
import org.restlet.representation.Representation;
import org.restlet.representation.StringRepresentation;
import org.restlet.util.Series;
/**
* Contains a list of parameters for JAX-RS constructors, (sub) resource methods
* and sub resource locators.
*
* @author Stephan Koops
*/
public class ParameterList {
/**
* Abstract super class for access to @*Param.
*/
abstract static class AbstractParamGetter implements ParamGetter {
/**
* The type of the collection. null, if this parameter do not represent
* a collection.
*/
protected final Class<Collection<?>> collType;
/**
* The class to convert to. If this object getter represents an *Param
* annotated parameter, and it should be to an array or collection of
* something, this value contains not the collection/array type, but the
* generic type of it.
*/
protected final Class<?> convertTo;
/**
* The default value for this parameter (if given)
*/
protected final DefaultValue defaultValue;
/**
* True, if this parameter should be an array, otherwise false. If true,
* the {@link #collType} must be set to a {@link List}.
*/
protected final boolean isArray;
protected final ThreadLocalizedContext tlContext;
@SuppressWarnings({ "unchecked", "rawtypes" })
AbstractParamGetter(DefaultValue defaultValue, Class<?> convToCl,
Type convToGen, ThreadLocalizedContext tlContext) {
this.tlContext = tlContext;
this.defaultValue = defaultValue;
if (convToCl.isArray()) {
this.convertTo = convToCl.getComponentType();
this.collType = (Class) ArrayList.class;
this.isArray = true;
} else if (convToGen instanceof ParameterizedType) {
final ParameterizedType parametrizedType = (ParameterizedType) convToGen;
final Type[] argTypes = parametrizedType
.getActualTypeArguments();
if (argTypes[0] instanceof Class) {
this.convertTo = (Class<?>) argTypes[0];
} else {
throw new NotYetImplementedException(
"Sorry, only Class is supported, but is "
+ argTypes[0]);
}
// TEST @*Param with array/collection and generic parameter
this.collType = collType(parametrizedType);
this.isArray = false;
} else {
this.convertTo = convToCl;
this.collType = null;
this.isArray = false;
}
}
protected Object convertParamValue(String firstHeader)
throws ConvertParameterException {
return convertParamValue(firstHeader, this.defaultValue);
}
/**
* Converts the given paramValue (found in the path, query, matrix or
* header) into the given paramClass.
*
* @param paramValue
* @param defaultValue
* @return
* @throws ConvertParameterException
* @see PathParam
* @see MatrixParam
* @see QueryParam
* @see HeaderParam
* @see CookieParam
*/
protected Object convertParamValue(String paramValue,
DefaultValue defaultValue) throws ConvertParameterException {
if (decoding() && (paramValue != null)) {
paramValue = Reference.decode(paramValue);
} else if ((paramValue == null) && (defaultValue != null)) {
paramValue = defaultValue.value();
}
if (this.convertTo.equals(String.class)) {
return paramValue;
}
if (this.convertTo.isPrimitive()) {
if ((paramValue != null) && (paramValue.length() <= 0)) {
paramValue = defaultValue.value();
}
return getParamValueForPrimitive(paramValue);
}
return convertParamValueInner(paramValue, defaultValue);
}
/**
* Converts the given value without any decoding.
*
* @param paramValue
* @param defaultValue
* @return
* @throws ConvertParameterException
* @throws WebApplicationException
* if the conversion method throws an
* WebApplicationException.
*/
private Object convertParamValueInner(String paramValue,
DefaultValue defaultValue) throws ConvertParameterException,
WebApplicationException {
Object convertWithConverterUtils = convertWithConverterUtils(paramValue);
if (convertWithConverterUtils != null) {
return convertWithConverterUtils;
}
String value = paramValue;
if (StringUtils.isEmpty(paramValue)) {
if (defaultValue == null || defaultValue.value() == null) {
return null;
}
value = defaultValue.value();
}
try {
return ConstructorUtils.invokeConstructor(convertTo, value);
} catch (Exception e) {
handleExceptionOnInvocation(value, e);
}
// fixes for:
// https://github.com/restlet/restlet-framework-java/issues/645
try {
return MethodUtils.invokeStaticMethod(convertTo,
convertTo.isEnum() ? "fromString" : "valueOf", value);
} catch (Exception e) {
handleExceptionOnInvocation(value, e);
}
try {
return MethodUtils.invokeStaticMethod(convertTo,
convertTo.isEnum() ? "valueOf" : "fromString", value);
} catch (Exception e) {
handleExceptionOnInvocation(value, e);
}
throw ConvertParameterException
.object(this.convertTo,
value,
new Exception(
"Target object has no String constructor, valueOf or fromString method."));
}
protected Object convertParamValues(Iterator<String> paramValueIter)
throws ConvertParameterException {
final Collection<Object> coll = createColl();
while (paramValueIter.hasNext()) {
final String queryParamValue = paramValueIter.next();
final Object convertedValue = convertParamValue(
queryParamValue, null);
if (convertedValue != null) {
coll.add(convertedValue);
}
}
if (coll.isEmpty()) {
coll.add(convertParamValue(null));
}
if (this.isArray) {
return Util.toArray(coll, this.convertTo);
}
return unmodifiable(coll);
}
private Object convertWithConverterUtils(String paramValue) {
Object result = null;
if (this.tlContext.get().getRequest().getEntity() != null
&& paramValue != null) {
try {
ConverterHelper converterHelper = ConverterUtils
.getBestHelper(this.tlContext.get().getRequest()
.getEntity(), this.convertTo, null);
List<VariantInfo> variants = converterHelper
.getVariants(this.convertTo);
for (int i = 0; result == null && i < variants.size(); i++) {
result = converterHelper.toObject(
new StringRepresentation(paramValue, variants
.get(i).getMediaType()),
this.convertTo, null);
}
} catch (Exception exception) {
// -- don't worry about it...proceed with reflective calls
}
}
return result;
}
/**
* @return an new created instance of {@link #collType}. Returns null,
* if collType is null.
*/
@SuppressWarnings("unchecked")
protected <A> Collection<A> createColl() {
try {
if (this.collType != null) {
return (Collection<A>) this.collType.newInstance();
}
return null;
} catch (Exception e) {
throw new RuntimeException(
"Could not instantiate the collection type "
+ this.collType, e);
}
}
protected abstract boolean decoding();
/**
* @return the concrete value of this parameter for the current request.
*/
public abstract Object getParamValue();
protected Object getParamValueForPrimitive(String paramValue)
throws ConvertParameterException {
try {
if (this.convertTo == Integer.TYPE) {
if (((paramValue == null) || (paramValue.length() <= 0))) {
return DEFAULT_INT;
}
return new Integer(paramValue);
}
if (this.convertTo == Double.TYPE) {
if (((paramValue == null) || (paramValue.length() <= 0))) {
return DEFAULT_DOUBLE;
}
return new Double(paramValue);
}
if (this.convertTo == Float.TYPE) {
if (((paramValue == null) || (paramValue.length() <= 0))) {
return DEFAULT_FLOAT;
}
return new Float(paramValue);
}
if (this.convertTo == Byte.TYPE) {
if (((paramValue == null) || (paramValue.length() <= 0))) {
return DEFAULT_BYTE;
}
return new Byte(paramValue);
}
if (this.convertTo == Long.TYPE) {
if (((paramValue == null) || (paramValue.length() <= 0))) {
return DEFAULT_LONG;
}
return new Long(paramValue);
}
if (this.convertTo == Short.TYPE) {
if (((paramValue == null) || (paramValue.length() <= 0))) {
return DEFAULT_SHORT;
}
return new Short(paramValue);
}
if (this.convertTo == Character.TYPE) {
if (((paramValue == null) || (paramValue.length() <= 0))) {
return DEFAULT_CHAR;
}
if (paramValue.length() == 1) {
return paramValue.charAt(0);
}
throw ConvertParameterException.primitive(this.convertTo,
paramValue, null);
}
if (this.convertTo == Boolean.TYPE) {
if (((paramValue == null) || (paramValue.length() <= 0))) {
return DEFAULT_BOOLEAN;
}
if (paramValue.equalsIgnoreCase("true")) {
return Boolean.TRUE;
}
if (paramValue.equalsIgnoreCase("false")) {
return Boolean.FALSE;
}
throw ConvertParameterException.primitive(this.convertTo,
paramValue, null);
}
} catch (IllegalArgumentException e) {
throw ConvertParameterException.primitive(this.convertTo,
paramValue, e);
}
String warning;
if (this.convertTo == Void.TYPE) {
warning = "an object should be converted to a void; but this could not be here";
} else {
warning = "an object should be converted to a "
+ this.convertTo
+ ", but here are only primitives allowed.";
}
localLogger.warning(warning);
final ResponseBuilder rb = javax.ws.rs.core.Response.serverError();
rb.entity(warning);
throw new WebApplicationException(rb.build());
}
public Object getValue() {
return getParamValue();
}
private void handleExceptionOnInvocation(String value, Exception e)
throws ConvertParameterException {
final Throwable cause = e.getCause();
if (e instanceof WebApplicationException
|| cause instanceof WebApplicationException) {
throw (WebApplicationException) cause;
// swallow the typical invocation exceptions, convert real
// exceptions to ConvertParameterException
} else if (!(e instanceof NoSuchMethodException)
&& !(e instanceof IllegalAccessException)
&& !(e instanceof InvocationTargetException)
&& !(e instanceof InstantiationException)) {
throw ConvertParameterException
.object(this.convertTo, value, e);
}
}
protected <A> Collection<A> unmodifiable(Collection<A> coll) {
if (coll instanceof List<?>)
return Collections.unmodifiableList((List<A>) coll);
if (coll instanceof SortedSet<?>)
return Collections.unmodifiableSortedSet((SortedSet<A>) coll);
if (coll instanceof Set<?>)
return Collections.unmodifiableSet((Set<A>) coll);
return Collections.unmodifiableCollection(coll);
}
}
static class CookieParamGetter extends NoEncParamGetter {
private final CookieParam cookieParam;
/**
* @param annoSaysLeaveClassEncoded
* to check if the annotation is available, but should not
* be.
*/
CookieParamGetter(CookieParam cookieParam, DefaultValue defaultValue,
Class<?> convToCl, Type convToGen,
ThreadLocalizedContext tlContext,
boolean annoSaysLeaveClassEncoded) {
super(defaultValue, convToCl, convToGen, tlContext,
annoSaysLeaveClassEncoded);
this.cookieParam = cookieParam;
}
@Override
@SuppressWarnings({ "unchecked", "rawtypes" })
public Object getParamValue() {
String cookieName = this.cookieParam.value();
Series<org.restlet.data.Cookie> cookies;
cookies = this.tlContext.get().getRequest().getCookies();
if (this.convertTo.equals(Cookie.class)) {
Collection<Cookie> coll = createColl();
for (org.restlet.data.Cookie rc : cookies) {
if (!rc.getName().equals(cookieName)) {
continue;
}
Cookie cookie = Converter.toJaxRsCookie(rc);
if (coll == null) {
return cookie;
}
coll.add(cookie);
}
if (coll == null) {
return null;
}
if (coll.isEmpty()) {
String value = this.defaultValue.value();
coll.add(new Cookie(cookieName, value));
}
if (this.isArray) {
return Util.toArray(coll, Cookie.class);
}
return coll;
}
try {
if (this.collType == null) { // no collection parameter
String firstCookieValue = WrapperUtil.getValue(cookies
.getFirst(cookieName));
return convertParamValue(firstCookieValue);
}
return convertParamValues(new NamedValuesIter(
(Series) cookies.subList(cookieName)));
} catch (ConvertParameterException e) {
throw new ConvertCookieParamException(e);
}
}
}
/**
* Abstract super class for access to the entity or to @*Param where
* encoded is allowed (@{@link PathParam}, @{@link MatrixParam} and
* @{@link QueryParam}).
*/
abstract static class EncParamGetter extends AbstractParamGetter {
private final boolean decoding;
EncParamGetter(DefaultValue defaultValue, Class<?> convToCl,
Type convToGen, ThreadLocalizedContext tlContext,
boolean leaveEncoded) {
super(defaultValue, convToCl, convToGen, tlContext);
this.decoding = !leaveEncoded;
}
@Override
protected boolean decoding() {
return this.decoding;
}
}
static abstract class FormOrQueryParamGetter extends EncParamGetter {
FormOrQueryParamGetter(DefaultValue defaultValue, Class<?> convToCl,
Type convToGen, ThreadLocalizedContext tlContext,
boolean leaveEncoded) {
super(defaultValue, convToCl, convToGen, tlContext, leaveEncoded);
}
/**
* @param params
* @param paramName
* @return
* @throws ConvertQueryParamException
*/
Object getParamValue(final Series<Parameter> params,
final String paramName) throws ConvertParameterException {
Series<Parameter> parameters = params.subList(paramName);
if (this.collType == null) { // no collection parameter
Parameter firstFormParam = params.getFirst(paramName);
String queryParamValue = WrapperUtil.getValue(firstFormParam);
return convertParamValue(queryParamValue);
}
NamedValuesIter queryParamValueIter;
queryParamValueIter = new NamedValuesIter(parameters);
return convertParamValues(queryParamValueIter);
}
}
static class FormParamGetter extends FormOrQueryParamGetter {
private static Form form;
private final FormParam formParam;
FormParamGetter(FormParam formParam, DefaultValue defaultValue,
Class<?> convToCl, Type convToGen,
ThreadLocalizedContext tlContext, boolean leaveEncoded) {
super(defaultValue, convToCl, convToGen, tlContext, leaveEncoded);
this.formParam = formParam;
}
@Override
public Object getParamValue() {
Representation entity = this.tlContext.get().getRequest()
.getEntity();
if (entity != null && entity.isAvailable()) {
form = new Form(entity, false);
}
final String paramName = this.formParam.value();
try {
return super.getParamValue(form, paramName);
} catch (ConvertParameterException e) {
throw new ConvertQueryParamException(e);
}
}
}
static class HeaderParamGetter extends NoEncParamGetter {
private final HeaderParam headerParam;
/**
* @param annoSaysLeaveClassEncoded
* to check if the annotation is available.
*/
HeaderParamGetter(HeaderParam headerParam, DefaultValue defaultValue,
Class<?> convToCl, Type paramGenericType,
ThreadLocalizedContext tlContext,
boolean annoSaysLeaveClassEncoded) {
super(defaultValue, convToCl, paramGenericType, tlContext,
annoSaysLeaveClassEncoded);
this.headerParam = headerParam;
}
@Override
public Object getParamValue() {
Series<Header> httpHeaders = Util.getHttpHeaders(this.tlContext
.get().getRequest());
String headerName = this.headerParam.value();
try {
if (this.collType == null) { // no collection parameter
final String firstHeader = WrapperUtil.getValue(httpHeaders
.getFirst(headerName, true));
return convertParamValue(firstHeader);
}
return convertParamValues(new NamedValuesIter(
httpHeaders.subList(headerName, true)));
} catch (ConvertParameterException e) {
throw new ConvertHeaderParamException(e);
}
}
}
static class MatrixParamGetter extends EncParamGetter {
private final MatrixParam matrixParam;
MatrixParamGetter(MatrixParam matrixParam, DefaultValue defaultValue,
Class<?> convToCl, Type convToGen,
ThreadLocalizedContext tlContext, boolean leaveEncoded) {
super(defaultValue, convToCl, convToGen, tlContext, leaveEncoded);
this.matrixParam = matrixParam;
}
@Override
public Object getParamValue() {
final CallContext callContext = this.tlContext.get();
try {
if (this.collType == null) { // no collection parameter
final String matrixParamValue = callContext
.getLastMatrixParamEnc(this.matrixParam);
return convertParamValue(matrixParamValue);
}
Iterator<String> matrixParamValues;
matrixParamValues = callContext
.matrixParamEncIter(this.matrixParam);
return convertParamValues(matrixParamValues);
} catch (ConvertParameterException e) {
throw new ConvertMatrixParamException(e);
}
}
}
/**
* Abstract super class for access to the entity or to @*Param where
* encoded is allowed (@{@link PathParam}, @{@link MatrixParam} and
* @{@link QueryParam}).
*/
abstract static class NoEncParamGetter extends AbstractParamGetter {
/**
* @param annoSaysLeaveEncoded
* to check if the annotation is available.
*/
NoEncParamGetter(DefaultValue defaultValue, Class<?> convToCl,
Type convToGen, ThreadLocalizedContext tlContext,
boolean annoSaysLeaveEncoded) {
super(defaultValue, convToCl, convToGen, tlContext);
checkForEncodedAnno(annoSaysLeaveEncoded);
}
/**
* Checks if the annotation @{@link Encoded} is available on the
* given field or bean setter. If yes, a warning is logged.
*/
void checkForEncodedAnno(AccessibleObject fieldOrBeanSetter) {
checkForEncodedAnno(fieldOrBeanSetter
.isAnnotationPresent(Encoded.class));
}
/**
* Checks if the annotation @{@link Encoded} is available on the
* given field or bean setter. If yes, this method logs a warning.
*/
void checkForEncodedAnno(boolean annoSaysLeaveEncoded) {
if (annoSaysLeaveEncoded) {
localLogger
.warning("You should not use @Encoded on a @HeaderParam or @CookieParam. Will ignore it");
}
}
@Override
protected boolean decoding() {
return false;
}
}
static interface ParamGetter {
/**
* Returns the value for this param.
*
* @return the value for this param.
* @throws InvocationTargetException
* @throws ConvertRepresentationException
* @throws WebApplicationException
*/
public Object getValue() throws InvocationTargetException,
ConvertRepresentationException, WebApplicationException;
}
static class PathParamGetter extends EncParamGetter {
private final PathParam pathParam;
PathParamGetter(PathParam pathParam, DefaultValue defaultValue,
Class<?> convToCl, Type convToGen,
ThreadLocalizedContext tlContext, boolean leaveEncoded)
throws IllegalPathParamTypeException {
super(defaultValue, convToCl, convToGen, tlContext, leaveEncoded);
if ((this.collType != null)
&& (!this.convertTo.equals(PathSegment.class))) {
throw new IllegalPathParamTypeException(
"The type of a @PathParam annotated parameter etc. must not be a collection type or array, if the type parameter is not PathSegment");
}
this.pathParam = pathParam;
}
/**
* Creates a {@link PathSegment}.
*
* @param pathSegmentEnc
* @return
* @throws IllegalArgumentException
*/
private PathSegment createPathSegment(final String pathSegmentEnc)
throws IllegalArgumentException {
return new PathSegmentImpl(pathSegmentEnc, this.decoding(), -1);
}
@Override
public Object getParamValue() {
final CallContext callContext = this.tlContext.get();
if (this.convertTo.equals(PathSegment.class)) {
if (this.collType == null) { // no collection parameter
final String pathSegmentEnc = callContext
.getLastPathSegmentEnc(this.pathParam);
return createPathSegment(pathSegmentEnc);
}
final Iterator<String> pathSegmentEncIter;
pathSegmentEncIter = callContext
.pathSegementEncIter(this.pathParam);
final Collection<Object> coll = createColl();
while (pathSegmentEncIter.hasNext()) {
final String pathSegmentEnc = pathSegmentEncIter.next();
coll.add(createPathSegment(pathSegmentEnc));
}
if (this.isArray) {
return Util.toArray(coll, this.convertTo);
}
return unmodifiable(coll);
}
try {
final String pathParamValue;
pathParamValue = callContext.getLastPathParamEnc(pathParam);
return convertParamValue(pathParamValue);
} catch (ConvertParameterException e) {
throw new ConvertPathParamException(e);
}
}
}
static class QueryParamGetter extends FormOrQueryParamGetter {
private final QueryParam queryParam;
QueryParamGetter(QueryParam queryParam, DefaultValue defaultValue,
Class<?> convToCl, Type convToGen,
ThreadLocalizedContext tlContext, boolean leaveEncoded) {
super(defaultValue, convToCl, convToGen, tlContext, leaveEncoded);
this.queryParam = queryParam;
}
@Override
public Object getParamValue() {
final Reference resourceRef = this.tlContext.get().getRequest()
.getResourceRef();
final String queryString = resourceRef.getQuery();
final Form form = Converter.toFormEncoded(queryString);
final String paramName = this.queryParam.value();
try {
return super.getParamValue(form, paramName);
} catch (ConvertParameterException e) {
throw new ConvertQueryParamException(e);
}
}
}
/**
* @author Stephan Koops
*/
private static class UriInfoGetter implements ParamGetter {
private final boolean availableMandatory;
private final ThreadLocalizedUriInfo uriInfo;
private UriInfoGetter(ThreadLocalizedContext tlContext,
boolean availableMandatory) {
this.uriInfo = new ThreadLocalizedUriInfo(tlContext);
this.availableMandatory = availableMandatory;
}
public Object getValue() throws InvocationTargetException,
ConvertRepresentationException, WebApplicationException {
this.uriInfo.saveStateForCurrentThread(this.availableMandatory);
return this.uriInfo;
}
}
private static final String COLL_PARAM_NOT_DEFAULT = "The collection type Collection is not supported for parameters. Use List, Set or SortedSet";
private static final Boolean DEFAULT_BOOLEAN = Boolean.FALSE;
private static final Byte DEFAULT_BYTE = (byte) 0;
private static final Character DEFAULT_CHAR = new Character('\0');
private static final Double DEFAULT_DOUBLE = 0d;
private static final Float DEFAULT_FLOAT = 0.0f;
private static final Integer DEFAULT_INT = 0;
private static final Long DEFAULT_LONG = new Long(0);
private static final Short DEFAULT_SHORT = 0;
private static final Logger localLogger = org.restlet.Context
.getCurrentLogger();
private static final Collection<Class<? extends Annotation>> VALID_ANNOTATIONS = createValidAnnotations();
/**
* @return the collection type for the given {@link ParameterizedType
* parametrized Type}.<br>
* If the given type do not represent an collection, null is
* returned.
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
private static Class<Collection<?>> collType(ParameterizedType type) {
final Type rawType = type.getRawType();
if (rawType.equals(List.class)) {
return (Class) ArrayList.class;
} else if (rawType.equals(Set.class)) {
return (Class) HashSet.class;
} else if (rawType.equals(SortedSet.class)) {
return (Class) TreeSet.class;
} else if (rawType.equals(Collection.class)) {
localLogger.config(ParameterList.COLL_PARAM_NOT_DEFAULT);
return (Class) ArrayList.class;
}
return null;
}
static Collection<Class<? extends Annotation>> createValidAnnotations() {
return Arrays.asList(Context.class, HeaderParam.class,
MatrixParam.class, QueryParam.class, PathParam.class,
CookieParam.class);
}
/**
* Returns the given annotation, if it is available in the given array of
* annotations.
*/
@SuppressWarnings("unchecked")
static <A extends Annotation> A getAnno(Annotation[] annotations,
Class<A> annoType) {
for (final Annotation annot : annotations) {
final Class<? extends Annotation> annotationType = annot
.annotationType();
if (annotationType.equals(annoType)) {
return (A) annot;
}
}
return null;
}
/**
* Returns true, if one of the annotations is @{@link Encoded}
*/
static boolean getLeaveEncoded(Annotation[] annotations) {
for (final Annotation annot : annotations) {
final Class<? extends Annotation> annotationType = annot
.annotationType();
if (annotationType.equals(Encoded.class)) {
return true;
}
}
return false;
}
/**
* must call the {@link EntityGetter} first, if @{@link FormParam} is
* used. A value less than zero means, that no special handling is needed.
*/
private final int entityPosition;
/** shortcut for {@link #parameters}.length */
private final int paramCount;
/** @see #paramCount */
private final ParamGetter[] parameters;
/**
* @param parameterTypes
* @param genParamTypes
* @param paramAnnoss
* @param tlContext
* @param leaveAllEncoded
* @param jaxRsProviders
* @param extensionBackwardMapping
* @param paramsAllowed
* true, if @*Params are allowed as parameter, otherwise
* false.
* @param entityAllowed
* true, if the entity is allowed as parameter, otherwise false.
* @param logger
* @param allMustBeAvailable
* if true, all values must be available (for singeltons creation
* it must be false)
* @throws MissingAnnotationException
* @throws IllegalTypeException
* if the given class is not valid to be annotated with @
* {@link Context}.
* @throws IllegalPathParamTypeException
*/
private ParameterList(Class<?>[] parameterTypes, Type[] genParamTypes,
Annotation[][] paramAnnoss, ThreadLocalizedContext tlContext,
boolean leaveAllEncoded, JaxRsProviders jaxRsProviders,
ExtensionBackwardMapping extensionBackwardMapping,
boolean paramsAllowed, boolean entityAllowed, Logger logger,
boolean allMustBeAvailable) throws MissingAnnotationException,
IllegalTypeException, IllegalPathParamTypeException {
this.paramCount = parameterTypes.length;
this.parameters = new ParamGetter[this.paramCount];
boolean entityAlreadyRead = false;
int entityPosition = -1;
for (int i = 0; i < this.paramCount; i++) {
final Class<?> parameterType = parameterTypes[i];
final Type genParamType = genParamTypes[i];
final Annotation[] paramAnnos = paramAnnoss[i];
final Context conntextAnno = getAnno(paramAnnos, Context.class);
if (conntextAnno != null) {
if (parameterType.equals(UriInfo.class)) {
this.parameters[i] = new UriInfoGetter(tlContext,
allMustBeAvailable);
} else {
this.parameters[i] = new ContextHolder(
ContextInjector.getInjectObject(parameterType,
tlContext, jaxRsProviders,
extensionBackwardMapping));
}
continue;
}
if (paramsAllowed) {
final boolean leaveThisEncoded = getLeaveEncoded(paramAnnos);
final DefaultValue defValue = getAnno(paramAnnos,
DefaultValue.class);
final CookieParam cookieParam = getAnno(paramAnnos,
CookieParam.class);
final HeaderParam headerParam = getAnno(paramAnnos,
HeaderParam.class);
final MatrixParam matrixParam = getAnno(paramAnnos,
MatrixParam.class);
final PathParam pathParam = getAnno(paramAnnos, PathParam.class);
final QueryParam queryParam = getAnno(paramAnnos,
QueryParam.class);
final FormParam formParam = getAnno(paramAnnos, FormParam.class);
if (pathParam != null) {
this.parameters[i] = new PathParamGetter(pathParam,
defValue, parameterType, genParamType, tlContext,
leaveAllEncoded || leaveThisEncoded);
continue;
} else if (cookieParam != null) {
this.parameters[i] = new CookieParamGetter(cookieParam,
defValue, parameterType, genParamType, tlContext,
leaveThisEncoded);
continue;
} else if (headerParam != null) {
this.parameters[i] = new HeaderParamGetter(headerParam,
defValue, parameterType, genParamType, tlContext,
leaveThisEncoded);
continue;
} else if (matrixParam != null) {
this.parameters[i] = new MatrixParamGetter(matrixParam,
defValue, parameterType, genParamType, tlContext,
leaveAllEncoded || leaveThisEncoded);
continue;
} else if (queryParam != null) {
this.parameters[i] = new QueryParamGetter(queryParam,
defValue, parameterType, genParamType, tlContext,
leaveAllEncoded || leaveThisEncoded);
continue;
} else if (formParam != null) {
this.parameters[i] = new FormParamGetter(formParam,
defValue, parameterType, genParamType, tlContext,
leaveAllEncoded || leaveThisEncoded);
continue;
}
}
// could only be the entity here
if (!entityAllowed) {
throw new MissingAnnotationException(
"All parameters requires one of the following annotations: "
+ VALID_ANNOTATIONS);
}
if (entityAlreadyRead) {
throw new MissingAnnotationException(
"The entity is already read. The " + i
+ ". parameter requires one of "
+ "the following annotations: "
+ VALID_ANNOTATIONS);
}
if (Representation.class.isAssignableFrom(parameterType)) {
this.parameters[i] = ReprEntityGetter.create(parameterType,
genParamType, logger);
}
if (this.parameters[i] == null) {
this.parameters[i] = new EntityGetter(parameterType,
genParamType, tlContext, jaxRsProviders, paramAnnos);
}
entityPosition = i;
entityAlreadyRead = true;
}
this.entityPosition = entityPosition;
}
/**
* @param constr
* @param tlContext
* @param leaveEncoded
* @param jaxRsProviders
* @param extensionBackwardMapping
* @param paramsAllowed
* @param logger
* @param allMustBeAvailable
* @throws MissingAnnotationException
* @throws IllegalTypeException
* if one of the parameters contains a @{@link Context} on
* an type that must not be annotated with @{@link Context}.
* @throws IllegalPathParamTypeException
*/
public ParameterList(Constructor<?> constr,
ThreadLocalizedContext tlContext, boolean leaveEncoded,
JaxRsProviders jaxRsProviders,
ExtensionBackwardMapping extensionBackwardMapping,
boolean paramsAllowed, Logger logger, boolean allMustBeAvailable)
throws MissingAnnotationException, IllegalTypeException,
IllegalPathParamTypeException {
this(constr.getParameterTypes(), constr.getGenericParameterTypes(),
constr.getParameterAnnotations(), tlContext, leaveEncoded,
jaxRsProviders, extensionBackwardMapping, paramsAllowed, false,
logger, allMustBeAvailable);
}
/**
* @param executeMethod
* @param annotatedMethod
* @param tlContext
* @param leaveEncoded
* @param jaxRsProviders
* @param extensionBackwardMapping
* @param entityAllowed
* @param logger
* @throws MissingAnnotationException
* @throws IllegalTypeException
* if one of the parameters contains a @{@link Context} on
* an type that must not be annotated with @{@link Context}.
* @throws IllegalPathParamTypeException
*/
public ParameterList(Method executeMethod, Method annotatedMethod,
ThreadLocalizedContext tlContext, boolean leaveEncoded,
JaxRsProviders jaxRsProviders,
ExtensionBackwardMapping extensionBackwardMapping,
boolean entityAllowed, Logger logger)
throws MissingAnnotationException, IllegalTypeException,
IllegalPathParamTypeException {
this(executeMethod.getParameterTypes(), executeMethod
.getGenericParameterTypes(), annotatedMethod
.getParameterAnnotations(), tlContext, leaveEncoded,
jaxRsProviders, extensionBackwardMapping, true, entityAllowed,
logger, true);
}
/**
* Returns the concrete parameter array for the current request.
*
* @return the concrete parameter array for the current request.
* @throws InvocationTargetException
* @throws ConvertRepresentationException
* @throws WebApplicationException
*/
public Object[] get() throws ConvertRepresentationException,
InvocationTargetException, WebApplicationException {
final Object[] args = new Object[this.parameters.length];
if (this.entityPosition >= 0) {
args[entityPosition] = this.parameters[entityPosition].getValue();
}
for (int i = 0; i < this.paramCount; i++) {
if (i != this.entityPosition) {
args[i] = this.parameters[i].getValue();
}
}
return args;
}
}