/*
 * JBoss, Home of Professional Open Source
 * Copyright 2005, JBoss Inc., and individual contributors as indicated
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software 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. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.jboss.web.tomcat.tc4;


import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import javax.naming.NameClassPair;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;

import org.apache.catalina.Authenticator;
import org.apache.catalina.Connector;
import org.apache.catalina.Container;
import org.apache.catalina.Context;
import org.apache.catalina.Engine;
import org.apache.catalina.Host;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleEvent;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.Logger;
import org.apache.catalina.Pipeline;
import org.apache.catalina.Service;
import org.apache.catalina.Valve;
import org.apache.catalina.Wrapper;
import org.apache.catalina.core.ContainerBase;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.deploy.ApplicationParameter;
import org.apache.catalina.deploy.ErrorPage;
import org.apache.catalina.deploy.FilterDef;
import org.apache.catalina.deploy.FilterMap;
import org.apache.catalina.deploy.LoginConfig;
import org.apache.catalina.deploy.SecurityConstraint;
import org.apache.catalina.startup.Constants;
import org.apache.catalina.startup.ContextConfig;
import org.apache.catalina.startup.TldRuleSet;
import org.apache.catalina.startup.WebRuleSet;
import org.apache.catalina.util.StringManager;
import org.apache.commons.digester.Digester;
import org.xml.sax.InputSource;
import org.xml.sax.SAXParseException;

/**
 * Startup event listener for a <b>Context</b> that configures the properties
 * of that Context, and the associated defined servlets, using by default the
 * authenticator classes found in package
 * <code>org.jboss.web.tomcat.tc4.authenticator</code>.
 * <p>
 * Differs from the standard Tomcat ContextConfig class solely in that it
 * determines any authenticator valve to load by referring to property file
 * <code>org.jboss.web.tomcat.tc4.Authenticators</code> instead of
 * <code>org.apache.catalina.startup.Authenticators</code>.  This difference
 * is implemented in private method <code>authenticatorConfig</code>.
 *
 * @author B Stansberry, based almost entirely on work by Craig R. McClanahan
 *
 * @version $Revison$ $Date: 2005/12/01 19:51:37 $ derived from Tomcat Revision: 1.67
 */

public final class SingleSignOnContextConfig implements LifecycleListener
{


   // ----------------------------------------------------- Instance Variables


   /**
    * The set of Authenticators that we know how to configure.  The key is
    * the name of the implemented authentication method, and the value is
    * the fully qualified Java class name of the corresponding Valve.
    */
   private static ResourceBundle authenticators = null;


   /**
    * The Context we are associated with.
    */
   private Context context = null;


   /**
    * The debugging detail level for this component.
    */
   private int debug = 0;


   /**
    * Track any fatal errors during startup configuration processing.
    */
   private boolean ok = false;


   /**
    * The string resources for this package.
    */
   private static final StringManager sm =
         StringManager.getManager(Constants.Package);


   /**
    * The <code>Digester</code> we will use to process tag library
    * descriptor files.
    */
   private static Digester tldDigester = createTldDigester();


   /**
    * The <code>Digester</code> we will use to process web application
    * deployment descriptor files.
    */
   private static Digester webDigester = createWebDigester();


   // ------------------------------------------------------------- Properties


   /**
    * Return the debugging detail level for this component.
    */
   public int getDebug()
   {

      return (this.debug);

   }


   /**
    * Set the debugging detail level for this component.
    *
    * @param debug The new debugging detail level
    */
   public void setDebug(int debug)
   {

      this.debug = debug;

   }


   // --------------------------------------------------------- Public Methods


   /**
    * Process the START event for an associated Context.
    *
    * @param event The lifecycle event that has occurred
    */
   public void lifecycleEvent(LifecycleEvent event)
   {

      // Identify the context we are associated with
      try
      {
         context = (Context) event.getLifecycle();
         if (context instanceof StandardContext)
         {
            int contextDebug = ((StandardContext) context).getDebug();
            if (contextDebug > this.debug)
               this.debug = contextDebug;
         }
      }
      catch (ClassCastException e)
      {
         log(sm.getString("contextConfig.cce", event.getLifecycle()), e);
         return;
      }

      // Process the event that has occurred
      if (event.getType().equals(Lifecycle.START_EVENT))
         start();
      else if (event.getType().equals(Lifecycle.STOP_EVENT))
         stop();

   }


