Package org.expressme.webwind

Source Code of org.expressme.webwind.Dispatcher

package org.expressme.webwind;

import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.expressme.webwind.container.ContainerFactory;
import org.expressme.webwind.converter.ConverterFactory;
import org.expressme.webwind.renderer.JavaScriptRenderer;
import org.expressme.webwind.renderer.Renderer;
import org.expressme.webwind.renderer.TextRenderer;
import org.expressme.webwind.template.JspTemplateFactory;
import org.expressme.webwind.template.TemplateFactory;

/**
* Dispatcher handles ALL requests from clients, and dispatches to appropriate
* handler to handle each request.
*
* @author Michael Liao (askxuefeng@gmail.com)
*/
class Dispatcher {

    private final Log log = LogFactory.getLog(getClass());

    private ServletContext servletContext;
    private ContainerFactory containerFactory;
    private boolean multipartSupport = false;
    private long maxFileSize = 10L * 1024L * 1024L; // default to 10M.
    private UrlMatcher[] urlMatchers = null;
    private Map<UrlMatcher, Action> urlMap = new HashMap<UrlMatcher, Action>();
    private ConverterFactory converterFactory = new ConverterFactory();
    private Interceptor[] interceptors = null;
    private ExceptionHandler exceptionHandler = null;

    public void init(Config config) throws ServletException {
        log.info("Init Dispatcher...");
        this.servletContext = config.getServletContext();
        try {
            initAll(config);
        }
        catch (ServletException e) {
            throw e;
        }
        catch (Exception e) {
            throw new ServletException("Dispatcher init failed.", e);
        }
    }

    void initAll(Config config) throws Exception {
        // detect multipart support:
        try {
            Class.forName("org.apache.commons.fileupload.servlet.ServletFileUpload");
            this.multipartSupport = true;
            log.info("Using CommonsFileUpload to handle multipart http request.");
            String maxFileSize = config.getInitParameter("maxFileSize");
            if (maxFileSize!=null) {
                try {
                    long n = Long.parseLong(maxFileSize);
                    if (n<=0)
                        throw new NumberFormatException();
                    this.maxFileSize = n;
                }
                catch (NumberFormatException e) {
                    log.warn("Invalid parameter <maxFileSize> value '" + maxFileSize + "', using default.");
                }
            }
        }
        catch (ClassNotFoundException e) {
            log.info("CommonsFileUpload not found. Multipart http request can not be handled.");
        }

        // init IoC container:
        String containerName = config.getInitParameter("container");
        if (containerName==null)
            throw new ConfigException("Missing init parameter <container>.");
        this.containerFactory = Utils.createContainerFactory(containerName);
        this.containerFactory.init(config);
        List<Object> beans = this.containerFactory.findAllBeans();
        initComponents(beans);

        // init template engine:
        initTemplateFactory(config);
    }

    void initTemplateFactory(Config config) {
        String name = config.getInitParameter("template");
        if (name==null) {
            name = JspTemplateFactory.class.getName();
            log.info("No template factory specified. Default to '" + name + "'.");
        }
        TemplateFactory tf = Utils.createTemplateFactory(name);
        tf.init(config);
        log.info("Template factory '" + tf.getClass().getName() + "' init ok.");
        TemplateFactory.setTemplateFactory(tf);
    }

    void initComponents(List<Object> beans) {
        List<Interceptor> intList = new ArrayList<Interceptor>();
        for (Object bean : beans) {
            if (bean instanceof Interceptor)
                intList.add((Interceptor)bean);
            if (this.exceptionHandler==null && bean instanceof ExceptionHandler)
                this.exceptionHandler = (ExceptionHandler) bean;
            addActions(bean);
        }
        if (this.exceptionHandler==null)
            this.exceptionHandler = new DefaultExceptionHandler();
        this.interceptors = intList.toArray(new Interceptor[intList.size()]);
        // sort interceptors by its annotation of 'InterceptorOrder':
        Arrays.sort(
                this.interceptors,
                new Comparator<Interceptor>() {
                    public int compare(Interceptor i1, Interceptor i2) {
                        InterceptorOrder o1 = i1.getClass().getAnnotation(InterceptorOrder.class);
                        InterceptorOrder o2 = i2.getClass().getAnnotation(InterceptorOrder.class);
                        int n1 = o1==null ? Integer.MAX_VALUE : o1.value();
                        int n2 = o2==null ? Integer.MAX_VALUE : o2.value();
                        if (n1==n2)
                            return i1.getClass().getName().compareTo(i2.getClass().getName());
                        return n1<n2 ? (-1) : 1;
                    }
                }
        );
        this.urlMatchers = urlMap.keySet().toArray(new UrlMatcher[urlMap.size()]);
        // sort url matchers by its url:
        Arrays.sort(
                this.urlMatchers,
                new Comparator<UrlMatcher>() {
                    public int compare(UrlMatcher o1, UrlMatcher o2) {
                        String u1 = o1.url;
                        String u2 = o2.url;
                        int n = u1.compareTo(u2);
                        if (n==0)
                            throw new ConfigException("Cannot mapping one url '" + u1 + "' to more than one action method.");
                        return n;
                    }
                }
        );
    }

