/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2015 Cloudee Huang ( https://github.com/cloudeecn / cloudeecn@gmail.com )
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package works.cirno.mocha;
import java.io.File;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import works.cirno.mocha.parameter.name.Parameter;
import works.cirno.mocha.parameter.value.ParameterSource;
import works.cirno.mocha.result.Renderer;
/**
*
*/
public class InvokeTarget {
private static final HashMap<Class<?>, Object> defaultPrimitives = new HashMap<Class<?>, Object>();
static {
defaultPrimitives.put(Boolean.TYPE, false);
defaultPrimitives.put(Byte.TYPE, (byte) 0);
defaultPrimitives.put(Character.TYPE, (char) 0);
defaultPrimitives.put(Short.TYPE, (short) 0);
defaultPrimitives.put(Integer.TYPE, 0);
defaultPrimitives.put(Float.TYPE, 0f);
defaultPrimitives.put(Long.TYPE, 0l);
defaultPrimitives.put(Double.TYPE, 0d);
}
private final Logger log;
private final Logger perfLog = LoggerFactory.getLogger("perf." + InvokeTarget.class.getName());
// private final Invoker invoker;
// private final Class<?> controllerClass;
// private final String methodName;
private Map<Class<?>, Renderer> exceptionHandlers = new HashMap<>();
private Set<String> groupNames;
private List<Renderer> resultRenderers;
private Object controller;
private Method method;
private Parameter[] parameters;
private boolean raw;
private File uploadTemp;
public InvokeTarget(Dispatcher dispatcher, Object controller, String methodName) {
Class<?> controllerClass = controller.getClass();
this.log = LoggerFactory
.getLogger(getClass().getName() + ".(" + controller.getClass().getName() + "." + methodName + ")");
this.controller = controller;
Method[] methods = controllerClass.getMethods();
boolean resolved = false;
for (Method method : methods) {
if (method.getName().equals(methodName)) {
if (resolved) {
throw new IllegalArgumentException(
"Duplicated method " + methodName + " in controller " + controllerClass.getName());
} else {
this.method = method;
resolved = true;
}
}
}
if (this.method == null) {
throw new IllegalArgumentException(
"Can't find method " + methodName + " in controller " + controllerClass.getName());
}
}
void setExceptionHandlers(Map<Class<?>, Renderer> exceptionHandlers) {
this.exceptionHandlers.clear();
this.exceptionHandlers.putAll(exceptionHandlers);
}
void setParameters(Parameter[] parameters) {
this.parameters = parameters.clone();
}
void setResultRenderers(List<Renderer> resultRenderers) {
this.resultRenderers = new ArrayList<>(resultRenderers);
}
void setGroupNames(Set<String> groupNames) {
this.groupNames = groupNames;
}
void setRaw(boolean raw) {
this.raw = raw;
}
void setUploadTemp(File uploadTemp) {
this.uploadTemp = uploadTemp;
}
public Class<?> getControllerClass() {
return controller.getClass();
}
public Object getController() {
return controller;
}
public Method getMethod() {
return method;
}
public Parameter parameterAt(int idx) {
return parameters[idx];
}
public int parameterCount() {
return parameters.length;
}
public List<Renderer> getResultRenderers() {
return resultRenderers;
}
public Logger log() {
return log;
}
public Set<String> getGroupNames() {
return groupNames;
}
public boolean isRaw() {
return raw;
}
public void invoke(String uri, InvokeContext context) {
long beginTime = 0;
if (perfLog.isDebugEnabled()) {
perfLog.debug("Invoking {}", uri);
beginTime = System.nanoTime();
}
try {
HttpServletRequest req = context.getRequest();
HttpServletResponse resp = context.getResponse();
if (ServletFileUpload.isMultipartContent(req)) {
try {
// TODO Rework this to make it configurable and reuseable
FileItemFactory fileItemFactory = new DiskFileItemFactory(
DiskFileItemFactory.DEFAULT_SIZE_THRESHOLD,
uploadTemp);
context.setMultipart(true);
ServletFileUpload servletFileUpload = new ServletFileUpload(fileItemFactory);
List<FileItem> items = servletFileUpload.parseRequest(req);
for (FileItem item : items) {
MultiPartItem part = new MultiPartItemCommon(item);
if (!item.isInMemory()) {
context.registerCloseable(part,
"UploadFile: " + item.getFieldName() + " name: " + item.getName());
}
context.addPart(part);
}
} catch (FileUploadException e) {
throw new RuntimeException(e);
}
if (perfLog.isDebugEnabled()) {
perfLog.debug("Parse multipart in {}ms", (System.nanoTime() - beginTime) / 1000000.0f);
beginTime = System.nanoTime();
}
}
int parametersCount = parameters.length;
Object[] invokeParams = new Object[parametersCount];
try {
// bind parameter
for (int i = 0; i < parametersCount; i++) {
Parameter parameter = parameters[i];
Class<?> type = parameter.getType();
Object value = ParameterSource.NOT_HERE;
for (ParameterSource source : parameter.getParameterSources()) {
value = source.getParameterValue(context, parameter);
}
if (value == ParameterSource.NOT_HERE) {
value = null;
}
if (value == null && type.isPrimitive()) {
value = defaultPrimitives.get(type);
}
invokeParams[i] = value;
}
} catch (Throwable t) {
log.error("Exception occurred binding parameters {}{}", req.getRequestURI(),
req.getQueryString() != null ? req.getQueryString() : "", t);
handleException(context, t);
}
if (perfLog.isDebugEnabled()) {
perfLog.debug("Parameter resolve in {}ms", (System.nanoTime() - beginTime) / 1000000.0f);
beginTime = System.nanoTime();
}
try {
Object result = method.invoke(controller, invokeParams);
if (perfLog.isDebugEnabled()) {
perfLog.debug("Controller execution in {}ms", (System.nanoTime() - beginTime) / 1000000.0f);
beginTime = System.nanoTime();
}
handleResult(context, req, resp, result);
if (perfLog.isDebugEnabled()) {
perfLog.debug("Result handle in {}ms", (System.nanoTime() - beginTime) / 1000000.0f);
beginTime = System.nanoTime();
}
} catch (Throwable t) {
log.error("Exception occurred processing request {}{}", req.getRequestURI(),
req.getQueryString() != null ? req.getQueryString() : "", t);
handleException(context, t);
}
} finally {
try {
context.close();
} catch (Exception e) {
log.warn("Failed closing context", e);
}
}
}
public void handleResult(InvokeContext ctx, HttpServletRequest req, HttpServletResponse resp, Object resultObj) {
if (resultObj == null) {
return;
} else if (resultObj instanceof Renderer) {
Renderer result = (Renderer) resultObj;
try {
if (!result.renderResult(ctx, null)) {
handleException(ctx,
new IllegalStateException("Can't handle specified renderer: " + resultObj));
}
} catch (Exception e) {
log.error("Exception occurred processing result of request {}{}", req.getRequestURI(),
req.getQueryString() != null ? req.getQueryString() : "", e);
handleException(ctx, e);
}
} else {
boolean handled = false;
for (Renderer renderer : resultRenderers) {
if (renderer.renderResult(ctx, resultObj)) {
handled = true;
break;
}
}
if (!handled) {
log.error("Exception occurred processing result of request {}{}, Can't handle specified result: {}",
req.getRequestURI(), req.getQueryString() != null ? req.getQueryString() : "", resultObj);
handleException(ctx,
new IllegalStateException("Can't handle specified result: " + resultObj));
}
}
}
public void handleException(InvokeContext ctx, Throwable e) {
Class<?> exceptionType = e.getClass();
Renderer renderer;
do {
renderer = exceptionHandlers.get(exceptionType);
if (renderer != null) {
break;
}
} while (Throwable.class.isAssignableFrom(exceptionType = exceptionType.getSuperclass()));
if (renderer != null) {
try {
renderer.renderResult(ctx, e);
} catch (Throwable ex) {
log.error("Exception occored handling exception: {}", e, ex);
}
} else {
try {
ctx.getResponse().sendError(500, "Server internal error");
} catch (Throwable ex) {
log.warn("Can't send error page to client due to IOException, exception is {}", e, ex);
}
}
}
}