   // -------------------------------------------------------- Private Methods


   /**
    * Process the application configuration file, if it exists.
    */
   private void applicationConfig()
   {

      // Open the application web.xml file, if it exists
      InputStream stream = null;
      ServletContext servletContext = context.getServletContext();
      if (servletContext != null)
         stream = servletContext.getResourceAsStream
               (Constants.ApplicationWebXml);
      if (stream == null)
      {
         log(sm.getString("contextConfig.applicationMissing"));
         return;
      }

      // Process the application web.xml file
      synchronized (webDigester)
      {
         try
         {
            URL url =
                  servletContext.getResource(Constants.ApplicationWebXml);
            InputSource is = new InputSource(url.toExternalForm());
            is.setByteStream(stream);
            webDigester.setDebug(getDebug());
            if (context instanceof StandardContext)
            {
               ((StandardContext) context).setReplaceWelcomeFiles(true);
            }
            webDigester.clear();
            webDigester.push(context);
            webDigester.parse(is);
         }
         catch (SAXParseException e)
         {
            log(sm.getString("contextConfig.applicationParse"), e);
            log(sm.getString("contextConfig.applicationPosition",
                             "" + e.getLineNumber(),
                             "" + e.getColumnNumber()));
            ok = false;
         }
         catch (Exception e)
         {
            log(sm.getString("contextConfig.applicationParse"), e);
            ok = false;
         }
         finally
         {
            try
            {
               if (stream != null)
               {
                  stream.close();
               }
            }
            catch (IOException e)
            {
               log(sm.getString("contextConfig.applicationClose"), e);
            }
         }
      }

   }


   /**
    * Set up an Authenticator automatically if required, and one has not
    * already been configured.
    * <p>
    * Differs from the standard Tomcat ContextConfig class in that it
    * determines the authenticator to load by referring to property file
    * <code>org.jboss.web.tomcat.tc4.Authenticators</code> instead of
    * <code>org.apache.catalina.startup.Authenticators</code>.
    */
   private synchronized void authenticatorConfig()
   {

      // Does this Context require an Authenticator?
      SecurityConstraint constraints[] = context.findConstraints();
      if ((constraints == null) || (constraints.length == 0))
         return;
      LoginConfig loginConfig = context.getLoginConfig();
      if (loginConfig == null)
      {
         loginConfig = new LoginConfig("NONE", null, null, null);
         context.setLoginConfig(loginConfig);
      }

      // Has an authenticator been configured already?
      if (context instanceof Authenticator)
         return;
      if (context instanceof ContainerBase)
      {
         Pipeline pipeline = ((ContainerBase) context).getPipeline();
         if (pipeline != null)
         {
            Valve basic = pipeline.getBasic();
            if ((basic != null) && (basic instanceof Authenticator))
               return;
            Valve valves[] = pipeline.getValves();
            for (int i = 0; i < valves.length; i++)
            {
               if (valves[i] instanceof Authenticator)
                  return;
            }
         }
      }
      else
      {
         return;     // Cannot install a Valve even if it would be needed
      }

      // Has a Realm been configured for us to authenticate against?
      if (context.getRealm() == null)
      {
         log(sm.getString("contextConfig.missingRealm"));
         ok = false;
         return;
      }

      // Load our mapping properties if necessary
      if (authenticators == null)
      {
         try
         {
            // HERE IS THE DIFFERENCE FROM THE STANDARD TOMCAT VERSION
            authenticators = ResourceBundle.getBundle
                  ("org.jboss.web.tomcat.tc4.Authenticators");
         }
         catch (MissingResourceException e)
         {
            log(sm.getString("contextConfig.authenticatorResources"), e);
            ok = false;
            return;
         }
      }

      // Identify the class name of the Valve we should configure
      String authenticatorName = null;
      try
      {
         authenticatorName =
               authenticators.getString(loginConfig.getAuthMethod());
      }
      catch (MissingResourceException e)
      {
         authenticatorName = null;
      }
      if (authenticatorName == null)
      {
         log(sm.getString("contextConfig.authenticatorMissing",
                          loginConfig.getAuthMethod()));
         ok = false;
         return;
      }

      // Instantiate and install an Authenticator of the requested class
      Valve authenticator = null;
      try
      {
         Class authenticatorClass = Class.forName(authenticatorName);
         authenticator = (Valve) authenticatorClass.newInstance();
         if (context instanceof ContainerBase)
         {
            Pipeline pipeline = ((ContainerBase) context).getPipeline();
            if (pipeline != null)
            {
               ((ContainerBase) context).addValve(authenticator);
               log(sm.getString("contextConfig.authenticatorConfigured",
                                loginConfig.getAuthMethod()));
            }
         }
      }
      catch (Throwable t)
      {
         log(sm.getString("contextConfig.authenticatorInstantiate",
                          authenticatorName), t);
         ok = false;
      }

   }


