Package dk.brics.jwig.server

Source Code of dk.brics.jwig.server.Dispatcher

package dk.brics.jwig.server;

import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.SortedSet;
import java.util.TreeSet;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.UnavailableException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;

import dk.brics.jwig.FileField;
import dk.brics.jwig.JWIGException;
import dk.brics.jwig.Response;
import dk.brics.jwig.WebApp;
import dk.brics.jwig.WebSite;
import dk.brics.jwig.persistence.JwigCurrentSessionContext;
import dk.brics.jwig.persistence.JwigCurrentSessionContextProxy;
import dk.brics.jwig.server.cache.DependencyMap;
import dk.brics.xact.XML;

/**
* Dispatcher servlet.
* <p/>
* Connects the JWIG system with the underlying servlet engine.
*/
public final class Dispatcher implements Filter {

    private Logger log;

    private final List<DispatchListener> listeners = new LinkedList<DispatchListener>();
    private static final Collection<String> ALLOWED_METHODS = new HashSet<String>(Arrays.asList("GET", "POST", "TRACE", "PUT", "DELETE", "HEAD", "OPTIONS"));

    private ServletFileUpload fileupload;


    static {
        // set XHTML namespace declarations for XACT
        String XHTML_NAMESPACE = "http://www.w3.org/1999/xhtml";
        XML.getNamespaceMap().put("", XHTML_NAMESPACE);
        XML.getNamespaceMap().put("xhtml", XHTML_NAMESPACE);
    }

    /**
     * Constructs a new dispatcher.
     */
    public Dispatcher() {
        super();
    }

    /**
     * Adds a dispatch event listener.
     */
    public void addListener(DispatchListener d) {
        listeners.add(d);
    }

    /**
     * Invoked by the servlet engine when starting.
     */
    @Override
    public void init(FilterConfig config) throws ServletException {
        try {

            WebApp.context = config.getServletContext();
            // prepare persistence session context
            JwigCurrentSessionContextProxy.registerCallBack(new JwigCurrentSessionContextProxy.ConstructorCallBack() {
                @Override
                public void call(JwigCurrentSessionContext sessionContext) {
                    addListener(sessionContext);
                }
            });

            // find main web site/app class
            String websiteclass = config.getInitParameter("MainClass");
            if (websiteclass == null) {
                websiteclass = "Main";
            }
            final Object o = Dispatcher.class.getClassLoader().loadClass(websiteclass).newInstance();
            WebSite website;
            if (o instanceof WebApp) {
                website = new WebSite() {
                    @Override
                    public void init() {
                        add((WebApp) o);
                    }
                };
            } else if (o instanceof WebSite) {
                website = (WebSite) o;
            } else {
                throw new ServletException("JWIG main class does not extend dk.brics.jwig.WebSite or dk.brics.jwig.WebApp");
            }

            // set thread context, log4j properties, and Hibernate properties
            ThreadContext.setWebSite(website);
            String base_url = Config.get("jwig.base_url", (String) null);
            String base_url_secure = Config.get("jwig.base_url_secure", (String) null);
            boolean hibernate = Config.get("jwig.hibernate", false);
            Properties properties = new Properties();
            properties.putAll(website.getProperties());
            if (!Logger.getRootLogger().getAllAppenders().hasMoreElements()) {
                PropertyConfigurator.configure(properties);
            }
            log = Logger.getLogger(Dispatcher.class);
            log.info("Initializing {"
                     + "MainClass=" + websiteclass
                     + ", jwig.hibernate=" + hibernate
                     + (base_url != null ? ", jwig.base_url=" + base_url : "")
                     + (base_url_secure != null ? ", jwig.base_url_secure=" + base_url_secure : "")
                     + "}");
            website.getQuerier().preInit(properties);
            website.init();
            if (!Logger.getRootLogger().getAllAppenders().hasMoreElements()) {
                //Make absolutely sure that at least some logging can happen
                BasicConfigurator.configure();
                log.warn("Log4J logging not configured by the application. Initializing Log4J with a default setup.");
            }

            website.getQuerier().postInit();
            List<RequestManager> requestmanagers = new ArrayList<RequestManager>();
            ThreadContext.init(base_url, base_url_secure, website.getCache(), requestmanagers, new SessionManager(config.getServletContext()),
                               new DependencyMap(), config.getServletContext().getRealPath("/"), this);

            // register request manager for each web app
            for (WebApp app : website.getWebApps()) {
                requestmanagers.add(new RequestManager(app));
                addListener(app.getSecurityManager());
            }

            int fileupload_memory_threshold = Config.get("jwig.fileupload_memory_threshold", 100000);
            String fileupload_tmpdir = Config.get("jwig.fileupload_tmpdir", System.getProperty("java.io.tmpdir"));
            long multipart_maxsize = Config.get("jwig.multipart_maxsize", 100000000L);
            long fileupload_maxsize = Config.get("jwig.fileupload_maxsize", -1L);

            File repository = new File(fileupload_tmpdir);
            if (!repository.isAbsolute()) {
                repository = new File(ThreadContext.getServletHome(), fileupload_tmpdir);
            }
            fileupload = new ServletFileUpload(new DiskFileItemFactory(fileupload_memory_threshold, repository));
            fileupload.setSizeMax(multipart_maxsize);
            fileupload.setFileSizeMax(fileupload_maxsize);
            log.debug("Initializing {jwig.fileupload_memory_threshold=" + fileupload_memory_threshold +
                      ", jwig.fileupload_tmpdir=" + fileupload_tmpdir +
                      ", jwig.multipart_maxsize=" + multipart_maxsize +
                      ", jwig.fileupload_maxsize=" + fileupload_maxsize +
                      "}");

            website.postInit();
        } catch (Exception e) {
            if (log != null) {
                log.fatal("Exception in init", e);
            } else {
                e.printStackTrace();
            }
            throw new UnavailableException("Web site initialization error - see the server log", 0);
        }
    }

