org.ireland.jnetty.dispatch.servlet.ServletMapper.java Source code

Java tutorial

Introduction

Here is the source code for org.ireland.jnetty.dispatch.servlet.ServletMapper.java

Source

/*
 * Copyright (c) 1998-2012 Caucho Technology -- all rights reserved
 *
 * This file is part of Resin(R) Open Source
 *
 * Each copy or derived work must preserve the copyright notice and this
 * notice unmodified.
 *
 * Resin Open Source is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Resin Open Source is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
 * of NON-INFRINGEMENT.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Resin Open Source; if not, write to the
 *
 *   Free Software Foundation, Inc.
 *   59 Temple Place, Suite 330
 *   Boston, MA 02111-1307  USA
 *
 * @author Scott Ferguson
 */

package org.ireland.jnetty.dispatch.servlet;

import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.logging.Log;

import javax.servlet.FilterChain;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;

import org.ireland.jnetty.config.ConfigException;
import org.ireland.jnetty.dispatch.FilterChainInvocation;
import org.ireland.jnetty.dispatch.filterchain.ErrorFilterChain;
import org.ireland.jnetty.jsp.JspServletComposite;
import org.ireland.jnetty.webapp.WebApp;
import org.springframework.util.Assert;

import com.caucho.util.LruCache;

/**
 * Manages dispatching: servlets and filters. :TODO: rename "ServletMapper" TO "ServletMatcher"
 * 
 * Servlet?
 * 
 */
public class ServletMapper {
    private static final Log log = LogFactory.getLog(ServletMapper.class.getName());

    private static final boolean debug = log.isDebugEnabled();

    private final WebApp _webApp;

    private final ServletContext _servletContext;

    private final ServletManager _servletManager;

    // LRUCacheContextURIServletFilterChain()
    private LruCache<String, FilterChain> _servletChainCache = new LruCache<String, FilterChain>(256);

    //  urlPattern  <servlet-mapping>(URL?)
    // 1:ServletMappings for Exact Match <urlPattern,ServletMapping>
    private Map<String, ServletMapping> _exactServletMappings = new HashMap<String, ServletMapping>();

    // 2:ServletMappings for Longest Prefix Match <prefixPattern,ServletMapping>(pattern?)
    private SortedMap<String, ServletMapping> _prefixServletMappings = new TreeMap<String, ServletMapping>(
            new PatternLengthComparator());

    // 3:ServletMappings for Extension Match <Extension,ServletMapping> (web.xml?)
    private LinkedHashMap<String, ServletMapping> _extensionServletMappings = new LinkedHashMap<String, ServletMapping>();

    // 4:Default servlet (urlPattern"/",?Servletjsp,?Servlet)
    private ServletConfigImpl _defaultServlet;

    //  ServletName  urlPattern 
    // Servlet 3.0 maps serletName to urlPattern <serletName,Set<urlPattern>>
    private Map<String, Set<String>> _urlPatterns = new HashMap<String, Set<String>>();

    public ServletMapper(WebApp webApp, ServletContext servletContext, ServletManager servletManager) {
        Assert.notNull(webApp);
        Assert.notNull(servletContext);
        Assert.notNull(servletManager);

        _webApp = webApp;
        _servletContext = servletContext;
        _servletManager = servletManager;
    }

    // Getter and Setter---------------------------------------------------
    /**
     * Gets the servlet context.
     */
    public WebApp getWebApp() {
        return _webApp;
    }

    /**
     * Returns the servlet manager.
     */
    public ServletManager getServletManager() {
        return _servletManager;
    }

    // Getter and Setter---------------------------------------------------

    /**
     * Add a servletMapping
     * @param mapping
     * @throws ServletException
     */
    public void addServletMapping(ServletMapping mapping) throws ServletException {
        if (mapping.getURLPatterns() != null) {
            for (String urlPattern : mapping.getURLPatterns()) {
                addUrlMapping(urlPattern, mapping);
            }
        }
    }