    // find all action methods and add them into urlMap:
    void addActions(Object bean) {
        Class<?> clazz = bean.getClass();
        Method[] ms = clazz.getMethods();
        for (Method m : ms) {
            if (isActionMethod(m)) {
                Mapping mapping = m.getAnnotation(Mapping.class);
                String url = mapping.value();
                UrlMatcher matcher = new UrlMatcher(url);
                if (matcher.getArgumentCount()!=m.getParameterTypes().length) {
                    warnInvalidActionMethod(m, "Arguments in URL '" + url + "' does not match the arguments of method.");
                    continue;
                }
                log.info("Mapping url '" + url + "' to method '" + m.toGenericString() + "'.");
                urlMap.put(matcher, new Action(bean, m));
            }
        }
    }

    // check if the specified method is a vaild action method:
    boolean isActionMethod(Method m) {
        Mapping mapping = m.getAnnotation(Mapping.class);
        if (mapping==null)
            return false;
        if (mapping.value().length()==0) {
            warnInvalidActionMethod(m, "Url mapping cannot be empty.");
            return false;
        }
        if (Modifier.isStatic(m.getModifiers())) {
            warnInvalidActionMethod(m, "method is static.");
            return false;
        }
        Class<?>[] argTypes = m.getParameterTypes();
        for (Class<?> argType : argTypes) {
            if (!converterFactory.canConvert(argType)) {
                warnInvalidActionMethod(m, "unsupported parameter '" + argType.getName() + "'.");
                return false;
            }
        }
        Class<?> retType = m.getReturnType();
        if (retType.equals(void.class)
                || retType.equals(String.class)
                || Renderer.class.isAssignableFrom(retType)
        )
            return true;
        warnInvalidActionMethod(m, "unsupported return type '" + retType.getName() + "'.");
        return false;
    }

    // log warning message of invalid action method:
    void warnInvalidActionMethod(Method m, String string) {
        log.warn("Invalid Action method '" + m.toGenericString() + "': " + string);
    }

    public boolean service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String url = req.getRequestURI();
        String path = req.getContextPath();
        if (path.length()>0)
            url = url.substring(path.length());
        // set default character encoding to "utf-8" if encoding is not set:
        if (req.getCharacterEncoding()==null)
            req.setCharacterEncoding("UTF-8");
        if (log.isDebugEnabled())
            log.debug("Handle for URL: " + url);
        Execution execution = null;
        for (UrlMatcher matcher : this.urlMatchers) {
            String[] args = matcher.getMatchedParameters(url);
            if (args!=null) {
                Action action = urlMap.get(matcher);
                Object[] arguments = new Object[args.length];
                for (int i=0; i<args.length; i++) {
                    Class<?> type = action.arguments[i];
                    if (type.equals(String.class))
                        arguments[i] = args[i];
                    else
                        arguments[i] = converterFactory.convert(type, args[i]);
                }
                execution = new Execution(req, resp, action, arguments);
                break;
            }
        }
        if (execution!=null) {
            handleExecution(execution, req, resp);
        }
        return execution!=null;
    }

    void handleExecution(Execution execution, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        if (this.multipartSupport) {
            if (MultipartHttpServletRequest.isMultipartRequest(request)) {
                request = new MultipartHttpServletRequest(request, maxFileSize);
            }
        }
        ActionContext.setActionContext(servletContext, request, response);
        try {
            InterceptorChainImpl chains = new InterceptorChainImpl(interceptors);
            chains.doInterceptor(execution);
            handleResult(request, response, chains.getResult());
        }
        catch (Exception e) {
            handleException(request, response, e);
        }
        finally {
            ActionContext.removeActionContext();
        }
    }

    void handleException(HttpServletRequest request, HttpServletResponse response, Exception ex) throws ServletException, IOException {
        try {
            exceptionHandler.handle(request, response, ex);
        }
        catch (ServletException e) {
            throw e;
        }
        catch (IOException e) {
            throw e;
        }
        catch (Exception e) {
            throw new ServletException(e);
        }
    }

    void handleResult(HttpServletRequest request, HttpServletResponse response, Object result) throws Exception {
        if (result==null)
            return;
        if (result instanceof Renderer) {
            Renderer r = (Renderer) result;
            r.render(this.servletContext, request, response);
            return;
        }
        if (result instanceof String) {
            String s = (String) result;
            if (s.startsWith("redirect:")) {
                response.sendRedirect(s.substring("redirect:".length()));
                return;
            }
            if (s.startsWith("script:")) {
                String script = s.substring("script:".length());
                new JavaScriptRenderer(script).render(servletContext, request, response);
                return;
            }
            new TextRenderer(s).render(servletContext, request, response);
            return;
        }
        throw new ServletException("Cannot handle result with type '" + result.getClass().getName() + "'.");
    }

    public void destroy() {
        log.info("Destroy Dispatcher...");
        this.containerFactory.destroy();
    }

}
TOP

Related Classes of org.expressme.webwind.Dispatcher

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.