    /**
     * Invoked by the servlet engine when stopping.
     */
    @Override
    public void destroy() {
        log.info("Stopping");
        ThreadContext.getWebSite().getQuerier().close();
        ThreadContext.destroy();
    }

    /**
     * Invoked by the clients via the servlet engine on each request.
     */
    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
        try {
            // inform listeners
            for (DispatchListener l : listeners) {
                l.threadDispatched(new ThreadDispatchEvent(Thread.currentThread()));
            }

            // forward Comet requests to Synchronizer
            HttpServletRequest request = (HttpServletRequest) req;
            HttpServletResponse response = (HttpServletResponse) resp;
            if (request.getServletPath().equals("/==")) {
                chain.doFilter(req, resp);
                return;
            }

            // handle error codes
            WebSite website = ThreadContext.getWebSite();
            Object sc = request.getAttribute("javax.servlet.error.status_code");
            if (sc != null) {
                int status_code = (Integer) sc;
                String error_url = (String) request.getAttribute("javax.servlet.error.request_uri");
                log.info("Status code " + status_code);
                ThreadContext.set(new ThreadContext(request, response, error_url));
                switch (status_code) { // TODO: need to cover more status codes? (should match web.xml)
                    case HttpServletResponse.SC_NOT_FOUND:
                        website.sendError(status_code, "The requested resource is not found: " + error_url);
                        ThreadContext.get().getResponse().write(request, response);
                        break;
                    case HttpServletResponse.SC_FORBIDDEN:
                        website.sendError(status_code, "Access to the requested resource is forbidden: " + error_url);
                        ThreadContext.get().getResponse().write(request, response);
                        break;
                    default:
                        log.error("Unexpected status code " + status_code);
                }
                return;
            }

            // first phase of parsing the request
            String url = getFullRequestURL(request);
            String method = request.getMethod();
            ThreadContext.set(new ThreadContext(request, response, url));
            log.info(method + " from " + getClient(request) + ": " + url);
            if (!ALLOWED_METHODS.contains(method)) {
                log.info("HTTP method not allowed: " + method);
                website.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, "HTTP method " + method + " not allowed.");
                ThreadContext.get().getResponse().write(request, response);
                return;
            }
            //url = URLEncoding.decode(url);
            String path = request.getRequestURI().substring(request.getContextPath().length());
            ThreadContext context = new ThreadContext(request, response, url);
            ThreadContext.set(context);