    /**
     * Adds a servlet mapping Specification: Servlet-3_1-PFD chapter 12.1
     * 
     *  urlPattern + " -> " + ServletMapping 
     * 
     */
    public void addUrlMapping(final String urlPattern, ServletMapping mapping) throws ServletException {
        try {
            ServletConfigImpl config = mapping.getServletConfig();

            String servletName = config.getServletName();

            //ServletConfigImpl?ServletManager,
            if (_servletManager.getServlet(servletName) == null) {
                _servletManager.addServlet(config);
            }

            if ("/".equals(urlPattern)) // Default servlet
            {
                _defaultServlet = mapping.getServletConfig();
            }

            // ?
            _exactServletMappings.put(urlPattern, mapping);

            // ??/../*
            if (urlPattern.endsWith("/*")) {
                _prefixServletMappings.put(urlPattern, mapping);
            }

            // ???
            if (urlPattern.startsWith("*.")) {
                _extensionServletMappings.put(urlPattern, mapping);
            }

            //
            Set<String> patterns = _urlPatterns.get(servletName);

            if (patterns == null) {
                patterns = new HashSet<String>();

                _urlPatterns.put(servletName, patterns);
            }
            patterns.add(urlPattern);

            //
            log.debug("servlet-mapping " + urlPattern + " -> " + servletName);
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw ConfigException.create(e);
        }
    }

    public Set<String> getUrlPatterns(String servletName) {
        return _urlPatterns.get(servletName);
    }

    /**
     * Sets the default servlet. 4.
     * ?servlet????default?servlet?????default servlet??
     */
    public void setDefaultServlet(ServletConfigImpl config) throws ServletException {
        _defaultServlet = config;
    }

    /**
     *  FilterChainInvocation ?  Servlet,?FilterChain
     * 
     * ServletURL???URL??????
     * 
     * 1. ?servlet???servlet
     * 
     * 2. ??????/?servlet
     * 
     * 3. URL???? .jspservlet????Servlet????.?
     * 
     * 4. ?servlet????default?servlet?????default servlet??
     * 
     * XXX:ServletFilterChain,?contextURI(??),??URI,??contextURI?,??Servlet
     * XXX:/login.do?u=jack,/login.do?u=ken ?ServletFilterChain
     * 
     * @param fcInvocation
     * @return
     * @throws ServletException
     */
    public FilterChain buildServletChain(FilterChainInvocation fcInvocation) throws ServletException {
        String contextURI = fcInvocation.getContextURI();

        //?cache
        FilterChain servletChain = _servletChainCache.get(contextURI);

        if (servletChain != null)
            return servletChain;

        //
        ServletConfigImpl config = null;

        // 1-2-3:contextURI?Servlet
        config = mapServlet(contextURI);

        //3.5?jsp??
        if (config == null) {
            config = mapJspServlet(contextURI);
        }

        // 4:Servlet(urlPattern"/",?Servletjsp,?Servlet)
        if (config == null) {
            config = _defaultServlet;
        }

        // 5:?Servlet,404
        if (config == null) {
            if (debug)
                log.debug("'" + contextURI + "' has no default servlet defined");

            return new ErrorFilterChain(404);
        }

        String servletPath = contextURI; // TODO: how to decide servletPath ?

        fcInvocation.setServletPath(servletPath);

        if (servletPath.length() < contextURI.length())
            fcInvocation.setPathInfo(contextURI.substring(servletPath.length()));
        else
            fcInvocation.setPathInfo(null);

        String servletName = config.getServletName();

        fcInvocation.setServletName(servletName);

        if (debug)
            log.debug(_webApp + " map (uri:" + contextURI + " -> " + servletName + ")");

        // ServletFilterChain
        FilterChain chain = null;

        if (config != null)
            chain = _servletManager.createServletChain(config, fcInvocation);

        //put to cache
        _servletChainCache.put(contextURI, chain);

        return chain;
    }

    /**
     * contextURI?Jsp,??JspServletCompositeServletConfig
     * @param contextURI
     * @return
     */
    protected ServletConfigImpl mapJspServlet(String contextURI) {
        if (contextURI != null && contextURI.endsWith(".jsp")) {
            return _servletManager.getServlet(JspServletComposite.class.getCanonicalName());
        }

        return null;
    }

