/*
* Copyright 2011 cruxframework.org
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.cruxframework.crux.core.server.rest.core.dispatch;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.cruxframework.crux.core.server.rest.core.RequestProcessors;
import org.cruxframework.crux.core.server.rest.spi.BadRequestException;
import org.cruxframework.crux.core.server.rest.spi.HttpRequest;
import org.cruxframework.crux.core.server.rest.spi.HttpResponse;
import org.cruxframework.crux.core.server.rest.spi.InternalServerErrorException;
import org.cruxframework.crux.core.server.rest.spi.RestFailure;
import org.cruxframework.crux.core.shared.rest.annotation.CookieParam;
import org.cruxframework.crux.core.shared.rest.annotation.DefaultValue;
import org.cruxframework.crux.core.shared.rest.annotation.FormParam;
import org.cruxframework.crux.core.shared.rest.annotation.HeaderParam;
import org.cruxframework.crux.core.shared.rest.annotation.PathParam;
import org.cruxframework.crux.core.shared.rest.annotation.QueryParam;
import org.cruxframework.crux.core.utils.ClassUtils;
/**
*
* @author Thiago da Rosa de Bustamante
*
*/
public class MethodInvoker
{
protected static enum RestParameterType{query, header, form, cookie, path, body}
private static final Log logger = LogFactory.getLog(MethodInvoker.class);
protected Method method;
protected Class<?> rootClass;
protected ValueInjector[] params;
protected List<RequestPreprocessor> preprocessors;
protected List<RequestPostprocessor> postprocessors;
private RestErrorHandler restErrorHandler;
public MethodInvoker(Class<?> root, Method method, String httpMethod)
{
this.method = method;
this.rootClass = root;
this.restErrorHandler = RestErrorHandlerFactory.createErrorHandler(method);
this.params = new ValueInjector[method.getParameterTypes().length];
Type[] genericParameterTypes = method.getGenericParameterTypes();
for (int i = 0; i < genericParameterTypes.length; i++)
{
Annotation[] annotations = method.getParameterAnnotations()[i];
Type paramType = ClassUtils.resolveGenericTypeOnMethod(genericParameterTypes[i], rootClass, method);
params[i] = createParameterExtractor(root, paramType, annotations);
}
validateParamExtractors(httpMethod);
initializePreprocessors();
initializePostprocessors();
}
public ValueInjector[] getParams()
{
return params;
}
public Object[] injectArguments(HttpRequest input)
{
try
{
Object[] args = null;
if (params != null && params.length > 0)
{
args = new Object[params.length];
int i = 0;
for (ValueInjector extractor : params)
{
args[i++] = extractor.inject(input);
}
}
return args;
}
catch (RestFailure f)
{
throw f;
}
catch (Exception e)
{
BadRequestException badRequest = new BadRequestException("Failed processing arguments of " + method.toString(), "Can not invoke requested service with given arguments", e);
throw badRequest;
}
}
public Object invoke(HttpRequest request, HttpResponse response, Object resource) throws RestFailure
{
preprocess(request);
Object[] args = injectArguments(request);
try
{
Object result = method.invoke(resource, args);
return result;
}
catch (IllegalAccessException e)
{
throw new InternalServerErrorException("Not allowed to reflect on method: " + method.toString(), "Can not execute requested service", e);
}
catch (InvocationTargetException e)
{
return restErrorHandler.handleError(e);
}
catch (IllegalArgumentException e)
{
String msg = "Bad arguments passed to " + method.toString() + " (";
if (args != null)
{
boolean first = false;
for (Object arg : args)
{
if (!first)
{
first = true;
}
else
{
msg += ",";
}
if (arg == null)
{
msg += " null";
continue;
}
msg += " " + arg.getClass().getName();
}
}
msg += " )";
throw new InternalServerErrorException(msg, "Can not execute requested service", e);
}
finally
{
postprocess(request, response);
}
}
protected void initializePostprocessors() throws RequestProcessorException
{
RequestProcessorContext context = new RequestProcessorContext();
context.setTargetMethod(method);
context.setTargetClass(rootClass);
postprocessors = new ArrayList<RequestPostprocessor>();
Iterator<RequestPostprocessor> iterator = RequestProcessors.iteratePostprocessors();
while (iterator.hasNext())
{
RequestPostprocessor processor = iterator.next().createProcessor(context);
if (processor != null)
{
postprocessors.add(processor);
}
}
}
protected void initializePreprocessors() throws RequestProcessorException
{
RequestProcessorContext context = new RequestProcessorContext();
context.setTargetMethod(method);
context.setTargetClass(rootClass);
preprocessors = new ArrayList<RequestPreprocessor>();
Iterator<RequestPreprocessor> iterator = RequestProcessors.iteratePreprocessors();
while (iterator.hasNext())
{
RequestPreprocessor processor = iterator.next().createProcessor(context);
if (processor != null)
{
preprocessors.add(processor);
}
}
}
protected void postprocess(HttpRequest request, HttpResponse response) throws RestFailure
{
for (RequestPostprocessor postprocessor : postprocessors)
{
try
{
postprocessor.postprocess(request, response);
}
catch (Exception e)
{
logger.error("Error running processor ["+postprocessor.getClass().getCanonicalName()+"]", e);
}
}
}
protected void preprocess(HttpRequest request) throws RestFailure
{
for (RequestPreprocessor preprocessor : preprocessors)
{
preprocessor.preprocess(request);
}
}
protected static ValueInjector createParameterExtractor(Class<?> injectTargetClass, Type type, Annotation[] annotations)
{
if (ClassUtils.isSimpleType(type))
{
return createParameterExtractorForSimpleType(injectTargetClass, type, annotations);
}
return createParameterExtractorForComplexType(injectTargetClass, type, annotations);
}
protected static ValueInjector createParameterExtractorForSimpleType(Class<?> injectTargetClass, Type type, Annotation[] annotations)
{
DefaultValue defaultValue = ClassUtils.findAnnotation(annotations, DefaultValue.class);
String defaultVal = null;
if (defaultValue != null)
{
defaultVal = defaultValue.value();
}
QueryParam query;
HeaderParam header;
PathParam uriParam;
CookieParam cookie;
FormParam formParam;
if ((query = ClassUtils.findAnnotation(annotations, QueryParam.class)) != null)
{
return createParameterExtractorForSimpleType(RestParameterType.query, injectTargetClass, type, query.value(), defaultVal);
}
else if ((header = ClassUtils.findAnnotation(annotations, HeaderParam.class)) != null)
{
return createParameterExtractorForSimpleType(RestParameterType.header, injectTargetClass, type, header.value(), defaultVal);
}
else if ((formParam = ClassUtils.findAnnotation(annotations, FormParam.class)) != null)
{
return createParameterExtractorForSimpleType(RestParameterType.form, injectTargetClass, type, formParam.value(), defaultVal);
}
else if ((cookie = ClassUtils.findAnnotation(annotations, CookieParam.class)) != null)
{
return createParameterExtractorForSimpleType(RestParameterType.cookie, injectTargetClass, type, cookie.value(), defaultVal);
}
else if ((uriParam = ClassUtils.findAnnotation(annotations, PathParam.class)) != null)
{
return createParameterExtractorForSimpleType(RestParameterType.path, injectTargetClass, type, uriParam.value(), defaultVal);
}
else
{
return createParameterExtractorForSimpleType(RestParameterType.body, injectTargetClass, type, null, null);
}
}
protected static ValueInjector createParameterExtractorForSimpleType(RestParameterType restParameterType, Class<?> injectTargetClass, Type type,
String paramName, String defaultValue)
{
switch (restParameterType)
{
case query:
return new QueryParamInjector(type, paramName, defaultValue);
case header:
return new HeaderParamInjector(type, paramName, defaultValue);
case form:
return new FormParamInjector(type, paramName, defaultValue);
case cookie:
return new CookieParamInjector(type, paramName, defaultValue);
case path:
return new PathParamInjector(type, paramName, defaultValue);
default:
return new MessageBodyParamInjector(injectTargetClass, type);
}
}
/**
* The user can define a value object to group parameters that are passed in the same way (query, form, path, cookie, header).
* @param injectTargetClass
* @param injectTarget
* @param type
* @param genericType
* @param annotations
* @return
*/
protected static ValueInjector createParameterExtractorForComplexType(Class<?> injectTargetClass, Type type, Annotation[] annotations)
{
QueryParam query;
HeaderParam header;
PathParam uriParam;
CookieParam cookie;
FormParam formParam;
if ((query = ClassUtils.findAnnotation(annotations, QueryParam.class)) != null)
{
return new GroupValueInjector(RestParameterType.query, type, query.value());
}
else if ((header = ClassUtils.findAnnotation(annotations, HeaderParam.class)) != null)
{
return new GroupValueInjector(RestParameterType.header, type, header.value());
}
else if ((formParam = ClassUtils.findAnnotation(annotations, FormParam.class)) != null)
{
return new GroupValueInjector(RestParameterType.form, type, formParam.value());
}
else if ((cookie = ClassUtils.findAnnotation(annotations, CookieParam.class)) != null)
{
return new GroupValueInjector(RestParameterType.cookie, type, cookie.value());
}
else if ((uriParam = ClassUtils.findAnnotation(annotations, PathParam.class)) != null)
{
return new GroupValueInjector(RestParameterType.path, type, uriParam.value());
}
else
{
return new MessageBodyParamInjector(injectTargetClass, type);
}
}
protected void validateParamExtractors(String httpMethod)
{
boolean hasFormParam = false;
boolean hasBodyParam = false;
for (ValueInjector paramInjector : params)
{
if (paramInjector instanceof FormParamInjector)
{
hasFormParam = true;
}
if (paramInjector instanceof MessageBodyParamInjector)
{
if (hasBodyParam)
{
throw new InternalServerErrorException("Invalid rest method: " + method.toString() + ". Can not receive " +
"more than one parameter through body text", "Can not execute requested service");
}
hasBodyParam = true;
}
}
if (hasBodyParam && hasFormParam)
{
throw new InternalServerErrorException("Invalid rest method: " + method.toString() + ". Can not use both " +
"types on the same method: FormParam and BodyParam", "Can not execute requested service");
}
if ((hasBodyParam || hasFormParam) && httpMethod.equals("GET"))
{
throw new InternalServerErrorException("Invalid rest method: " + method.toString() + ". Can not receive " +
"parameters on body for GET methods.", "Can not execute requested service");
}
}
}