   /**
    * Create and deploy a Valve to expose the SSL certificates presented
    * by this client, if any.  If we cannot instantiate such a Valve
    * (because the JSSE classes are not available), silently continue.
    * This is only instantiated for those Contexts being served by
    * a Connector with secure set to true.
    */
   private void certificatesConfig()
   {

      // Only install this valve if there is a Connector installed
      // which has secure set to true.
      boolean secure = false;
      Container container = context.getParent();
      if (container instanceof Host)
      {
         container = container.getParent();
      }
      if (container instanceof Engine)
      {
         Service service = ((Engine) container).getService();
         // The service can be null when Tomcat is run in embedded mode
         if (service == null)
         {
            secure = true;
         }
         else
         {
            Connector[] connectors = service.findConnectors();
            for (int i = 0; i < connectors.length; i++)
            {
               secure = connectors[i].getSecure();
               if (secure)
               {
                  break;
               }
            }
         }
      }
      if (!secure)
      {
         return;
      }

      // Validate that the JSSE classes are present
      try
      {
         Class clazz = this.getClass().getClassLoader().loadClass
               ("javax.net.ssl.SSLSocket");
         if (clazz == null)
            return;
      }
      catch (Throwable t)
      {
         return;
      }

      // Instantiate a new CertificatesValve if possible
      Valve certificates = null;
      try
      {
         Class clazz =
               Class.forName("org.apache.catalina.valves.CertificatesValve");
         certificates = (Valve) clazz.newInstance();
      }
      catch (Throwable t)
      {
         return;     // Probably JSSE classes not present
      }

      // Add this Valve to our Pipeline
      try
      {
         if (context instanceof ContainerBase)
         {
            Pipeline pipeline = ((ContainerBase) context).getPipeline();
            if (pipeline != null)
            {
               ((ContainerBase) context).addValve(certificates);
               log(sm.getString
                   ("contextConfig.certificatesConfig.added"));
            }
         }
      }
      catch (Throwable t)
      {
         log(sm.getString("contextConfig.certificatesConfig.error"), t);
         ok = false;
      }

   }


   /**
    * Create (if necessary) and return a Digester configured to process a tag
    * library descriptor, looking for additional listener classes to be
    * registered.
    */
   private static Digester createTldDigester()
   {

      URL url = null;
      Digester tldDigester = new Digester();
      tldDigester.setValidating(true);
      url = ContextConfig.class.getResource(Constants.TldDtdResourcePath_11);
      tldDigester.register(Constants.TldDtdPublicId_11,
                           url.toString());
      url = ContextConfig.class.getResource(Constants.TldDtdResourcePath_12);
      tldDigester.register(Constants.TldDtdPublicId_12,
                           url.toString());
      tldDigester.addRuleSet(new TldRuleSet());
      return (tldDigester);

   }