    /**
     * contextURI?Servlet Specification: Servlet-3_1-PFD chapter 12.1
     * 
     * @param contextURI
     * @return
     */
    public ServletConfigImpl mapServlet(String contextURI) {
        // Rule 1 -- Exact Match :?URL?<servler-mapping>
        if (_exactServletMappings.size() > 0) {
            ServletMapping servletMapping = _exactServletMappings.get(contextURI);

            if (servletMapping != null)
                return servletMapping.getServletConfig();
        }

        // Rule 2 -- Longest Prefix Match : ??Servlet,:/page/today/123  ? /page/today/*,?/page/*
        if (_prefixServletMappings.size() > 0) {
            String prefixPattern;
            ServletMapping servletMapping = null;

            for (Map.Entry<String, ServletMapping> entry : _prefixServletMappings.entrySet()) {
                prefixPattern = entry.getKey();

                if (prefixPatternMatch(contextURI, prefixPattern)) {
                    servletMapping = entry.getValue();

                    if (servletMapping != null)
                        return servletMapping.getServletConfig();
                }
            }
        }

        // Rule 3 -- Extension Match : ?.do???
        if (_extensionServletMappings.size() > 0) {
            String extensionPattern;
            ServletMapping servletMapping = null;

            for (Map.Entry<String, ServletMapping> entry : _extensionServletMappings.entrySet()) {
                extensionPattern = entry.getKey();

                if (extensionPatternMatch(contextURI, extensionPattern)) {
                    servletMapping = entry.getValue();

                    if (servletMapping != null)
                        return servletMapping.getServletConfig();
                }
            }
        }

        return null;
    }

    public ServletMapping getServletMapping(String pattern) {
        return _exactServletMappings.get(pattern);
    }

    private void addServlet(String servletName) throws ServletException {
        if (_servletManager.getServlet(servletName) != null)
            return;

        ServletConfigImpl config = _webApp.createNewServletConfig();

        try {
            config.setServletClass(servletName);
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new ServletException(e);
        }

        config.init();

        _servletManager.addServlet(config);
    }

    public void destroy() {
        _servletManager.destroy();
    }

    /**
     * 
     * String?
     * 
     * @author KEN
     * 
     */
    private static class PatternLengthComparator implements Comparator<String> {
        @Override
        public int compare(String pattern1, String pattern2) {
            if (pattern1.length() > pattern2.length())
                return -1;
            else if (pattern1.length() == pattern2.length())
                return 0;
            else
                return 1;
        }
    }

    // util Method---------------------------------------------------------------------------

    /**
     * ??
     * 
     * @param requestPath
     * @param prefixPattern
     * @return
     */
    private static boolean prefixPatternMatch(String requestPath, String prefixPattern) {

        if (prefixPattern == null)
            return (false);

        // Case 2 - Path Match ("/.../*")
        if (prefixPattern.equals("/*"))
            return (true);
        if (prefixPattern.endsWith("/*")) {
            if (prefixPattern.regionMatches(0, requestPath, 0, prefixPattern.length() - 2)) {
                if (requestPath.length() == (prefixPattern.length() - 2)) {
                    return (true);
                } else if ('/' == requestPath.charAt(prefixPattern.length() - 2)) {
                    return (true);
                }
            }
            return (false);
        }

        return (false);
    }

    /**
     * 
     * @param requestPath
     * @param extensionPattern
     * @return
     */
    private static boolean extensionPatternMatch(String requestPath, String extensionPattern) {

        if (extensionPattern == null)
            return (false);

        // Case 3 - Extension Match
        if (extensionPattern.startsWith("*.")) {
            int slash = requestPath.lastIndexOf('/');
            int period = requestPath.lastIndexOf('.');
            if ((slash >= 0) && (period > slash) && (period != requestPath.length() - 1)
                    && ((requestPath.length() - period) == (extensionPattern.length() - 1))) {
                return (extensionPattern.regionMatches(2, requestPath, period + 1, extensionPattern.length() - 2));
            }
        }

        return (false);
    }

}