            //Add all web methods that may answer to the URL provided
            SortedSet<RegisteredMethod> allMethods = new TreeSet<RegisteredMethod>();
            for (RequestManager r : ThreadContext.getRequestManagers()) {
                if (r.matches(path)) {
                    allMethods.addAll(r.getWebMethods());
                }
            }
            context.setMatchedWebMethods(new LinkedList<RegisteredMethod>(allMethods));

            // try all request managers until one takes the request

            if (process(path, url, request, response)) {
                if (!response.isCommitted()) {
                    log.error("No response generated");
                    website.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "No response.");
                    ThreadContext.get().getResponse().write(request, response);
                }
                log.info("Completed");
                return;
            }

            // try default servlet to serve static resources
            log.info("Forwarding to default servlet for static resources");
            chain.doFilter(req, resp);

        } catch (RuntimeException t) {
            log.error("Exception", t);
            throw t;
        } finally {
            ThreadContext.set(null);
            for (DispatchListener l : listeners) {
                l.threadDismissed(new ThreadDispatchEvent(Thread.currentThread()));
            }
        }
    }

    /**
     * Processes the given request with this web app. The web app should match the request.
     *
     * @return true if processed by this request manager
     */
    public boolean process(String path, String url, HttpServletRequest request, HttpServletResponse response) {
        ThreadContext oc = ThreadContext.get();
        ThreadContext threadContext = new ThreadContext(request, response, url, new Response(),
                                                        getServletParams(request), path, oc);
        threadContext.createInvocationContext(null, null, null, -1);
        threadContext.setRequestManager(null);

        try {
            ThreadContext.set(threadContext);
            invokeNextWebMethod();
            Object result = threadContext.getResultIfAny();
            if (result != null) {
              if (!response.isCommitted()) {
                send(threadContext.getReferer(), result);
              }
            }
        } finally {
            ThreadContext.set(oc);
        }
        return response.isCommitted();

    }

    /**
     * Invokes the next applicable web method.
     */
    public void invokeNextWebMethod() {
        ThreadContext c = ThreadContext.get();
        String path = c.getFullRequestPath();
        LinkedList<RegisteredMethod> skippedList = new LinkedList<RegisteredMethod>();

        while (true) {
            LinkedList<RegisteredMethod> webmethods = c.getMatchedWebMethods();
            if (webmethods.isEmpty()) {
                break;
            }

            RegisteredMethod rm = webmethods.removeFirst();
            RequestManager manager = rm.getRequestManager();


            RequestManager oldManager = c.getRequestManager();
            try {
                c.setRequestManager(manager);
                if (!c.isDone()) {
                    int prefixLength = c.getCurrentInvocationContext().getPrefixLength();

                    Object[] args = manager.match(rm, path.substring(prefixLength));
                    if (args != null) {
                        try {
                            for (DispatchListener l : listeners) {
                                l.webMethodDispatched(new WebMethodDispatchEvent(rm.getRequestManager().getWebApp(), rm.getMethod().getName(), args));
                            }
                            manager.invoke(rm.getMethod(), manager.getWebApp(), args);
                        }
                        finally {
                            c.setDone(true);
                        }
                    }
                } else {
                    skippedList.add(rm);
                }
            } finally {
                c.setRequestManager(oldManager);
            }
        }
        c.setMatchedWebMethods(skippedList);
    }

    /**
     * Finds the servlet parameters (application/x-www-form-urlencoded or multipart/form-data).
     */
    @SuppressWarnings("unchecked")
    private Map<String, Object[]> getServletParams(HttpServletRequest request) {
        if (request.getMethod().equals("POST") && ServletFileUpload.isMultipartContent(request)) {
            Map<String, List<Object>> paramslists = new HashMap<String, List<Object>>();
            try {
                List<FileItem> parsed_request = ThreadContext.get().getParsed_request();
                if (parsed_request == null) {
                    parsed_request = fileupload.parseRequest(request);
                    ThreadContext.get().setParsed_request(parsed_request);
                }
                for (FileItem aParsed_request : parsed_request) {
                    String name = aParsed_request.getFieldName();
                    Object value;
                    if (aParsed_request.isFormField()) {
                        try {
                            value = aParsed_request.getString("UTF-8");
                        } catch (UnsupportedEncodingException e) {
                            throw new JWIGException(e);
                        }
                    } else {
                        value = new FileField(aParsed_request);
                    }
                    List<Object> ps = paramslists.get(name);
                    if (ps == null) {
                        ps = new ArrayList<Object>();
                        paramslists.put(name, ps);
                    }
                    ps.add(value);
                }
                Map<String, Object[]> params = new HashMap<String, Object[]>();
                for (Map.Entry<String, List<Object>> es : paramslists.entrySet()) {
                    params.put(es.getKey(), es.getValue().toArray());
                }
                return params;
            } catch (FileUploadException e) {
                throw new JWIGException(e);
            }
        } else {
            if (request.getCharacterEncoding() == null) {
                try {
                    request.setCharacterEncoding("UTF-8");
                } catch (UnsupportedEncodingException e) {
                    throw new JWIGException(e);
                }
            }
            return request.getParameterMap();
        }
    }

    private void send(String referer, Object result) {
        ThreadContext c = ThreadContext.get();
        HttpServletResponse response = c.getServletResponse();
        if (result == null && referer != null && !response.isCommitted()) {
            try {
                log.info("Sending empty response");
                response.setContentLength(0);
                response.flushBuffer();
            } catch (IOException e) {
                log.warn("IOException when sending empty response: " + e.getMessage());
            }
        }
        if (result != null) {
            if (result instanceof XML || result instanceof String) {
                Response r = new Response(c.getResponse());
                r.setResult(result);
                send(r);
            } else if (result instanceof URL) {
                try {
                    response.sendRedirect(result.toString());
                } catch (IOException e) {
                    throw new JWIGException(e);
                }
            } else { // TODO: support other return types? (e.g. binary data?)
                throw new JWIGException("Unrecognized return type of web method");
            }
        }
    }


    /**
     * Sends the given response.
     */
    public void send(Response r) {
        ThreadContext c = ThreadContext.get();
        c.setResponse(r);
        if (!c.getServletResponse().isCommitted()) {
            log.info("Sending response page");
            r.write(c.getServletRequest(), c.getServletResponse());
        } else {
            log.debug("Response already committed");
        }
    }

    /**
     * Describes the client location (for logging).
     */
    public static String getClient(HttpServletRequest request) {
        String client = request.getHeader("X-Forwarded-For");
        if (client != null) {
            client += " (via proxy)";
        } else {
            client = request.getRemoteHost() + ":" + request.getRemotePort();
        }
        return client;
    }

    /**
     * Returns the full request URL.
     */
    private String getFullRequestURL(HttpServletRequest request) {
        StringBuffer b = request.getRequestURL();
        String base = ThreadContext.getBaseURL(request.isSecure());
        if (base.length() > 0 && b.length() > 9 && b.indexOf("/", 9) > 0) {
            b.delete(0, b.indexOf("/", 9)).insert(0, base);
        }
        String query = request.getQueryString();
        if (query != null) {
            b.append('?').append(query);
        }
        return b.toString();
    }
}
TOP

Related Classes of dk.brics.jwig.server.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.