   /**
    * Create (if necessary) and return a Digester configured to process the
    * web application deployment descriptor (web.xml).
    */
   private static Digester createWebDigester()
   {

      URL url = null;
      Digester webDigester = new Digester();
      webDigester.setValidating(true);
      url = ContextConfig.class.getResource(Constants.WebDtdResourcePath_22);
      webDigester.register(Constants.WebDtdPublicId_22,
                           url.toString());
      url = ContextConfig.class.getResource(Constants.WebDtdResourcePath_23);
      webDigester.register(Constants.WebDtdPublicId_23,
                           url.toString());
      webDigester.addRuleSet(new WebRuleSet());
      return (webDigester);

   }


   /**
    * Process the default configuration file, if it exists.
    */
   private void defaultConfig()
   {

      // Open the default web.xml file, if it exists
      File file = new File(Constants.DefaultWebXml);
      if (!file.isAbsolute())
         file = new File(System.getProperty("catalina.base"),
                         Constants.DefaultWebXml);
      FileInputStream stream = null;
      try
      {
         stream = new FileInputStream(file.getCanonicalPath());
         stream.close();
         stream = null;
      }
      catch (FileNotFoundException e)
      {
         log(sm.getString("contextConfig.defaultMissing"));
         return;
      }
      catch (IOException e)
      {
         log(sm.getString("contextConfig.defaultMissing"), e);
         return;
      }

      // Process the default web.xml file
      synchronized (webDigester)
      {
         ClassLoader prevLoader = Thread.currentThread().getContextClassLoader();
         try
         {
            InputSource is =
                  new InputSource("file://" + file.getAbsolutePath());
            stream = new FileInputStream(file);
            is.setByteStream(stream);
            webDigester.setDebug(getDebug());
            if (context instanceof StandardContext)
               ((StandardContext) context).setReplaceWelcomeFiles(true);
            webDigester.clear();
            webDigester.push(context);
            ClassLoader loader = getClass().getClassLoader();
            Thread.currentThread().setContextClassLoader(loader);
            webDigester.parse(is);
         }
         catch (SAXParseException e)
         {
            log(sm.getString("contextConfig.defaultParse"), e);
            log(sm.getString("contextConfig.defaultPosition",
                             "" + e.getLineNumber(),
                             "" + e.getColumnNumber()));
            ok = false;
         }
         catch (Exception e)
         {
            log(sm.getString("contextConfig.defaultParse"), e);
            ok = false;
         }
         finally
         {
            try
            {
               Thread.currentThread().setContextClassLoader(prevLoader);
               if (stream != null)
               {
                  stream.close();
               }
            }
            catch (IOException e)
            {
               log(sm.getString("contextConfig.defaultClose"), e);
            }
         }
      }

   }


   /**
    * Log a message on the Logger associated with our Context (if any)
    *
    * @param message Message to be logged
    */
   private void log(String message)
   {

      Logger logger = null;
      if (context != null)
         logger = context.getLogger();
      if (logger != null)
         logger.log("SingleSignOnContextConfig[" + context.getName() + "]: "
                    + message);
      else
         System.out.println("SingleSignOnContextConfig[" + context.getName()
                            + "]: " + message);

   }


   /**
    * Log a message on the Logger associated with our Context (if any)
    *
    * @param message Message to be logged
    * @param throwable Associated exception
    */
   private void log(String message, Throwable throwable)
   {

      Logger logger = null;
      if (context != null)
         logger = context.getLogger();
      if (logger != null)
         logger.log("SingleSignOnContextConfig[" + context.getName() + "] "
                    + message, throwable);
      else
      {
         System.out.println("SingleSignOnContextConfig[" + context.getName()
                            + "]: " + message);
         System.out.println("" + throwable);
         throwable.printStackTrace(System.out);
      }

   }


