package org.jboss.resteasy.core;

import org.jboss.resteasy.annotations.Form;
import org.jboss.resteasy.annotations.Query;
import org.jboss.resteasy.spi.ConstructorInjector;
import org.jboss.resteasy.spi.HttpRequest;
import org.jboss.resteasy.spi.HttpResponse;
import org.jboss.resteasy.spi.InjectorFactory;
import org.jboss.resteasy.spi.MethodInjector;
import org.jboss.resteasy.spi.PropertyInjector;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.jboss.resteasy.spi.ValueInjector;
import org.jboss.resteasy.spi.metadata.Parameter;
import org.jboss.resteasy.spi.metadata.ResourceClass;
import org.jboss.resteasy.spi.metadata.ResourceConstructor;
import org.jboss.resteasy.spi.metadata.ResourceLocator;
import org.jboss.resteasy.spi.util.Types;

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 java.lang.annotation.Annotation;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.OptionalDouble;
import java.util.OptionalInt;
import java.util.OptionalLong;
import java.util.concurrent.CompletionStage;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import static org.jboss.resteasy.spi.util.FindAnnotation.findAnnotation;

Author:Bill Burke
Version:$Revision: 1 $
/** * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @version $Revision: 1 $ */
@SuppressWarnings({"unchecked", "rawtypes"}) public class InjectorFactoryImpl implements InjectorFactory { public static final InjectorFactoryImpl INSTANCE = new InjectorFactoryImpl(); @Override public ConstructorInjector createConstructor(Constructor constructor, ResteasyProviderFactory providerFactory) { return new ConstructorInjectorImpl(constructor, providerFactory); } @Override public ConstructorInjector createConstructor(ResourceConstructor constructor, ResteasyProviderFactory providerFactory) { return new ConstructorInjectorImpl(constructor, providerFactory); } @Override public PropertyInjector createPropertyInjector(Class resourceClass, ResteasyProviderFactory providerFactory) { return new PropertyInjectorImpl(resourceClass, providerFactory); } @Override public PropertyInjector createPropertyInjector(ResourceClass resourceClass, ResteasyProviderFactory providerFactory) { return new ResourcePropertyInjector(resourceClass, providerFactory); } @Override public MethodInjector createMethodInjector(ResourceLocator method, ResteasyProviderFactory factory) { return new MethodInjectorImpl(method, factory); } @Override public ValueInjector createParameterExtractor(Parameter parameter, ResteasyProviderFactory providerFactory) { return OptionalInjections.wrap(parameter.getGenericType(), innerType -> createParameterExtractor0(parameter, providerFactory, innerType)); } private ValueInjector createParameterExtractor0(Parameter parameter, ResteasyProviderFactory providerFactory, Type parameterType) { Class<?> rawType = Types.getRawType(parameterType); switch (parameter.getParamType()) { case QUERY_PARAM: return new QueryParamInjector(rawType, parameterType, parameter.getAccessibleObject(), parameter.getParamName(), parameter.getDefaultValue(), parameter.isEncoded(), parameter.getAnnotations(), providerFactory); case QUERY: return new QueryInjector(rawType, providerFactory); case HEADER_PARAM: return new HeaderParamInjector(rawType, parameterType, parameter.getAccessibleObject(), parameter.getParamName(), parameter.getDefaultValue(), parameter.getAnnotations(), providerFactory); case FORM_PARAM: return new FormParamInjector(rawType, parameterType, parameter.getAccessibleObject(), parameter.getParamName(), parameter.getDefaultValue(), parameter.isEncoded(), parameter.getAnnotations(), providerFactory); case COOKIE_PARAM: return new CookieParamInjector(rawType, parameterType, parameter.getAccessibleObject(), parameter.getParamName(), parameter.getDefaultValue(), parameter.getAnnotations(), providerFactory); case PATH_PARAM: return new PathParamInjector(parameter.getType(), parameter.getGenericType(), parameter.getAccessibleObject(), parameter.getParamName(), parameter.getDefaultValue(), parameter.isEncoded(), parameter.getAnnotations(), providerFactory); case FORM: { String prefix = parameter.getParamName(); if (prefix.length() > 0) { if (parameterType instanceof ParameterizedType) { ParameterizedType pType = (ParameterizedType) parameterType; if (Types.isA(List.class, pType)) { return new ListFormInjector(rawType, Types.getArgumentType(pType, 0), prefix, providerFactory); } if (Types.isA(Map.class, pType)) { return new MapFormInjector(rawType, Types.getArgumentType(pType, 0), Types.getArgumentType(pType, 1), prefix, providerFactory); } } return new PrefixedFormInjector(rawType, prefix, providerFactory); } return new FormInjector(rawType, providerFactory); } case BEAN_PARAM: return new FormInjector(rawType, providerFactory); case MATRIX_PARAM: return new MatrixParamInjector(rawType, parameterType, parameter.getAccessibleObject(), parameter.getParamName(), parameter.getDefaultValue(), parameter.isEncoded(), parameter.getAnnotations(), providerFactory); case CONTEXT: return new ContextParameterInjector(null, rawType, parameterType, parameter.getAnnotations(), providerFactory); case SUSPENDED: return new AsynchronousResponseInjector(); case MESSAGE_BODY: return new MessageBodyParameterInjector(parameter.getResourceClass().getClazz(), parameter.getAccessibleObject(), rawType, parameterType, parameter.getAnnotations(), providerFactory); default: return null; } } @Override public ValueInjector createParameterExtractor(Class injectTargetClass, AccessibleObject injectTarget, String defaultName, Class type, Type genericType, Annotation[] annotations, ResteasyProviderFactory providerFactory) { return createParameterExtractor(injectTargetClass, injectTarget, defaultName, type, genericType, annotations, true, providerFactory); } @Override public ValueInjector createParameterExtractor(Class injectTargetClass, AccessibleObject injectTarget, String defaultName, Class type, Type genericType, Annotation[] annotations, boolean useDefault, ResteasyProviderFactory providerFactory) { return OptionalInjections.wrap(genericType, innerType -> createParameterExtractor0(injectTargetClass, injectTarget, defaultName, genericType.equals(innerType) ? type : Types.getRawType(innerType), innerType, annotations, useDefault, providerFactory)); } private ValueInjector createParameterExtractor0(Class injectTargetClass, AccessibleObject injectTarget, String defaultName, Class type, Type genericType, Annotation[] annotations, boolean useDefault, ResteasyProviderFactory providerFactory) { DefaultValue defaultValue = findAnnotation(annotations, DefaultValue.class); boolean encode = findAnnotation(annotations, Encoded.class) != null || injectTarget.isAnnotationPresent(Encoded.class) || type.isAnnotationPresent(Encoded.class); String defaultVal = null; if (defaultValue != null) defaultVal = defaultValue.value(); QueryParam queryParam; HeaderParam header; MatrixParam matrix; PathParam uriParam; CookieParam cookie; FormParam formParam; Form form; if ((queryParam = findAnnotation(annotations, QueryParam.class)) != null) { return new QueryParamInjector(type, genericType, injectTarget, queryParam.value(), defaultVal, encode, annotations, providerFactory); } else if (findAnnotation(annotations, org.jboss.resteasy.annotations.jaxrs.QueryParam.class) != null) { return new QueryParamInjector(type, genericType, injectTarget, defaultName, defaultVal, encode, annotations, providerFactory); } else if (findAnnotation(annotations, Query.class) != null) { return new QueryInjector(type, providerFactory); } else if ((header = findAnnotation(annotations, HeaderParam.class)) != null) { return new HeaderParamInjector(type, genericType, injectTarget, header.value(), defaultVal, annotations, providerFactory); } else if (findAnnotation(annotations, org.jboss.resteasy.annotations.jaxrs.HeaderParam.class) != null) { return new HeaderParamInjector(type, genericType, injectTarget, defaultName, defaultVal, annotations, providerFactory); } else if ((formParam = findAnnotation(annotations, FormParam.class)) != null) { return new FormParamInjector(type, genericType, injectTarget, formParam.value(), defaultVal, encode, annotations, providerFactory); } else if (findAnnotation(annotations, org.jboss.resteasy.annotations.jaxrs.FormParam.class) != null) { return new FormParamInjector(type, genericType, injectTarget, defaultName, defaultVal, encode, annotations, providerFactory); } else if ((cookie = findAnnotation(annotations, CookieParam.class)) != null) { return new CookieParamInjector(type, genericType, injectTarget, cookie.value(), defaultVal, annotations, providerFactory); } else if (findAnnotation(annotations, org.jboss.resteasy.annotations.jaxrs.CookieParam.class) != null) { return new CookieParamInjector(type, genericType, injectTarget, defaultName, defaultVal, annotations, providerFactory); } else if ((uriParam = findAnnotation(annotations, PathParam.class)) != null) { return new PathParamInjector(type, genericType, injectTarget, uriParam.value(), defaultVal, encode, annotations, providerFactory); } else if (findAnnotation(annotations, org.jboss.resteasy.annotations.jaxrs.PathParam.class) != null) { return new PathParamInjector(type, genericType, injectTarget, defaultName, defaultVal, encode, annotations, providerFactory); } else if ((form = findAnnotation(annotations, Form.class)) != null) { String prefix = form.prefix(); if (prefix.length() > 0) { if (genericType instanceof ParameterizedType) { ParameterizedType pType = (ParameterizedType) genericType; if (Types.isA(List.class, pType)) { return new ListFormInjector(type, Types.getArgumentType(pType, 0), prefix, providerFactory); } if (Types.isA(Map.class, pType)) { return new MapFormInjector(type, Types.getArgumentType(pType, 0), Types.getArgumentType(pType, 1), prefix, providerFactory); } } return new PrefixedFormInjector(type, prefix, providerFactory); } return new FormInjector(type, providerFactory); } else if (findAnnotation(annotations, BeanParam.class) != null) { return new FormInjector(type, providerFactory); } else if ((matrix = findAnnotation(annotations, MatrixParam.class)) != null) { return new MatrixParamInjector(type, genericType, injectTarget, matrix.value(), defaultVal, encode, annotations, providerFactory); } else if (findAnnotation(annotations, org.jboss.resteasy.annotations.jaxrs.MatrixParam.class) != null) { return new MatrixParamInjector(type, genericType, injectTarget, defaultName, defaultVal, encode, annotations, providerFactory); } else if (findAnnotation(annotations, Context.class) != null) { return new ContextParameterInjector(null, type, genericType, annotations, providerFactory); } else if (findAnnotation(annotations, Suspended.class) != null) { return new AsynchronousResponseInjector(); } else if (javax.ws.rs.container.AsyncResponse.class.isAssignableFrom(type)) { return new AsynchronousResponseInjector(); } else if (useDefault) { return new MessageBodyParameterInjector(injectTargetClass, injectTarget, type, genericType, annotations, providerFactory); } else { return null; } } enum OptionalInjections { OPT (Optional.class, OptionalInjections::getTypeArgument, Optional::empty, Optional::of), OINT (OptionalInt.class, x -> Integer.class, OptionalInt::empty, v -> OptionalInt.of((Integer) v)), OLONG (OptionalLong.class, x -> Long.class, OptionalLong::empty, v -> OptionalLong.of((Long) v)), ODOUBLE (OptionalDouble.class, x -> Double.class, OptionalDouble::empty, v -> OptionalDouble.of((Double) v)), ; static Map<Class<?>, OptionalInjections> optionalTypes; static { optionalTypes = Arrays.stream(OptionalInjections.values()) .collect(Collectors.toMap(o -> o.optional, Function.identity())); } final Class<?> optional; final Function<Type, Type> valueType; final Supplier<Object> empty; final Function<Object, Object> present; OptionalInjections(final Class<?> optional, final Function<Type, Type> valueType, final Supplier<Object> empty, final Function<Object, Object> present) { this.optional = optional; this.valueType = valueType; this.empty = empty; this.present = present; } static ValueInjector wrap(Type paramType, Function<Type, ValueInjector> injectorFactory) { return Optional.ofNullable(optionalTypes.get(Types.getRawType(paramType))) .<ValueInjector>map(oi -> { ValueInjector valueInjector = injectorFactory.apply(oi.valueType.apply(paramType)); return valueInjector == null ? null : new DelegatingInjector(oi, valueInjector); }) .orElseGet(() -> injectorFactory.apply(paramType)); } static Type getTypeArgument(Type type) { if (!(type instanceof ParameterizedType)) throw new UnsupportedOperationException("non-parameterized Optional type: " + type); return ((ParameterizedType) type).getActualTypeArguments()[0]; } static class DelegatingInjector implements ValueInjector { private final OptionalInjections oi; private final ValueInjector delegate; DelegatingInjector(final OptionalInjections oi, final ValueInjector delegate) { this.oi = oi; this.delegate = delegate; } @Override public Object inject(boolean unwrapAsync) { Object injectedValue = delegate.inject(unwrapAsync); if (injectedValue != null && injectedValue instanceof CompletionStage) { return ((CompletionStage<Object>)injectedValue).thenApply(this::wrap); } return wrap(CompletionStageHolder.resolve(injectedValue)); } @Override public Object inject(HttpRequest request, HttpResponse response, boolean unwrapAsync) { Object injectedValue = delegate.inject(request, response, unwrapAsync); injectedValue = injectedValue; if (injectedValue != null && injectedValue instanceof CompletionStage) { return ((CompletionStage<Object>)injectedValue).thenApply(this::wrap); } return wrap(CompletionStageHolder.resolve(injectedValue)); } public Object wrap(Object value) { if (value == null) { return oi.empty.get(); } return oi.present.apply(value); } } } }