0001: package com.meterware.servletunit;
0002:
0003: /********************************************************************************************************************
0004: * $Id: WebApplication.java,v 1.27 2006/03/24 19:59:12 russgold Exp $
0005: *
0006: * Copyright (c) 2001-2004, 2006 Russell Gold
0007: *
0008: * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
0009: * documentation files (the "Software"), to deal in the Software without restriction, including without limitation
0010: * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
0011: * to permit persons to whom the Software is furnished to do so, subject to the following conditions:
0012: *
0013: * The above copyright notice and this permission notice shall be included in all copies or substantial portions
0014: * of the Software.
0015: *
0016: * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
0017: * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
0018: * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
0019: * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
0020: * DEALINGS IN THE SOFTWARE.
0021: *
0022: *******************************************************************************************************************/
0023: import com.meterware.httpunit.HttpInternalErrorException;
0024: import com.meterware.httpunit.HttpNotFoundException;
0025:
0026: import java.io.File;
0027: import java.io.IOException;
0028: import java.net.MalformedURLException;
0029: import java.net.URL;
0030: import java.util.*;
0031:
0032: import javax.servlet.*;
0033: import javax.servlet.http.*;
0034:
0035: import org.w3c.dom.Document;
0036: import org.w3c.dom.Element;
0037: import org.w3c.dom.NodeList;
0038: import org.xml.sax.SAXException;
0039:
0040: /**
0041: * This class represents the information recorded about a single web
0042: * application. It is usually extracted from web.xml.
0043: *
0044: * @author <a href="mailto:russgold@httpunit.org">Russell Gold</a>
0045: * @author <a href="balld@webslingerZ.com">Donald Ball</a>
0046: * @author <a href="jaydunning@users.sourceforge.net">Jay Dunning</a>
0047: **/
0048: class WebApplication implements SessionListenerDispatcher {
0049:
0050: private final static SecurityConstraint NULL_SECURITY_CONSTRAINT = new NullSecurityConstraint();
0051:
0052: private final ServletConfiguration SECURITY_CHECK_CONFIGURATION = new ServletConfiguration(
0053: SecurityCheckServlet.class.getName());
0054:
0055: private final WebResourceMapping SECURITY_CHECK_MAPPING = new WebResourceMapping(
0056: SECURITY_CHECK_CONFIGURATION);
0057:
0058: /** A mapping of resource names to servlet configurations. **/
0059: private WebResourceMap _servletMapping = new WebResourceMap();
0060:
0061: /** A mapping of resource names to filter configurations. **/
0062: private FilterUrlMap _filterUrlMapping = new FilterUrlMap();
0063:
0064: /** A mapping of servlet names to filter configurations. **/
0065: private Hashtable _filterMapping = new Hashtable();
0066:
0067: private ArrayList _securityConstraints = new ArrayList();
0068:
0069: private ArrayList _contextListeners = new ArrayList();
0070:
0071: private ArrayList _contextAttributeListeners = new ArrayList();
0072:
0073: private ArrayList _sessionListeners = new ArrayList();
0074:
0075: private ArrayList _sessionAttributeListeners = new ArrayList();
0076:
0077: private boolean _useBasicAuthentication;
0078:
0079: private boolean _useFormAuthentication;
0080:
0081: private String _authenticationRealm = "";
0082:
0083: private URL _loginURL;
0084:
0085: private URL _errorURL;
0086:
0087: private Hashtable _contextParameters = new Hashtable();
0088:
0089: private File _contextDir = null;
0090:
0091: private String _contextPath = null;
0092:
0093: private ServletUnitServletContext _servletContext;
0094:
0095: private String _displayName;
0096:
0097: /**
0098: * Constructs a default application spec with no information.
0099: */
0100: WebApplication() {
0101: _contextPath = "";
0102: }
0103:
0104: /**
0105: * Constructs an application spec from an XML document.
0106: */
0107: WebApplication(Document document) throws MalformedURLException,
0108: SAXException {
0109: this (document, null, "");
0110: }
0111:
0112: /**
0113: * Constructs an application spec from an XML document.
0114: */
0115: WebApplication(Document document, String contextPath)
0116: throws MalformedURLException, SAXException {
0117: this (document, null, contextPath);
0118: }
0119:
0120: /**
0121: * Constructs an application spec from an XML document.
0122: */
0123: WebApplication(Document document, File file, String contextPath)
0124: throws MalformedURLException, SAXException {
0125: if (contextPath != null && contextPath.length() > 0
0126: && !contextPath.startsWith("/"))
0127: throw new IllegalArgumentException("Context path "
0128: + contextPath + " must start with '/'");
0129: _contextDir = file;
0130: _contextPath = contextPath == null ? "" : contextPath;
0131: NodeList nl = document.getElementsByTagName("display-name");
0132: if (nl.getLength() > 0)
0133: _displayName = XMLUtils.getTextValue(nl.item(0)).trim();
0134:
0135: registerServlets(document);
0136: registerFilters(document);
0137: extractSecurityConstraints(document);
0138: extractContextParameters(document);
0139: extractLoginConfiguration(document);
0140: extractListeners(document);
0141: notifyContextInitialized();
0142: _servletMapping.autoLoadServlets();
0143: }
0144:
0145: private void extractListeners(Document document)
0146: throws SAXException {
0147: NodeList nl = document.getElementsByTagName("listener");
0148: for (int i = 0; i < nl.getLength(); i++) {
0149: String listenerName = XMLUtils.getChildNodeValue(
0150: (Element) nl.item(i), "listener-class").trim();
0151: try {
0152: Object listener = Class.forName(listenerName)
0153: .newInstance();
0154:
0155: if (listener instanceof ServletContextListener)
0156: _contextListeners.add(listener);
0157: if (listener instanceof ServletContextAttributeListener)
0158: _contextAttributeListeners.add(listener);
0159: if (listener instanceof HttpSessionListener)
0160: _sessionListeners.add(listener);
0161: if (listener instanceof HttpSessionAttributeListener)
0162: _sessionAttributeListeners.add(listener);
0163: } catch (Throwable e) {
0164: throw new RuntimeException(
0165: "Unable to load context listener "
0166: + listenerName + ": " + e.toString());
0167: }
0168: }
0169: }
0170:
0171: private void notifyContextInitialized() {
0172: ServletContextEvent event = new ServletContextEvent(
0173: getServletContext());
0174:
0175: for (Iterator i = _contextListeners.iterator(); i.hasNext();) {
0176: ServletContextListener listener = (ServletContextListener) i
0177: .next();
0178: listener.contextInitialized(event);
0179: }
0180: }
0181:
0182: void shutDown() {
0183: destroyServlets();
0184: notifyContextDestroyed();
0185: }
0186:
0187: private void notifyContextDestroyed() {
0188: ServletContextEvent event = new ServletContextEvent(
0189: getServletContext());
0190:
0191: for (ListIterator i = _contextListeners
0192: .listIterator(_contextListeners.size()); i
0193: .hasPrevious();) {
0194: ServletContextListener listener = (ServletContextListener) i
0195: .previous();
0196: listener.contextDestroyed(event);
0197: }
0198: }
0199:
0200: void sendAttributeAdded(String name, Object value) {
0201: ServletContextAttributeEvent event = new ServletContextAttributeEvent(
0202: getServletContext(), name, value);
0203:
0204: for (Iterator i = _contextAttributeListeners.iterator(); i
0205: .hasNext();) {
0206: ServletContextAttributeListener listener = (ServletContextAttributeListener) i
0207: .next();
0208: listener.attributeAdded(event);
0209: }
0210: }
0211:
0212: void sendAttributeReplaced(String name, Object value) {
0213: ServletContextAttributeEvent event = new ServletContextAttributeEvent(
0214: getServletContext(), name, value);
0215:
0216: for (Iterator i = _contextAttributeListeners.iterator(); i
0217: .hasNext();) {
0218: ServletContextAttributeListener listener = (ServletContextAttributeListener) i
0219: .next();
0220: listener.attributeReplaced(event);
0221: }
0222: }
0223:
0224: void sendAttributeRemoved(String name, Object value) {
0225: ServletContextAttributeEvent event = new ServletContextAttributeEvent(
0226: getServletContext(), name, value);
0227:
0228: for (Iterator i = _contextAttributeListeners.iterator(); i
0229: .hasNext();) {
0230: ServletContextAttributeListener listener = (ServletContextAttributeListener) i
0231: .next();
0232: listener.attributeRemoved(event);
0233: }
0234: }
0235:
0236: private void extractSecurityConstraints(Document document)
0237: throws SAXException {
0238: NodeList nl = document
0239: .getElementsByTagName("security-constraint");
0240: for (int i = 0; i < nl.getLength(); i++) {
0241: _securityConstraints.add(new SecurityConstraintImpl(
0242: (Element) nl.item(i)));
0243: }
0244: }
0245:
0246: String getContextPath() {
0247: return _contextPath;
0248: }
0249:
0250: ServletContext getServletContext() {
0251: if (_servletContext == null) {
0252: _servletContext = new ServletUnitServletContext(this );
0253: }
0254: return _servletContext;
0255: }
0256:
0257: /**
0258: * Registers a servlet class to be run.
0259: **/
0260: void registerServlet(String resourceName, String servletClassName,
0261: Hashtable initParams) {
0262: registerServlet(resourceName, new ServletConfiguration(
0263: servletClassName, initParams));
0264: }
0265:
0266: /**
0267: * Registers a servlet to be run.
0268: **/
0269: void registerServlet(String resourceName,
0270: ServletConfiguration servletConfiguration) {
0271: // FIXME - shouldn't everything start with one or the other?
0272: if (!resourceName.startsWith("/")
0273: && !resourceName.startsWith("*")) {
0274: resourceName = "/" + resourceName;
0275: }
0276: _servletMapping.put(resourceName, servletConfiguration);
0277: }
0278:
0279: /**
0280: * Calls the destroy method for every active servlet.
0281: */
0282: void destroyServlets() {
0283: _servletMapping.destroyWebResources();
0284: }
0285:
0286: ServletMetaData getServletRequest(URL url) {
0287: return _servletMapping.get(url);
0288: }
0289:
0290: /**
0291: * Returns true if this application uses Basic Authentication.
0292: */
0293: boolean usesBasicAuthentication() {
0294: return _useBasicAuthentication;
0295: }
0296:
0297: /**
0298: * Returns true if this application uses form-based authentication.
0299: */
0300: boolean usesFormAuthentication() {
0301: return _useFormAuthentication;
0302: }
0303:
0304: String getAuthenticationRealm() {
0305: return _authenticationRealm;
0306: }
0307:
0308: URL getLoginURL() {
0309: return _loginURL;
0310: }
0311:
0312: URL getErrorURL() {
0313: return _errorURL;
0314: }
0315:
0316: /**
0317: * Returns true if the specified path may only be accesses by an authorized user.
0318: * @param url the application-relative path of the URL
0319: */
0320: boolean requiresAuthorization(URL url) {
0321: String result;
0322: String file = url.getFile();
0323: if (_contextPath.equals("")) {
0324: result = file;
0325: } else if (file.startsWith(_contextPath)) {
0326: result = file.substring(_contextPath.length());
0327: } else {
0328: result = null;
0329: }
0330: return getControllingConstraint(result) != NULL_SECURITY_CONSTRAINT;
0331: }
0332:
0333: /**
0334: * Returns an array containing the roles permitted to access the specified URL.
0335: */
0336: String[] getPermittedRoles(URL url) {
0337: String result;
0338: String file = url.getFile();
0339: if (_contextPath.equals("")) {
0340: result = file;
0341: } else if (file.startsWith(_contextPath)) {
0342: result = file.substring(_contextPath.length());
0343: } else {
0344: result = null;
0345: }
0346: return getControllingConstraint(result).getPermittedRoles();
0347: }
0348:
0349: private SecurityConstraint getControllingConstraint(String urlPath) {
0350: for (Iterator i = _securityConstraints.iterator(); i.hasNext();) {
0351: SecurityConstraint sc = (SecurityConstraint) i.next();
0352: if (sc.controlsPath(urlPath))
0353: return sc;
0354: }
0355: return NULL_SECURITY_CONSTRAINT;
0356: }
0357:
0358: File getResourceFile(String path) {
0359: String relativePath = path.startsWith("/") ? path.substring(1)
0360: : path;
0361: if (_contextDir == null) {
0362: return new File(relativePath);
0363: } else {
0364: return new File(_contextDir, relativePath);
0365: }
0366: }
0367:
0368: Hashtable getContextParameters() {
0369: return _contextParameters;
0370: }
0371:
0372: //---------------------------------------- SessionListenerDispatcher methods -------------------------------------------
0373:
0374: public void sendSessionCreated(HttpSession session) {
0375: HttpSessionEvent event = new HttpSessionEvent(session);
0376:
0377: for (Iterator i = _sessionListeners.iterator(); i.hasNext();) {
0378: HttpSessionListener listener = (HttpSessionListener) i
0379: .next();
0380: listener.sessionCreated(event);
0381: }
0382: }
0383:
0384: public void sendSessionDestroyed(HttpSession session) {
0385: HttpSessionEvent event = new HttpSessionEvent(session);
0386:
0387: for (Iterator i = _sessionListeners.iterator(); i.hasNext();) {
0388: HttpSessionListener listener = (HttpSessionListener) i
0389: .next();
0390: listener.sessionDestroyed(event);
0391: }
0392: }
0393:
0394: public void sendAttributeAdded(HttpSession session, String name,
0395: Object value) {
0396: HttpSessionBindingEvent event = new HttpSessionBindingEvent(
0397: session, name, value);
0398:
0399: for (Iterator i = _sessionAttributeListeners.iterator(); i
0400: .hasNext();) {
0401: HttpSessionAttributeListener listener = (HttpSessionAttributeListener) i
0402: .next();
0403: listener.attributeAdded(event);
0404: }
0405: }
0406:
0407: public void sendAttributeReplaced(HttpSession session, String name,
0408: Object oldValue) {
0409: HttpSessionBindingEvent event = new HttpSessionBindingEvent(
0410: session, name, oldValue);
0411:
0412: for (Iterator i = _sessionAttributeListeners.iterator(); i
0413: .hasNext();) {
0414: HttpSessionAttributeListener listener = (HttpSessionAttributeListener) i
0415: .next();
0416: listener.attributeReplaced(event);
0417: }
0418: }
0419:
0420: public void sendAttributeRemoved(HttpSession session, String name,
0421: Object oldValue) {
0422: HttpSessionBindingEvent event = new HttpSessionBindingEvent(
0423: session, name, oldValue);
0424:
0425: for (Iterator i = _sessionAttributeListeners.iterator(); i
0426: .hasNext();) {
0427: HttpSessionAttributeListener listener = (HttpSessionAttributeListener) i
0428: .next();
0429: listener.attributeRemoved(event);
0430: }
0431: }
0432:
0433: //--------------------------------------------------- private members --------------------------------------------------
0434:
0435: private void registerFilters(Document document) throws SAXException {
0436: Hashtable nameToClass = new Hashtable();
0437: NodeList nl = document.getElementsByTagName("filter");
0438: for (int i = 0; i < nl.getLength(); i++)
0439: registerFilterClass(nameToClass, (Element) nl.item(i));
0440: nl = document.getElementsByTagName("filter-mapping");
0441: for (int i = 0; i < nl.getLength(); i++)
0442: registerFilter(nameToClass, (Element) nl.item(i));
0443: }
0444:
0445: private void registerFilterClass(Dictionary mapping,
0446: Element filterElement) throws SAXException {
0447: String filterName = XMLUtils.getChildNodeValue(filterElement,
0448: "filter-name");
0449: mapping.put(filterName, new FilterConfiguration(filterName,
0450: filterElement));
0451: }
0452:
0453: private void registerFilter(Dictionary mapping,
0454: Element filterElement) throws SAXException {
0455: if (XMLUtils.hasChildNode(filterElement, "servlet-name")) {
0456: registerFilterForServlet(XMLUtils.getChildNodeValue(
0457: filterElement, "servlet-name"),
0458: (FilterConfiguration) mapping.get(XMLUtils
0459: .getChildNodeValue(filterElement,
0460: "filter-name")));
0461: }
0462: if (XMLUtils.hasChildNode(filterElement, "url-pattern")) {
0463: registerFilterForUrl(XMLUtils.getChildNodeValue(
0464: filterElement, "url-pattern"),
0465: (FilterConfiguration) mapping.get(XMLUtils
0466: .getChildNodeValue(filterElement,
0467: "filter-name")));
0468: }
0469: }
0470:
0471: private void registerFilterForUrl(String resourceName,
0472: FilterConfiguration filterConfiguration) {
0473: _filterUrlMapping.put(resourceName, filterConfiguration);
0474: }
0475:
0476: private void registerFilterForServlet(String servletName,
0477: FilterConfiguration filterConfiguration) {
0478: List list = (List) _filterMapping.get(servletName);
0479: if (list == null) {
0480: list = new ArrayList();
0481: _filterMapping.put(servletName, list);
0482: }
0483: list.add(filterConfiguration);
0484: }
0485:
0486: private void extractLoginConfiguration(Document document)
0487: throws MalformedURLException, SAXException {
0488: NodeList nl = document.getElementsByTagName("login-config");
0489: if (nl.getLength() == 1) {
0490: final Element loginConfigElement = (Element) nl.item(0);
0491: String authenticationMethod = XMLUtils.getChildNodeValue(
0492: loginConfigElement, "auth-method", "BASIC");
0493: _authenticationRealm = XMLUtils.getChildNodeValue(
0494: loginConfigElement, "realm-name", "");
0495: if (authenticationMethod.equalsIgnoreCase("BASIC")) {
0496: _useBasicAuthentication = true;
0497: if (_authenticationRealm.length() == 0)
0498: throw new SAXException(
0499: "No realm specified for BASIC Authorization");
0500: } else if (authenticationMethod.equalsIgnoreCase("FORM")) {
0501: _useFormAuthentication = true;
0502: if (_authenticationRealm.length() == 0)
0503: throw new SAXException(
0504: "No realm specified for FORM Authorization");
0505: _loginURL = new URL("http", "localhost", _contextPath
0506: + XMLUtils.getChildNodeValue(
0507: loginConfigElement, "form-login-page"));
0508: _errorURL = new URL("http", "localhost", _contextPath
0509: + XMLUtils.getChildNodeValue(
0510: loginConfigElement, "form-error-page"));
0511: }
0512: }
0513: }
0514:
0515: private void registerServlets(Document document)
0516: throws SAXException {
0517: Hashtable nameToClass = new Hashtable();
0518: NodeList nl = document.getElementsByTagName("servlet");
0519: for (int i = 0; i < nl.getLength(); i++)
0520: registerServletClass(nameToClass, (Element) nl.item(i));
0521: nl = document.getElementsByTagName("servlet-mapping");
0522: for (int i = 0; i < nl.getLength(); i++)
0523: registerServlet(nameToClass, (Element) nl.item(i));
0524: }
0525:
0526: private void registerServletClass(Dictionary mapping,
0527: Element servletElement) throws SAXException {
0528: mapping.put(XMLUtils.getChildNodeValue(servletElement,
0529: "servlet-name"), new ServletConfiguration(
0530: servletElement));
0531: }
0532:
0533: private void registerServlet(Dictionary mapping,
0534: Element servletElement) throws SAXException {
0535: registerServlet(XMLUtils.getChildNodeValue(servletElement,
0536: "url-pattern"), (ServletConfiguration) mapping
0537: .get(XMLUtils.getChildNodeValue(servletElement,
0538: "servlet-name")));
0539: }
0540:
0541: private void extractContextParameters(Document document)
0542: throws SAXException {
0543: NodeList nl = document.getElementsByTagName("context-param");
0544: for (int i = 0; i < nl.getLength(); i++) {
0545: Element param = (Element) nl.item(i);
0546: String name = XMLUtils.getChildNodeValue(param,
0547: "param-name");
0548: String value = XMLUtils.getChildNodeValue(param,
0549: "param-value");
0550: _contextParameters.put(name, value);
0551: }
0552: }
0553:
0554: private static boolean patternMatches(String urlPattern,
0555: String urlPath) {
0556: return urlPattern.equals(urlPath);
0557: }
0558:
0559: String getDisplayName() {
0560: return _displayName;
0561: }
0562:
0563: //============================================= SecurityCheckServlet class =============================================
0564:
0565: static class SecurityCheckServlet extends HttpServlet {
0566:
0567: protected void doGet(HttpServletRequest req,
0568: HttpServletResponse resp) throws ServletException,
0569: IOException {
0570: handleLogin(req, resp);
0571: }
0572:
0573: protected void doPost(HttpServletRequest req,
0574: HttpServletResponse resp) throws ServletException,
0575: IOException {
0576: handleLogin(req, resp);
0577: }
0578:
0579: private void handleLogin(HttpServletRequest req,
0580: HttpServletResponse resp) throws IOException {
0581: final String username = req.getParameter("j_username");
0582: final String roleList = req.getParameter("j_password");
0583: getServletSession(req).setUserInformation(username,
0584: ServletUnitHttpRequest.toArray(roleList));
0585: resp.sendRedirect(getServletSession(req).getOriginalURL()
0586: .toExternalForm());
0587: }
0588:
0589: private ServletUnitHttpSession getServletSession(
0590: HttpServletRequest req) {
0591: return (ServletUnitHttpSession) req.getSession();
0592: }
0593:
0594: }
0595:
0596: //============================================= ServletConfiguration class =============================================
0597:
0598: final static int DONT_AUTOLOAD = Integer.MIN_VALUE;
0599: final static int ANY_LOAD_ORDER = Integer.MAX_VALUE;
0600:
0601: class ServletConfiguration extends WebResourceConfiguration {
0602:
0603: private Servlet _servlet;
0604: private String _servletName;
0605: private int _loadOrder = DONT_AUTOLOAD;
0606:
0607: ServletConfiguration(String className) {
0608: super (className);
0609: }
0610:
0611: ServletConfiguration(String className, Hashtable initParams) {
0612: super (className, initParams);
0613: }
0614:
0615: ServletConfiguration(Element servletElement)
0616: throws SAXException {
0617: super (servletElement, "servlet-class");
0618: _servletName = XMLUtils.getChildNodeValue(servletElement,
0619: "servlet-name");
0620: final NodeList loadOrder = servletElement
0621: .getElementsByTagName("load-on-startup");
0622: for (int i = 0; i < loadOrder.getLength(); i++) {
0623: String order = XMLUtils.getTextValue(loadOrder.item(i));
0624: try {
0625: _loadOrder = Integer.parseInt(order);
0626: } catch (NumberFormatException e) {
0627: _loadOrder = ANY_LOAD_ORDER;
0628: }
0629: }
0630: }
0631:
0632: synchronized Servlet getServlet()
0633: throws ClassNotFoundException, InstantiationException,
0634: IllegalAccessException, ServletException {
0635: if (_servlet == null) {
0636: Class servletClass = Class.forName(getClassName());
0637: _servlet = (Servlet) servletClass.newInstance();
0638: String servletName = _servletName != null ? _servletName
0639: : _servlet.getClass().getName();
0640: _servlet.init(new ServletUnitServletConfig(servletName,
0641: WebApplication.this , getInitParams()));
0642: }
0643:
0644: return _servlet;
0645: }
0646:
0647: synchronized void destroyResource() {
0648: if (_servlet != null)
0649: _servlet.destroy();
0650: }
0651:
0652: String getServletName() {
0653: return _servletName;
0654: }
0655:
0656: boolean isLoadOnStartup() {
0657: return _loadOrder != DONT_AUTOLOAD;
0658: }
0659:
0660: public int getLoadOrder() {
0661: return _loadOrder;
0662: }
0663: }
0664:
0665: //============================================= FilterConfiguration class =============================================
0666:
0667: class FilterConfiguration extends WebResourceConfiguration
0668: implements FilterMetaData {
0669:
0670: private Filter _filter;
0671: private String _name;
0672:
0673: FilterConfiguration(String name, Element filterElement)
0674: throws SAXException {
0675: super (filterElement, "filter-class");
0676: _name = name;
0677: }
0678:
0679: public synchronized Filter getFilter() throws ServletException {
0680: try {
0681: if (_filter == null) {
0682: Class filterClass = Class.forName(getClassName());
0683: _filter = (Filter) filterClass.newInstance();
0684: _filter.init(new FilterConfigImpl(_name,
0685: getServletContext(), getInitParams()));
0686: }
0687:
0688: return _filter;
0689: } catch (ClassNotFoundException e) {
0690: throw new ServletException(
0691: "Did not find filter class: " + getClassName());
0692: } catch (IllegalAccessException e) {
0693: throw new ServletException("Filter class "
0694: + getClassName()
0695: + " lacks a public no-arg constructor");
0696: } catch (InstantiationException e) {
0697: throw new ServletException("Filter class "
0698: + getClassName()
0699: + " could not be instantiated.");
0700: } catch (ClassCastException e) {
0701: throw new ServletException("Filter class "
0702: + getClassName() + " does not implement"
0703: + Filter.class.getName());
0704: }
0705: }
0706:
0707: boolean isLoadOnStartup() {
0708: return false;
0709: }
0710:
0711: synchronized void destroyResource() {
0712: if (_filter != null)
0713: _filter.destroy();
0714: }
0715: }
0716:
0717: //=================================== SecurityConstract interface and implementations ==================================
0718:
0719: interface SecurityConstraint {
0720:
0721: boolean controlsPath(String urlPath);
0722:
0723: String[] getPermittedRoles();
0724: }
0725:
0726: static class NullSecurityConstraint implements SecurityConstraint {
0727:
0728: private static final String[] NO_ROLES = new String[0];
0729:
0730: public boolean controlsPath(String urlPath) {
0731: return false;
0732: }
0733:
0734: public String[] getPermittedRoles() {
0735: return NO_ROLES;
0736: }
0737: }
0738:
0739: static class SecurityConstraintImpl implements SecurityConstraint {
0740:
0741: SecurityConstraintImpl(Element root) throws SAXException {
0742: final NodeList roleNames = root
0743: .getElementsByTagName("role-name");
0744: for (int i = 0; i < roleNames.getLength(); i++)
0745: _roleList.add(XMLUtils.getTextValue(roleNames.item(i)));
0746:
0747: final NodeList resources = root
0748: .getElementsByTagName("web-resource-collection");
0749: for (int i = 0; i < resources.getLength(); i++)
0750: _resources.add(new WebResourceCollection(
0751: (Element) resources.item(i)));
0752: }
0753:
0754: public boolean controlsPath(String urlPath) {
0755: return getMatchingCollection(urlPath) != null;
0756: }
0757:
0758: public String[] getPermittedRoles() {
0759: if (_roles == null) {
0760: _roles = (String[]) _roleList
0761: .toArray(new String[_roleList.size()]);
0762: }
0763: return _roles;
0764: }
0765:
0766: private String[] _roles;
0767: private ArrayList _roleList = new ArrayList();
0768: private ArrayList _resources = new ArrayList();
0769:
0770: public WebResourceCollection getMatchingCollection(
0771: String urlPath) {
0772: for (Iterator i = _resources.iterator(); i.hasNext();) {
0773: WebResourceCollection wrc = (WebResourceCollection) i
0774: .next();
0775: if (wrc.controlsPath(urlPath))
0776: return wrc;
0777: }
0778: return null;
0779: }
0780:
0781: class WebResourceCollection {
0782:
0783: WebResourceCollection(Element root) throws SAXException {
0784: final NodeList urlPatterns = root
0785: .getElementsByTagName("url-pattern");
0786: for (int i = 0; i < urlPatterns.getLength(); i++)
0787: _urlPatterns.add(XMLUtils.getTextValue(urlPatterns
0788: .item(i)));
0789: }
0790:
0791: boolean controlsPath(String urlPath) {
0792: for (Iterator i = _urlPatterns.iterator(); i.hasNext();) {
0793: String pattern = (String) i.next();
0794: if (patternMatches(pattern, urlPath))
0795: return true;
0796: }
0797: return false;
0798: }
0799:
0800: private ArrayList _urlPatterns = new ArrayList();
0801: }
0802: }
0803:
0804: static final FilterMetaData[] NO_FILTERS = new FilterMetaData[0];
0805:
0806: static class ServletRequestImpl implements ServletMetaData {
0807:
0808: private URL _url;
0809: private String _fullServletPath;
0810: private WebResourceMapping _mapping;
0811: private Hashtable _filtersPerName;
0812: private FilterUrlMap _filtersPerUrl;
0813:
0814: ServletRequestImpl(URL url, String servletPath,
0815: WebResourceMapping mapping, Hashtable filtersPerName,
0816: FilterUrlMap filtersPerUrl) {
0817: _url = url;
0818: _fullServletPath = servletPath;
0819: _mapping = mapping;
0820: _filtersPerName = filtersPerName;
0821: _filtersPerUrl = filtersPerUrl;
0822: }
0823:
0824: public Servlet getServlet() throws ServletException {
0825: if (getConfiguration() == null)
0826: throw new HttpNotFoundException(
0827: "No servlet mapping defined", _url);
0828:
0829: try {
0830: return getConfiguration().getServlet();
0831: } catch (ClassNotFoundException e) {
0832: throw new HttpNotFoundException(_url, e);
0833: } catch (IllegalAccessException e) {
0834: throw new HttpInternalErrorException(_url, e);
0835: } catch (InstantiationException e) {
0836: throw new HttpInternalErrorException(_url, e);
0837: } catch (ClassCastException e) {
0838: throw new HttpInternalErrorException(_url, e);
0839: }
0840: }
0841:
0842: public String getServletPath() {
0843: return _mapping == null ? null : _mapping
0844: .getServletPath(_fullServletPath);
0845: }
0846:
0847: public String getPathInfo() {
0848: return _mapping == null ? null : _mapping
0849: .getPathInfo(_fullServletPath);
0850: }
0851:
0852: public FilterMetaData[] getFilters() {
0853: if (getConfiguration() == null)
0854: return NO_FILTERS;
0855:
0856: List filters = new ArrayList();
0857: addFiltersForPath(filters, _fullServletPath);
0858: addFiltersForServletWithName(filters, getConfiguration()
0859: .getServletName());
0860:
0861: return (FilterMetaData[]) filters
0862: .toArray(new FilterMetaData[filters.size()]);
0863: }
0864:
0865: private void addFiltersForPath(List filters,
0866: String fullServletPath) {
0867: FilterMetaData[] matches = _filtersPerUrl
0868: .getMatchingFilters(fullServletPath);
0869: for (int i = 0; i < matches.length; i++) {
0870: filters.add(matches[i]);
0871: }
0872: }
0873:
0874: private void addFiltersForServletWithName(List filters,
0875: String servletName) {
0876: if (servletName == null)
0877: return;
0878: List matches = (List) _filtersPerName.get(servletName);
0879: if (matches != null)
0880: filters.addAll(matches);
0881: }
0882:
0883: private ServletConfiguration getConfiguration() {
0884: return _mapping == null ? null
0885: : (ServletConfiguration) _mapping
0886: .getConfiguration();
0887: }
0888: }
0889:
0890: static class WebResourceMapping {
0891:
0892: private WebResourceConfiguration _configuration;
0893:
0894: WebResourceConfiguration getConfiguration() {
0895: return _configuration;
0896: }
0897:
0898: WebResourceMapping(WebResourceConfiguration configuration) {
0899: _configuration = configuration;
0900: }
0901:
0902: /**
0903: * Returns the portion of the request path which was actually used to select the servlet. This default
0904: * implementation returns the full specified path.
0905: * @param requestPath the full path of the request, relative to the application root.
0906: */
0907: String getServletPath(String requestPath) {
0908: return requestPath;
0909: }
0910:
0911: /**
0912: * Returns the portion of the request path which was not used to select the servlet, and can be
0913: * used as data by the servlet. This default implementation returns null.
0914: * @param requestPath the full path of the request, relative to the application root.
0915: */
0916: String getPathInfo(String requestPath) {
0917: return null;
0918: }
0919:
0920: public void destroyResource() {
0921: getConfiguration().destroyResource();
0922: }
0923: }
0924:
0925: static class PartialMatchWebResourceMapping extends
0926: WebResourceMapping {
0927:
0928: private String _prefix;
0929:
0930: public PartialMatchWebResourceMapping(
0931: WebResourceConfiguration configuration, String prefix) {
0932: super (configuration);
0933: if (!prefix.endsWith("/*"))
0934: throw new IllegalArgumentException(prefix
0935: + " does not end with '/*'");
0936: _prefix = prefix.substring(0, prefix.length() - 2);
0937: }
0938:
0939: String getServletPath(String requestPath) {
0940: return _prefix;
0941: }
0942:
0943: String getPathInfo(String requestPath) {
0944: return requestPath.length() > _prefix.length() ? requestPath
0945: .substring(_prefix.length())
0946: : null;
0947: }
0948: }
0949:
0950: /**
0951: * A utility class for mapping web resources to url patterns. This implements the
0952: * matching algorithm documented in section 10 of the JSDK-2.2 reference.
0953: */
0954: class WebResourceMap {
0955:
0956: private final Map _exactMatches = new HashMap();
0957: private final Map _extensions = new HashMap();
0958: private final Map _urlTree = new HashMap();
0959: private WebResourceMapping _defaultMapping;
0960:
0961: void put(String mapping, WebResourceConfiguration configuration) {
0962: if (mapping.equals("/")) {
0963: _defaultMapping = new WebResourceMapping(configuration);
0964: } else if (mapping.startsWith("*.")) {
0965: _extensions.put(mapping.substring(2),
0966: new WebResourceMapping(configuration));
0967: } else if (!mapping.startsWith("/")
0968: || !mapping.endsWith("/*")) {
0969: _exactMatches.put(mapping, new WebResourceMapping(
0970: configuration));
0971: } else {
0972: ParsedPath path = new ParsedPath(mapping);
0973: Map context = _urlTree;
0974: while (path.hasNext()) {
0975: String part = path.next();
0976: if (part.equals("*")) {
0977: context.put("*",
0978: new PartialMatchWebResourceMapping(
0979: configuration, mapping));
0980: return;
0981: }
0982: if (!context.containsKey(part)) {
0983: context.put(part, new HashMap());
0984: }
0985: context = (Map) context.get(part);
0986: }
0987: }
0988: }
0989:
0990: ServletMetaData get(URL url) {
0991: String file = url.getFile();
0992: if (!file.startsWith(_contextPath))
0993: throw new HttpNotFoundException(
0994: "File path does not begin with '"
0995: + _contextPath + "'", url);
0996:
0997: String servletPath = getServletPath(file
0998: .substring(_contextPath.length()));
0999:
1000: if (servletPath.endsWith("j_security_check")) {
1001: return new ServletRequestImpl(url, servletPath,
1002: SECURITY_CHECK_MAPPING, _filterMapping,
1003: _filterUrlMapping);
1004: } else {
1005: return new ServletRequestImpl(url, servletPath,
1006: getMapping(servletPath), _filterMapping,
1007: _filterUrlMapping);
1008: }
1009: }
1010:
1011: private String getServletPath(String urlFile) {
1012: if (urlFile.indexOf('?') < 0) {
1013: return urlFile;
1014: } else {
1015: return urlFile.substring(0, urlFile.indexOf('?'));
1016: }
1017: }
1018:
1019: public void destroyWebResources() {
1020: if (_defaultMapping != null)
1021: _defaultMapping.destroyResource();
1022: destroyWebResources(_exactMatches);
1023: destroyWebResources(_extensions);
1024: destroyWebResources(_urlTree);
1025: }
1026:
1027: private void destroyWebResources(Map map) {
1028: for (Iterator iterator = map.values().iterator(); iterator
1029: .hasNext();) {
1030: Object o = iterator.next();
1031: if (o instanceof WebResourceMapping) {
1032: WebResourceMapping webResourceMapping = (WebResourceMapping) o;
1033: webResourceMapping.destroyResource();
1034: } else {
1035: destroyWebResources((Map) o);
1036: }
1037: }
1038: }
1039:
1040: void autoLoadServlets() {
1041: ArrayList autoLoadable = new ArrayList();
1042: if (_defaultMapping != null
1043: && _defaultMapping.getConfiguration()
1044: .isLoadOnStartup())
1045: autoLoadable.add(_defaultMapping.getConfiguration());
1046: collectAutoLoadableServlets(autoLoadable, _exactMatches);
1047: collectAutoLoadableServlets(autoLoadable, _extensions);
1048: collectAutoLoadableServlets(autoLoadable, _urlTree);
1049: if (autoLoadable.isEmpty())
1050: return;
1051:
1052: Collections.sort(autoLoadable, new Comparator() {
1053: public int compare(Object o1, Object o2) {
1054: ServletConfiguration sc1 = (ServletConfiguration) o1;
1055: ServletConfiguration sc2 = (ServletConfiguration) o2;
1056: return (sc1.getLoadOrder() <= sc2.getLoadOrder()) ? -1
1057: : +1;
1058: }
1059: });
1060: for (Iterator iterator = autoLoadable.iterator(); iterator
1061: .hasNext();) {
1062: ServletConfiguration servletConfiguration = (ServletConfiguration) iterator
1063: .next();
1064: try {
1065: servletConfiguration.getServlet();
1066: } catch (Exception e) {
1067: e.printStackTrace();
1068: throw new RuntimeException(
1069: "Unable to autoload servlet: "
1070: + servletConfiguration
1071: .getClassName() + ": " + e);
1072: }
1073: }
1074: }
1075:
1076: private void collectAutoLoadableServlets(Collection collection,
1077: Map map) {
1078: for (Iterator iterator = map.values().iterator(); iterator
1079: .hasNext();) {
1080: Object o = iterator.next();
1081: if (o instanceof WebResourceMapping) {
1082: WebResourceMapping servletMapping = (WebResourceMapping) o;
1083: if (servletMapping.getConfiguration()
1084: .isLoadOnStartup())
1085: collection.add(servletMapping
1086: .getConfiguration());
1087: } else {
1088: collectAutoLoadableServlets(collection, (Map) o);
1089: }
1090: }
1091: }
1092:
1093: private WebResourceMapping getMapping(String url) {
1094: if (_exactMatches.containsKey(url))
1095: return (WebResourceMapping) _exactMatches.get(url);
1096:
1097: Map context = getContextForLongestPathPrefix(url);
1098: if (context.containsKey("*"))
1099: return (WebResourceMapping) context.get("*");
1100:
1101: if (_extensions.containsKey(getExtension(url)))
1102: return (WebResourceMapping) _extensions
1103: .get(getExtension(url));
1104:
1105: if (_urlTree.containsKey("/"))
1106: return (WebResourceMapping) _urlTree.get("/");
1107:
1108: if (_defaultMapping != null)
1109: return _defaultMapping;
1110:
1111: final String prefix = "/servlet/";
1112: if (!url.startsWith(prefix))
1113: return null;
1114:
1115: String className = url.substring(prefix.length());
1116: try {
1117: Class.forName(className);
1118: return new WebResourceMapping(new ServletConfiguration(
1119: className));
1120: } catch (ClassNotFoundException e) {
1121: return null;
1122: }
1123: }
1124:
1125: private Map getContextForLongestPathPrefix(String url) {
1126: Map context = _urlTree;
1127:
1128: ParsedPath path = new ParsedPath(url);
1129: while (path.hasNext()) {
1130: String part = path.next();
1131: if (!context.containsKey(part))
1132: break;
1133: context = (Map) context.get(part);
1134: }
1135: return context;
1136: }
1137:
1138: private String getExtension(String url) {
1139: int index = url.lastIndexOf('.');
1140: if (index == -1 || index >= url.length() - 1) {
1141: return "";
1142: } else {
1143: return url.substring(index + 1);
1144: }
1145: }
1146:
1147: }
1148:
1149: }
1150:
1151: /**
1152: * A utility class for parsing URLs into paths
1153: *
1154: * @author <a href="balld@webslingerZ.com">Donald Ball</a>
1155: */
1156: class ParsedPath {
1157:
1158: private final String path;
1159: private int position = 0;
1160: static final char seperator_char = '/';
1161:
1162: /**
1163: * Creates a new parsed path for the given path value
1164: *
1165: * @param path the path
1166: */
1167: ParsedPath(String path) {
1168: if (path.charAt(0) != seperator_char) {
1169: throw new IllegalArgumentException("Illegal path '" + path
1170: + "', does not begin with " + seperator_char);
1171: }
1172: this .path = path;
1173: }
1174:
1175: /**
1176: * Returns true if there are more parts left, otherwise false
1177: */
1178: public final boolean hasNext() {
1179: return (position < path.length());
1180: }
1181:
1182: /**
1183: * Returns the next part in the path
1184: */
1185: public final String next() {
1186: int offset = position + 1;
1187: while (offset < path.length()
1188: && path.charAt(offset) != seperator_char) {
1189: offset++;
1190: }
1191: String result = path.substring(position + 1, offset);
1192: position = offset;
1193: return result;
1194: }
1195:
1196: }
|