   /**
    * Process a "start" event for this Context.
    */
   private synchronized void start()
   {

      if (debug > 0)
         log(sm.getString("contextConfig.start"));
      context.setConfigured(false);
      ok = true;

      // Set properties based on DefaultContext
      Container container = context.getParent();
      if (!context.getOverride())
      {
         if (container instanceof Host)
         {
            ((Host) container).importDefaultContext(context);
            container = container.getParent();
         }
         if (container instanceof Engine)
         {
            ((Engine) container).importDefaultContext(context);
         }
      }

      // Process the default and application web.xml files
      defaultConfig();
      applicationConfig();
      if (ok)
      {
         validateSecurityRoles();
      }

      // Scan tag library descriptor files for additional listener classes
      if (ok)
      {
         try
         {
            tldScan();
         }
         catch (Exception e)
         {
            log(e.getMessage(), e);
            ok = false;
         }
      }

      // Configure a certificates exposer valve, if required
      if (ok)
         certificatesConfig();

      // Configure an authenticator if we need one
      if (ok)
         authenticatorConfig();

      // Dump the contents of this pipeline if requested
      if ((debug >= 1) && (context instanceof ContainerBase))
      {
         log("Pipline Configuration:");
         Pipeline pipeline = ((ContainerBase) context).getPipeline();
         Valve valves[] = null;
         if (pipeline != null)
            valves = pipeline.getValves();
         if (valves != null)
         {
            for (int i = 0; i < valves.length; i++)
            {
               log("  " + valves[i].getInfo());
            }
         }
         log("======================");
      }

      // Make our application available if no problems were encountered
      if (ok)
         context.setConfigured(true);
      else
      {
         log(sm.getString("contextConfig.unavailable"));
         context.setConfigured(false);
      }

   }


   /**
    * Process a "stop" event for this Context.
    */
   private synchronized void stop()
   {

      if (debug > 0)
         log(sm.getString("contextConfig.stop"));

      int i;

      // Removing children
      Container[] children = context.findChildren();
      for (i = 0; i < children.length; i++)
      {
         context.removeChild(children[i]);
      }

      // Removing application listeners
      String[] applicationListeners = context.findApplicationListeners();
      for (i = 0; i < applicationListeners.length; i++)
      {
         context.removeApplicationListener(applicationListeners[i]);
      }

      // Removing application parameters
      ApplicationParameter[] applicationParameters =
            context.findApplicationParameters();
      for (i = 0; i < applicationParameters.length; i++)
      {
         context.removeApplicationParameter
               (applicationParameters[i].getName());
      }

      // Removing security constraints
      SecurityConstraint[] securityConstraints = context.findConstraints();
      for (i = 0; i < securityConstraints.length; i++)
      {
         context.removeConstraint(securityConstraints[i]);
      }

      // Removing Ejbs
      /*
      ContextEjb[] contextEjbs = context.findEjbs();
      for (i = 0; i < contextEjbs.length; i++) {
          context.removeEjb(contextEjbs[i].getName());
      }
      */

      // Removing environments
      /*
      ContextEnvironment[] contextEnvironments = context.findEnvironments();
      for (i = 0; i < contextEnvironments.length; i++) {
          context.removeEnvironment(contextEnvironments[i].getName());
      }
      */

      // Removing errors pages
      ErrorPage[] errorPages = context.findErrorPages();
      for (i = 0; i < errorPages.length; i++)
      {
         context.removeErrorPage(errorPages[i]);
      }

      // Removing filter defs
      FilterDef[] filterDefs = context.findFilterDefs();
      for (i = 0; i < filterDefs.length; i++)
      {
         context.removeFilterDef(filterDefs[i]);
      }

      // Removing filter maps
      FilterMap[] filterMaps = context.findFilterMaps();
      for (i = 0; i < filterMaps.length; i++)
      {
         context.removeFilterMap(filterMaps[i]);
      }

      // Removing instance listeners
      String[] instanceListeners = context.findInstanceListeners();
      for (i = 0; i < instanceListeners.length; i++)
      {
         context.removeInstanceListener(instanceListeners[i]);
      }

      // Removing local ejbs
      /*
      ContextLocalEjb[] contextLocalEjbs = context.findLocalEjbs();
      for (i = 0; i < contextLocalEjbs.length; i++) {
          context.removeLocalEjb(contextLocalEjbs[i].getName());
      }
      */

      // Removing Mime mappings
      String[] mimeMappings = context.findMimeMappings();
      for (i = 0; i < mimeMappings.length; i++)
      {
         context.removeMimeMapping(mimeMappings[i]);
      }

      // Removing parameters
      String[] parameters = context.findParameters();
      for (i = 0; i < parameters.length; i++)
      {
         context.removeParameter(parameters[i]);
      }

      // Removing resource env refs
      /*
      String[] resourceEnvRefs = context.findResourceEnvRefs();
      for (i = 0; i < resourceEnvRefs.length; i++) {
          context.removeResourceEnvRef(resourceEnvRefs[i]);
      }
      */

      // Removing resource links
      /*
      ContextResourceLink[] contextResourceLinks =
          context.findResourceLinks();
      for (i = 0; i < contextResourceLinks.length; i++) {
          context.removeResourceLink(contextResourceLinks[i].getName());
      }
      */

      // Removing resources
      /*
      ContextResource[] contextResources = context.findResources();
      for (i = 0; i < contextResources.length; i++) {
          context.removeResource(contextResources[i].getName());
      }
      */

      // Removing sercurity role
      String[] securityRoles = context.findSecurityRoles();
      for (i = 0; i < securityRoles.length; i++)
      {
         context.removeSecurityRole(securityRoles[i]);
      }

      // Removing servlet mappings
      String[] servletMappings = context.findServletMappings();
      for (i = 0; i < servletMappings.length; i++)
      {
         context.removeServletMapping(servletMappings[i]);
      }

      // FIXME : Removing status pages

      // Removing taglibs
      String[] taglibs = context.findTaglibs();
      for (i = 0; i < taglibs.length; i++)
      {
         context.removeTaglib(taglibs[i]);
      }

      // Removing welcome files
      String[] welcomeFiles = context.findWelcomeFiles();
      for (i = 0; i < welcomeFiles.length; i++)
      {
         context.removeWelcomeFile(welcomeFiles[i]);
      }

      // Removing wrapper lifecycles
      String[] wrapperLifecycles = context.findWrapperLifecycles();
      for (i = 0; i < wrapperLifecycles.length; i++)
      {
         context.removeWrapperLifecycle(wrapperLifecycles[i]);
      }

      // Removing wrapper listeners
      String[] wrapperListeners = context.findWrapperListeners();
      for (i = 0; i < wrapperListeners.length; i++)
      {
         context.removeWrapperListener(wrapperListeners[i]);
      }

      ok = true;

   }


