package com.github.dynamicextensionsalfresco.webscripts.arguments;
import org.osgi.framework.BundleContext;
import org.osgi.util.tracker.ServiceTracker;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.extensions.webscripts.WebScriptRequest;
import org.springframework.extensions.webscripts.WebScriptResponse;
import org.springframework.util.Assert;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Strategy for resolving handler method arguments.
*
* @author Laurens Fridael
* @author Laurent Van der Linden
* @see ArgumentResolver
*/
public class HandlerMethodArgumentsResolver implements ApplicationContextAware {
/* Dependencies */
private StringValueConverter stringValueConverter;
private BundleContext bundleContext;
/* Configuration */
private List<ArgumentResolver<Object, Annotation>> argumentResolvers;
private final Map<ArgumentResolverKey, ArgumentResolver<Object, Annotation>> argumentResolverCache = new ConcurrentHashMap<ArgumentResolverKey, ArgumentResolver<Object, Annotation>>();
private final ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
private ServiceTracker resolverTracker;
private ApplicationContext applicationContext;
/* Main Operations */
@SuppressWarnings({ "rawtypes", "unchecked" })
protected void initializeArgumentResolvers() {
argumentResolvers = new ArrayList<ArgumentResolver<Object, Annotation>>();
argumentResolvers.add((ArgumentResolver) new RequestParamArgumentResolver(getStringValueConverter()));
argumentResolvers.add((ArgumentResolver) new UriVariableArgumentResolver(getStringValueConverter()));
argumentResolvers.add((ArgumentResolver) new AttributeArgumentResolver());
argumentResolvers.add((ArgumentResolver) new ModelArgumentResolver());
argumentResolvers.add((ArgumentResolver) new ExceptionArgumentResolver());
argumentResolvers.add((ArgumentResolver) new HeaderArgumentResolver());
argumentResolvers.add((ArgumentResolver) new ContentArgumentResolver());
argumentResolvers.add((ArgumentResolver) new CommandArgumentResolver());
argumentResolvers.add((ArgumentResolver) new FileFieldArgumentResolver());
argumentResolvers.add((ArgumentResolver) new WebScriptRequestArgumentResolver());
argumentResolvers.add((ArgumentResolver) new WebScriptResponseArgumentResolver());
argumentResolvers.add((ArgumentResolver) new WebScriptSessionArgumentResolver());
argumentResolvers.add((ArgumentResolver) new HttpServletRequestArgumentResolver());
argumentResolvers.add((ArgumentResolver) new HttpServletResponseArgumentResolver());
argumentResolvers.add((ArgumentResolver) new JsonObjectArgumentResolver());
if (bundleContext != null) {
resolverTracker = new ServiceTracker(bundleContext, ArgumentResolver.class, null);
resolverTracker.open(true);
}
}
/**
* Resolves the arguments of the given handler method.
*
* @param method
* The handler method.
* @param handler
* The handler itself.
* @param request
* @param response
* @return The arguments to invoke handlerMethod with.
*/
public Object[] resolveHandlerMethodArguments(Method method, final Object handler, final WebScriptRequest request,
final WebScriptResponse response) {
Assert.notNull(method, "Method cannot be null.");
Assert.notNull(request, "Request cannot be null.");
Assert.notNull(response, "Response cannot be null.");
final Class<?>[] parameterTypes = method.getParameterTypes();
final Object[] arguments = new Object[parameterTypes.length];
if (AopUtils.isAopProxy(handler)) {
method = AopUtils.getMostSpecificMethod(method, AopUtils.getTargetClass(handler));
}
final Annotation[][] parameterAnnotations = method.getParameterAnnotations();
final String[] methodParameterNames = parameterNameDiscoverer.getParameterNames(method);
for (int index = 0; index < parameterTypes.length; index++) {
Annotation mappingAnnotation = null;
ArgumentResolver<Object, Annotation> argumentResolver = null;
if (parameterAnnotations[index].length == 0) {
argumentResolver = getArgumentResolver(parameterTypes[index], null);
} else {
for (final Annotation parameterAnnotation : parameterAnnotations[index]) {
argumentResolver = getArgumentResolver(parameterTypes[index], parameterAnnotation.annotationType());
if (argumentResolver != null) {
mappingAnnotation = parameterAnnotation;
break;
}
}
}
if (argumentResolver == null) {
throw new IllegalStateException(String.format("Cannot map parameter at index %d of method %s.%s.", index,
handler.getClass().getSimpleName(), method.getName()));
}
String name = null;
if (methodParameterNames != null) {
name = methodParameterNames[index];
}
arguments[index] = argumentResolver.resolveArgument(parameterTypes[index], mappingAnnotation, name,
request, response);
}
return arguments;
}
/* Utility Operations */
@SuppressWarnings("unchecked")
protected ArgumentResolver<Object, Annotation> getArgumentResolver(final Class<?> parameterType,
final Class<? extends Annotation> annotationType) {
Assert.notNull(parameterType, "ParameterType cannot be null.");
final ArgumentResolverKey cacheKey = new ArgumentResolverKey(parameterType, annotationType);
final ArgumentResolver<Object, Annotation> match = argumentResolverCache.get(cacheKey);
if (match != null) {
return match;
}
// spring component resolvers
final Map<String, ArgumentResolver> localArgumentResolvers = applicationContext.getBeansOfType(ArgumentResolver.class);
for (ArgumentResolver argumentResolver : localArgumentResolvers.values()) {
if (argumentResolver.supports(parameterType, annotationType)) {
argumentResolverCache.put(cacheKey, argumentResolver);
return argumentResolver;
}
}
// osgi resolvers
if (resolverTracker != null) {
// check for Osgi additions
final Object[] services = resolverTracker.getServices();
if (services != null) {
for (Object service : services) {
final ArgumentResolver<Object,Annotation> argumentResolver = (ArgumentResolver<Object,Annotation>)service;
if (argumentResolver.supports(parameterType, annotationType)) {
// cannot cache these due to dynamic nature
return argumentResolver;
}
}
}
}
// static resolvers
for (final ArgumentResolver<Object, Annotation> argumentResolver : argumentResolvers) {
if (argumentResolver.supports(parameterType, annotationType)) {
argumentResolverCache.put(cacheKey, argumentResolver);
return argumentResolver;
}
}
return null;
}
/* Dependencies */
public void setStringValueConverter(final StringValueConverter stringValueConverter) {
this.stringValueConverter = stringValueConverter;
}
protected StringValueConverter getStringValueConverter() {
return stringValueConverter;
}
public void setBundleContext(BundleContext bundleContext) {
this.bundleContext = bundleContext;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
private static class ArgumentResolverKey {
public final Class parameterType;
public final Class annotationType;
private ArgumentResolverKey(Class parameterType, Class annotationType) {
this.parameterType = parameterType;
this.annotationType = annotationType;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ArgumentResolverKey that = (ArgumentResolverKey) o;
return !(annotationType != null ? !annotationType.equals(that.annotationType) : that.annotationType != null) && parameterType.equals(that.parameterType);
}
@Override
public int hashCode() {
int result = parameterType.hashCode();
result = 31 * result + (annotationType != null ? annotationType.hashCode() : 0);
return result;
}
}
}