works.cirno.mocha.InvokeTarget.java Source code

Java tutorial

Introduction

Here is the source code for works.cirno.mocha.InvokeTarget.java

Source

/*
 * 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);
            }
        }
    }
}