   /**
    * Scan for and configure all tag library descriptors found in this
    * web application.
    *
    * @exception Exception if a fatal input/output or parsing error occurs
    */
   private void tldScan() throws Exception
   {

      // Acquire this list of TLD resource paths to be processed
      Set resourcePaths = tldScanResourcePaths();

      // Scan each accumulated resource paths for TLDs to be processed
      Iterator paths = resourcePaths.iterator();
      while (paths.hasNext())
      {
         String path = (String) paths.next();
         if (path.endsWith(".jar"))
         {
            tldScanJar(path);
         }
         else
         {
            tldScanTld(path);
         }
      }

   }


   /**
    * Scan the JAR file at the specified resource path for TLDs in the
    * <code>META-INF</code> subdirectory, and scan them for application
    * event listeners that need to be registered.
    *
    * @param resourcePath Resource path of the JAR file to scan
    *
    * @exception Exception if an exception occurs while scanning this JAR
    */
   private void tldScanJar(String resourcePath) throws Exception
   {

      if (debug >= 1)
      {
         log(" Scanning JAR at resource path '" + resourcePath + "'");
      }

      JarFile jarFile = null;
      String name = null;
      InputStream inputStream = null;
      try
      {
         URL url = context.getServletContext().getResource(resourcePath);
         if (url == null)
         {
            throw new IllegalArgumentException
                  (sm.getString("contextConfig.tldResourcePath",
                                resourcePath));
         }
         url = new URL("jar:" + url.toString() + "!/");
         JarURLConnection conn =
               (JarURLConnection) url.openConnection();
         conn.setUseCaches(false);
         jarFile = conn.getJarFile();
         Enumeration entries = jarFile.entries();
         while (entries.hasMoreElements())
         {
            JarEntry entry = (JarEntry) entries.nextElement();
            name = entry.getName();
            if (!name.startsWith("META-INF/"))
            {
               continue;
            }
            if (!name.endsWith(".tld"))
            {
               continue;
            }
            if (debug >= 2)
            {
               log("  Processing TLD at '" + name + "'");
            }
            inputStream = jarFile.getInputStream(entry);
            tldScanStream(inputStream);
            inputStream.close();
            inputStream = null;
            name = null;
         }
         // FIXME - Closing the JAR file messes up the class loader???
         //            jarFile.close();
      }
      catch (Exception e)
      {
         if (name == null)
         {
            throw new ServletException
                  (sm.getString("contextConfig.tldJarException",
                                resourcePath), e);
         }
         else
         {
            throw new ServletException
                  (sm.getString("contextConfig.tldEntryException",
                                name, resourcePath), e);
         }
      }
      finally
      {
         if (inputStream != null)
         {
            try
            {
               inputStream.close();
            }
            catch (Throwable t)
            {
               ;
            }
            inputStream = null;
         }
         if (jarFile != null)
         {
            // FIXME - Closing the JAR file messes up the class loader???
            //                try {
            //                    jarFile.close();
            //                } catch (Throwable t) {
            //                    ;
            //                }
            jarFile = null;
         }
      }

   }


