/*****************************************************************************
* Copyright 2011 Zdenko Vrabel
*
* 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.zdevra.guice.mvc;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import org.zdevra.guice.mvc.annotations.Priority;
import org.zdevra.guice.mvc.exceptions.MethodInvokingException;
import org.zdevra.guice.mvc.parameters.ParamMetadata;
import org.zdevra.guice.mvc.parameters.ParamProcessor;
import org.zdevra.guice.mvc.parameters.ParamProcessorsService;
/**
* Class prepare data for controller's method, call the method
* and process the method's result.
*
* For data preparation is used {@link ParamProcessorsService} which use
* different processors defined in a package {@link org.zdevra.guice.mvc.parameters}
*
*/
class MethodInvokerImpl implements MethodInvoker {
private final Class<?> controllerClass;
private final ViewPoint defaultView;
private final String resultName;
private final Method method;
private final List<ParamProcessor> paramProcs;
private final int priority;
/**
* static factory method creates the invoker during
* scanning phase.
*
* @param reqMapping
* @return
*/
public static MethodInvoker createInvoker(MappingData reqMapping) {
ParamProcessorsService paramService = reqMapping.injector.getInstance(ParamProcessorsService.class);
ConversionService convertService = reqMapping.injector.getInstance(ConversionService.class);
ViewScannerService viewScannerService = reqMapping.injector.getInstance(ViewScannerService.class);
ViewPoint defaultView = viewScannerService.scan(reqMapping.method.getAnnotations());
if (defaultView == ViewPoint.NULL_VIEW) {
defaultView = viewScannerService.scan(reqMapping.controllerClass.getAnnotations());
}
List<ParamProcessor> processors = scanParams(reqMapping.method, paramService, convertService);
String resultName = reqMapping.resultName;
Priority priorityAnnotation = reqMapping.method.getAnnotation(Priority.class);
int priority = Priority.DEFAULT;
if (priorityAnnotation != null) {
priority = priorityAnnotation.value();
}
MethodInvoker invoker = new MethodInvokerImpl(reqMapping.controllerClass, reqMapping.method, defaultView, resultName, processors, priority);
return invoker;
}
/**
* Hidden constructor. The Invoker is constructed through the factory methods.
* @param defaultView
* @param resultName
* @param method
* @param paramProcs
*/
MethodInvokerImpl(Class<?> controllerClass, Method method, ViewPoint defaultView, String resultName, List<ParamProcessor> paramProcs, int priority) {
this.controllerClass = controllerClass;
this.defaultView = defaultView;
this.resultName = resultName;
this.method = method;
this.paramProcs = Collections.unmodifiableList(paramProcs);
this.priority = priority;
}
/**
* static method scanning method and returns list of
* {@link ParamProcessor processors} for each method's parameter.
*
* @param method
* @param paramService
* @param convertService
*
* @return
*/
private static final List<ParamProcessor> scanParams(Method method, ParamProcessorsService paramService, ConversionService convertService) {
Annotation[][] annotations = method.getParameterAnnotations();
Class<?>[] types = method.getParameterTypes();
List<ParamProcessor> result = new LinkedList<ParamProcessor>();
for (int i = 0; i < types.length; ++i) {
ParamMetadata metadata = new ParamMetadata(types[i], annotations[i], convertService, method);
ParamProcessor processor = paramService.createProcessor(metadata);
result.add(processor);
}
return result;
}
/**
* invokes the method for controller
*
* @param data
* @return
*/
public ModelAndView invoke(InvokeData data) {
try {
Object controllerObj = data.getInjector().getInstance(controllerClass);
if (controllerObj == null) {
throw new NullPointerException("null controller");
}
Object[] args = getValues(data);
Object result = method.invoke(controllerObj, args);
ModelAndView mav = processResult(result);
args = null;
return mav;
} catch (IllegalArgumentException e) {
throw new MethodInvokingException(method, e);
} catch (IllegalAccessException e) {
throw new MethodInvokingException(method, e);
} catch (InvocationTargetException e) {
throw new MethodInvokingException(method, e.getCause());
}
}
/**
* returns concrete values for method invocation.
* @param data
* @return
*/
private Object[] getValues(InvokeData data) {
Object[] out = new Object[paramProcs.size()];
for (int i = 0; i < paramProcs.size(); ++i) {
ParamProcessor processor = paramProcs.get(i);
out[i] = processor.getValue(data);
}
return out;
}
/**
* do processing of method result
*
* @param result
* @return
*/
private ModelAndView processResult(Object result) {
ModelAndView out = new ModelAndView(this.defaultView);
if (result == null) {
return out;
} else if (result instanceof ModelMap) {
ModelMap resultModel = (ModelMap) result;
out.addModel(resultModel);
} else if (result instanceof ModelAndView) {
return (ModelAndView) result;
} else if (result instanceof ViewPoint) {
ViewPoint resultView = (ViewPoint) result;
out.addView(resultView);
} else {
String name = getResultModelName();
out.getModel().addObject(name, result);
}
return out;
}
private String getResultModelName() {
if (this.resultName == null || this.resultName.length() == 0) {
return this.method.getName();
}
return this.resultName;
}
@Override
public int getPriority() {
return priority;
}
/**
* comparator of invokers determines which
* method invoker will be used first
*
* @param o
* @return
*/
@Override
public int compareTo(MethodInvoker o) {
if (this.getPriority() < o.getPriority()) {
return -1;
} else if (this.getPriority() > o.getPriority()) {
return 1;
} else {
return 0;
}
}
}