/* * Copyright (c) 1998-2011 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 com.caucho.server.dispatch; import java.io.InputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import javax.servlet.FilterChain; import javax.servlet.ServletException; import com.caucho.config.ConfigException; import com.caucho.make.DependencyContainer; import com.caucho.server.webapp.WebApp; import com.caucho.util.L10N; import com.caucho.vfs.Depend; import com.caucho.vfs.Path; /** * Manages dispatching: servlets and filters. */ public class ServletMapper { private static final Logger log = Logger.getLogger(ServletMapper.class.getName()); private static final L10N L = new L10N(ServletMapper.class); private static final HashSet<String> _welcomeFileResourceMap = new HashSet<String>(); private WebApp _webApp; private ServletManager _servletManager; private UrlMap<ServletMapping> _servletMap = new UrlMap<ServletMapping>(); private HashMap<String,ServletMapping> _regexpMap = new HashMap<String,ServletMapping>(); private ArrayList<String> _ignorePatterns = new ArrayList<String>(); private String _defaultServlet; //Servlet 3.0 maps serletName to urlPattern private Map<String, Set<String>> _urlPatterns = new HashMap<String, Set<String>>(); //Servlet 3.0 urlPattern to servletName private Map<String, String> _servletNamesMap = new HashMap<String, String>(); public ServletMapper(WebApp webApp) { _webApp = webApp; } /** * Gets the servlet context. */ public WebApp getWebApp() { return _webApp; } /** * Returns the servlet manager. */ public ServletManager getServletManager() { return _servletManager; } /** * Sets the servlet manager. */ public void setServletManager(ServletManager manager) { _servletManager = manager; } /** * Adds a servlet mapping */ public void addUrlRegexp(String regexp, String servletName, ServletMapping mapping) throws ServletException { _servletMap.addRegexp(regexp, mapping); _regexpMap.put(servletName, mapping); } /** * Adds a servlet mapping */ void addUrlMapping(final String urlPattern, String servletName, ServletMapping mapping, boolean ifAbsent) throws ServletException { try { boolean isIgnore = false; if (mapping.isInFragmentMode() && _servletMap.contains(new FragmentFilter(servletName))) return; if (servletName == null) { throw new ConfigException(L.l("servlets need a servlet-name.")); } else if (servletName.equals("invoker")) { // special case } else if (servletName.equals("plugin_match") || servletName.equals("plugin-match")) { // special case isIgnore = true; } else if (servletName.equals("plugin_ignore") || servletName.equals("plugin-ignore")) { if (urlPattern != null) _ignorePatterns.add(urlPattern); return; } else if (mapping.getBean() != null) { } else if (_servletManager.getServlet(servletName) == null) throw new ConfigException(L.l("'{0}' is an unknown servlet-name. servlet-mapping requires that the named servlet be defined in a <servlet> configuration before the <servlet-mapping>.", servletName)); if ("/".equals(urlPattern)) { _defaultServlet = servletName; } else if (mapping.isStrictMapping()) { _servletMap.addStrictMap(urlPattern, null, mapping); } else _servletMap.addMap(urlPattern, mapping, isIgnore, ifAbsent); Set<String> patterns = _urlPatterns.get(servletName); if (patterns == null) { patterns = new HashSet<String>(); _urlPatterns.put(servletName, patterns); } _servletNamesMap.put(urlPattern, servletName); patterns.add(urlPattern); log.config("servlet-mapping " + urlPattern + " -> " + servletName); } catch (ServletException e) { throw e; } 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. */ public void setDefaultServlet(String servletName) throws ServletException { _defaultServlet = servletName; } public FilterChain mapServlet(ServletInvocation invocation) throws ServletException { String contextURI = invocation.getContextURI(); String servletName = null; ArrayList<String> vars = new ArrayList<String>(); invocation.setClassLoader(Thread.currentThread().getContextClassLoader()); ServletConfigImpl config = null; ServletMapping servletRegexp = null; if (_servletMap != null) { ServletMapping servletMap = _servletMap.map(contextURI, vars); if (servletMap != null && servletMap.isServletConfig()) config = servletMap; if (servletMap != null) { servletRegexp = servletMap.initRegexpConfig(vars); if (servletRegexp != null) { try { servletRegexp.getServletClass(); } catch (Exception e) { log.log(Level.FINER, e.toString(), e); return new ErrorFilterChain(404); } } } if (servletRegexp != null) { servletName = servletRegexp.getServletName(); } else if (servletMap != null) { servletName = servletMap.getServletName(); } } if (servletName == null) { try { InputStream is; is = _webApp.getResourceAsStream(contextURI); if (is != null) { is.close(); servletName = _defaultServlet; } } catch (Exception e) { } } MatchResult matchResult = null; if (matchResult == null && contextURI.endsWith("j_security_check")) { servletName = "j_security_check"; } if (servletName == null) { // matchResult = matchWelcomeFileResource(invocation, vars); matchResult = null; if (matchResult != null) servletName = matchResult.getServletName(); if (matchResult != null && ! contextURI.endsWith("/") && ! (invocation instanceof SubInvocation)) { String contextPath = invocation.getContextPath(); return new RedirectFilterChain(contextPath + contextURI + "/"); } if (matchResult != null && invocation instanceof Invocation) { Invocation inv = (Invocation) invocation; inv.setContextURI(matchResult.getContextUri()); // server/10r9 // inv.setRawURI(inv.getRawURI() + file); } } if (servletName == null) { servletName = _defaultServlet; vars.clear(); if (matchResult != null) vars.add(matchResult.getContextUri()); else vars.add(contextURI); addWelcomeFileDependency(invocation); } if (servletName == null) { log.fine(L.l("'{0}' has no default servlet defined", contextURI)); return new ErrorFilterChain(404); } String servletPath = vars.get(0); invocation.setServletPath(servletPath); if (servletPath.length() < contextURI.length()) invocation.setPathInfo(contextURI.substring(servletPath.length())); else invocation.setPathInfo(null); if (servletRegexp != null) config = servletRegexp; if (servletName.equals("invoker")) servletName = handleInvoker(invocation); invocation.setServletName(servletName); if (log.isLoggable(Level.FINER)) { log.finer(_webApp + " map (uri:" + contextURI + " -> " + servletName + ")"); } // server/13f1 ServletConfigImpl newConfig = _servletManager.getServlet(servletName); if (newConfig != null) config = newConfig; if (config != null) { invocation.setSecurityRoleMap(config.getRoleMap()); } FilterChain chain = _servletManager.createServletChain(servletName, config, invocation); if (chain instanceof PageFilterChain) { PageFilterChain pageChain = (PageFilterChain) chain; chain = PrecompilePageFilterChain.create(invocation, pageChain); } return chain; } private void addWelcomeFileDependency(ServletInvocation servletInvocation) { if (! (servletInvocation instanceof Invocation)) return; Invocation invocation = (Invocation) servletInvocation; String contextURI = invocation.getContextURI(); DependencyContainer dependencyList = new DependencyContainer(); WebApp webApp = (WebApp) _webApp; String uriRealPath = webApp.getRealPath(contextURI); Path contextPath = webApp.getRootDirectory().lookup(uriRealPath); if (! contextPath.isDirectory()) return; ArrayList<String> welcomeFileList = webApp.getWelcomeFileList(); for (int i = 0; i < welcomeFileList.size(); i++) { String file = welcomeFileList.get(i); String realPath = webApp.getRealPath(contextURI + "/" + file); Path path = webApp.getRootDirectory().lookup(realPath); dependencyList.add(new Depend(path)); } dependencyList.clearModified(); invocation.setDependency(dependencyList); } private String handleInvoker(ServletInvocation invocation) throws ServletException { String tail; if (invocation.getPathInfo() != null) tail = invocation.getPathInfo(); else tail = invocation.getServletPath(); // XXX: this is really an unexpected, internal error that should never // happen if (! tail.startsWith("/")) { throw new ConfigException("expected '/' starting " + " sp:" + invocation.getServletPath() + " pi:" + invocation.getPathInfo() + " sn:invocation" + invocation); } int next = tail.indexOf('/', 1); String servletName; if (next < 0) servletName = tail.substring(1); else servletName = tail.substring(1, next); // XXX: This should be generalized, possibly with invoker configuration if (servletName.startsWith("com.caucho")) { throw new ConfigException(L.l("servlet '{0}' forbidden from invoker. com.caucho.* classes must be defined explicitly in a <servlet> declaration.", servletName)); } else if (servletName.equals("")) { throw new ConfigException(L.l("invoker needs a servlet name in URL '{0}'.", invocation.getContextURI())); } addServlet(servletName); String servletPath = invocation.getServletPath(); if (invocation.getPathInfo() == null) { } else if (next < 0) { invocation.setServletPath(servletPath + tail); invocation.setPathInfo(null); } else if (next < tail.length()) { invocation.setServletPath(servletPath + tail.substring(0, next)); invocation.setPathInfo(tail.substring(next)); } else { invocation.setServletPath(servletPath + tail); invocation.setPathInfo(null); } return servletName; } public String getServletPattern(String uri) { ArrayList<String> vars = new ArrayList<String>(); Object value = null; if (_servletMap != null) value = _servletMap.map(uri, vars); if (value != null) return uri; else return null; } public String getServletClassByUri(String uri) { ArrayList<String> vars = new ArrayList<String>(); ServletMapping value = null; if (_servletMap != null) value = _servletMap.map(uri, vars); if (value != null) { Class<?> servletClass = value.getServletClass(vars); if (servletClass != null) return servletClass.getName(); else { String servletName = value.getServletName(); ServletConfigImpl config = _servletManager.getServlet(servletName); if (config != null) return config.getServletClassName(); else return servletName; } } else return null; } /** * Returns the servlet matching patterns. */ public ArrayList<String> getURLPatterns() { ArrayList<String> patterns = _servletMap.getURLPatterns(); return patterns; } public String getServletName(String pattern) { return _servletNamesMap.get(pattern); } /** * Returns the servlet plugin_ignore patterns. */ public ArrayList<String> getIgnorePatterns() { return _ignorePatterns; } private void addServlet(String servletName) throws ServletException { if (_servletManager.getServlet(servletName) != null) return; ServletConfigImpl config = new ServletConfigImpl(); config.setServletContext(_webApp); config.setServletName(servletName); 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(); } private class FragmentFilter implements UrlMap.Filter<ServletMapping> { private String _servletName; public FragmentFilter(String servletName) { _servletName = servletName; } @Override public boolean isMatch(ServletMapping item) { return _servletName.equals(item.getServletName()); } } private static class MatchResult { String _servletName; String _contextUri; private MatchResult(String servletName, String contextUri) { _servletName = servletName; _contextUri = contextUri; } public String getServletName() { return _servletName; } public String getContextUri() { return _contextUri; } } static { _welcomeFileResourceMap.add("com.caucho.servlets.FileServlet"); _welcomeFileResourceMap.add("com.caucho.jsp.JspServlet"); _welcomeFileResourceMap.add("com.caucho.jsp.JspXmlServlet"); _welcomeFileResourceMap.add("com.caucho.quercus.servlet.QuercusServlet"); _welcomeFileResourceMap.add("com.caucho.jsp.XtpServlet"); } }