   /**
    * Scan the TLD contents in the specified input stream, and register
    * any application event listeners found there.  <b>NOTE</b> - It is
    * the responsibility of the caller to close the InputStream after this
    * method returns.
    *
    * @param resourceStream InputStream containing a tag library descriptor
    *
    * @exception Exception if an exception occurs while scanning this TLD
    */
   private void tldScanStream(InputStream resourceStream)
         throws Exception
   {

      synchronized (tldDigester)
      {
         tldDigester.clear();
         tldDigester.push(context);
         tldDigester.parse(resourceStream);
      }

   }


   /**
    * Scan the TLD contents at the specified resource path, and register
    * any application event listeners found there.
    *
    * @param resourcePath Resource path being scanned
    *
    * @exception Exception if an exception occurs while scanning this TLD
    */
   private void tldScanTld(String resourcePath) throws Exception
   {

      if (debug >= 1)
      {
         log(" Scanning TLD at resource path '" + resourcePath + "'");
      }

      InputStream inputStream = null;
      try
      {
         inputStream =
               context.getServletContext().getResourceAsStream(resourcePath);
         if (inputStream == null)
         {
            throw new IllegalArgumentException
                  (sm.getString("contextConfig.tldResourcePath",
                                resourcePath));
         }
         tldScanStream(inputStream);
         inputStream.close();
         inputStream = null;
      }
      catch (Exception e)
      {
         throw new ServletException
               (sm.getString("contextConfig.tldFileException", resourcePath),
                e);
      }
      finally
      {
         if (inputStream != null)
         {
            try
            {
               inputStream.close();
            }
            catch (Throwable t)
            {
               ;
            }
            inputStream = null;
         }
      }

   }


   /**
    * Accumulate and return a Set of resource paths to be analyzed for
    * tag library descriptors.  Each element of the returned set will be
    * the context-relative path to either a tag library descriptor file,
    * or to a JAR file that may contain tag library descriptors in its
    * <code>META-INF</code> subdirectory.
    *
    * @exception IOException if an input/output error occurs while
    *  accumulating the list of resource paths
    */
   private Set tldScanResourcePaths() throws IOException
   {

      if (debug >= 1)
      {
         log(" Accumulating TLD resource paths");
      }
      Set resourcePaths = new HashSet();

      // Accumulate resource paths explicitly listed in the web application
      // deployment descriptor
      if (debug >= 2)
      {
         log("  Scanning <taglib> elements in web.xml");
      }
      String taglibs[] = context.findTaglibs();
      for (int i = 0; i < taglibs.length; i++)
      {
         String resourcePath = context.findTaglib(taglibs[i]);
         // FIXME - Servlet 2.3 DTD implies that the location MUST be
         // a context-relative path starting with '/'?
         if (!resourcePath.startsWith("/"))
         {
            resourcePath = "/WEB-INF/" + resourcePath;
         }
         if (debug >= 3)
         {
            log("   Adding path '" + resourcePath +
                "' for URI '" + taglibs[i] + "'");
         }
         resourcePaths.add(resourcePath);
      }

      // Scan TLDs in the /WEB-INF subdirectory of the web application
      if (debug >= 2)
      {
         log("  Scanning TLDs in /WEB-INF subdirectory");
      }
      DirContext resources = context.getResources();
      try
      {
         NamingEnumeration items = resources.list("/WEB-INF");
         while (items.hasMoreElements())
         {
            NameClassPair item = (NameClassPair) items.nextElement();
            String resourcePath = "/WEB-INF/" + item.getName();
            // FIXME - JSP 1.2 is not explicit about whether we should
            // scan subdirectories of /WEB-INF for TLDs also
            if (!resourcePath.endsWith(".tld"))
            {
               continue;
            }
            if (debug >= 3)
            {
               log("   Adding path '" + resourcePath + "'");
            }
            resourcePaths.add(resourcePath);
         }
      }
      catch (NamingException e)
      {
         ; // Silent catch: it's valid that no /WEB-INF directory exists
      }

      // Scan JARs in the /WEB-INF/lib subdirectory of the web application
      if (debug >= 2)
      {
         log("  Scanning JARs in /WEB-INF/lib subdirectory");
      }
      try
      {
         NamingEnumeration items = resources.list("/WEB-INF/lib");
         while (items.hasMoreElements())
         {
            NameClassPair item = (NameClassPair) items.nextElement();
            String resourcePath = "/WEB-INF/lib/" + item.getName();
            if (!resourcePath.endsWith(".jar"))
            {
               continue;
            }
            if (debug >= 3)
            {
               log("   Adding path '" + resourcePath + "'");
            }
            resourcePaths.add(resourcePath);
         }
      }
      catch (NamingException e)
      {
         ; // Silent catch: it's valid that no /WEB-INF/lib directory exists
      }

      // Return the completed set
      return (resourcePaths);

   }


   /**
    * Validate the usage of security role names in the web application
    * deployment descriptor.  If any problems are found, issue warning
    * messages (for backwards compatibility) and add the missing roles.
    * (To make these problems fatal instead, simply set the <code>ok</code>
    * instance variable to <code>false</code> as well).
    */
   private void validateSecurityRoles()
   {

      // Check role names used in <security-constraint> elements
      SecurityConstraint constraints[] = context.findConstraints();
      for (int i = 0; i < constraints.length; i++)
      {
         String roles[] = constraints[i].findAuthRoles();
         for (int j = 0; j < roles.length; j++)
         {
            if (!"*".equals(roles[j]) &&
                  !context.findSecurityRole(roles[j]))
            {
               log(sm.getString("contextConfig.role.auth", roles[j]));
               context.addSecurityRole(roles[j]);
            }
         }
      }

      // Check role names used in <servlet> elements
      Container wrappers[] = context.findChildren();
      for (int i = 0; i < wrappers.length; i++)
      {
         Wrapper wrapper = (Wrapper) wrappers[i];
         String runAs = wrapper.getRunAs();
         if ((runAs != null) && !context.findSecurityRole(runAs))
         {
            log(sm.getString("contextConfig.role.runas", runAs));
            context.addSecurityRole(runAs);
         }
         String names[] = wrapper.findSecurityReferences();
         for (int j = 0; j < names.length; j++)
         {
            String link = wrapper.findSecurityReference(names[j]);
            if ((link != null) && !context.findSecurityRole(link))
            {
               log(sm.getString("contextConfig.role.link", link));
               context.addSecurityRole(link);
            }
         }
      }

   }


}
