Title: org/jboss/web/catalina ajp13connector support
I was wondering if there were a particular reason why EmbeddedCatalinaServiceSX was written to support http and warp connectors, but not Ajp13.  I added an initAjpConnector method which takes the default Embedded engine and then substitutes 'Ajp' for 'Warp' for the connector-specific code, and this seems to work (ie real .html files are displayed by Apache, fake ones are rejected by Apache, real jsp files are displayed by Catalina, and fake ones are rejected by Catalina).  Was this support left out because the tomcat folks call their ajp connector support beta and recommend using warp?  I'd use warp, but it doesn't seem to dig into war files for web.xml, and jboss will only look in war files for web.xml - I'd rather not have to support both.  Also, Warp doesn't seem to automatically deliver static content to Apache and dynamic content to catalina, although that is a promised feature, and I can't direct it to specifically serve all *.jsp files.

Should I be afraid of using Ajp?

Aaron




/* JBoss, the OpenSource EJB server
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package org.jboss.web.catalina;

import java.io.InputStream;
import java.io.IOException;
import java.io.File;
import java.io.PrintWriter;
import java.net.UnknownHostException;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.MalformedURLException;
import java.net.InetAddress;
import java.security.ProtectionDomain;
import java.util.HashMap;
import java.util.Iterator;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.servlet.ServletContext;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;

import org.jboss.deployment.DeploymentException;
import org.jboss.security.SecurityDomain;
import org.jboss.web.AbstractWebContainer;
import org.jboss.web.AbstractWebContainer.WebDescriptorParser;
import org.jboss.web.WebApplication;
import org.jboss.web.catalina.security.JBossSecurityMgrRealm;
import org.jboss.web.catalina.security.SSLServerSocketFactory;

import org.apache.log4j.Category;
import org.apache.log4j.Priority;
import org.apache.catalina.Connector;
import org.apache.catalina.Container;
import org.apache.catalina.Context;
import org.apache.catalina.Deployer;
import org.apache.catalina.Engine;
import org.apache.catalina.Host;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleEvent;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.Logger;
import org.apache.catalina.Realm;
import org.apache.catalina.Valve;
import org.apache.catalina.authenticator.AuthenticatorBase;
import org.apache.catalina.connector.http.HttpConnector;
import org.apache.catalina.connector.warp.WarpConnector;
import org.apache.catalina.connector.warp.WarpHost;
import org.apache.catalina.connector.warp.WarpEngine;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.loader.WebappClassLoader;
import org.apache.catalina.startup.Embedded;
import org.apache.ajp.tomcat4.*;
import org.apache.ajp.*;

/** An implementation of the AbstractWebContainer for the Jakarta Tomcat
4.0 servlet container. This uses the Embedded as the integration class. It
does not parse the catalina server.xml.

@see org.jboss.web.AbstractWebContainer
@see org.apache.catalina.startup.Embedded

@author [EMAIL PROTECTED]
@version $Revision: 1.2 $
*/
public class EmbeddedCatalinaServiceSX extends AbstractWebContainer implements EmbeddedCatalinaServiceSXMBean
{
   // Constants -----------------------------------------------------
   public static final String NAME = "EmbeddedCatalinaSX";

   /** The embedded instance used to configure catalina */
   private Embedded catalina;
   /** The catalina host container */
   private Host catalinaHost;
   /** The catalina debug level */
   private int debugLevel;
   /** The port the web server listens on */
   private int port = 8080;
   /** The interface to bind to. This is useful for multi-homed hosts
      that want control over which interfaces accept connections. */
   private InetAddress bindAddress;
   /** The accept count for this Connector. */
   private int acceptCount = 10;
   /** The "enable DNS lookups" flag. */
   private boolean enableLookups = false;
   /** The minimum number of processors to start at initialization time. */
   protected int minProcessors = 5;
   /**The maximum number of processors allowed, or <0 for unlimited. */
   private int maxProcessors = 20;
   /** The JNDI name of the SecurityDomain implementation binding */
   private String securityDomain;
   /** The type of connector that Tomcat will run (HTTP or WARP or Ajp) */
   private String connectorType = "http";
   /** Any extended configuration information specified via a config
    element in the mbean definition.
    */
   private Element extendedConfig;

   public EmbeddedCatalinaServiceSX()
   {
   }

   public String getName()
   {
      return NAME;
   }

   public void setPort(int port)
   {
      this.port = port;
   }
   public int getPort()
   {
      return port;
   }

   public String getBindAddress()
   {
      String address = "0.0.0.0";
      if( bindAddress != null )
         address = bindAddress.getHostAddress();
      return address;
   }
   public void setBindAddress(String host) throws UnknownHostException
   {
      if( host == null || host.length() == 0 )
         bindAddress = null;
      else
         bindAddress = InetAddress.getByName(host);
   }

   public String getConnectorType()
   {
      return this.connectorType;
   }

   public void setConnectorType(String type)
   {
      type = type.toLowerCase();
      if( type.equals("http") == false && type.equals("warp") == false && type.equals("ajp")==false )
      {
         String msg= "Invalid connector type: "+connectorType
            + " must be one of, http warp ajp";
         throw new IllegalArgumentException(msg);
      }
      this.connectorType = type;
   }

   public int getMinProcessors()
   {
      return this.minProcessors;
   }
   public void setMinProcessors(int minProcessors)
   {
      this.minProcessors = minProcessors;
   }

   public int getMaxProcessors()
   {
      return this.maxProcessors;
   }
   public void setMaxProcessors(int maxProcessors)
   {
      this.maxProcessors = maxProcessors;
   }

    public int getAcceptCount()
    {
       return this.acceptCount;
    }
    public void setAcceptCount(int acceptCount)
    {
       this.acceptCount = acceptCount;
    }

    public boolean getEnableLookups()
    {
       return enableLookups;
    }
    public void setEnableLookups(boolean enableLookups)
    {
       this.enableLookups = enableLookups;
    }

    public String getSecurityDomain()
    {
       return this.securityDomain;
    }
    public void setSecurityDomain(String securityDomain)
    {
       this.securityDomain = securityDomain;
    }

    /** This method is invoked to import an arbitrary XML configuration tree.
     Subclasses should override this method if they support such a configuration
     capability. This implementation does nothing.
     */
    public void setConfig(Element config)
    {
       this.extendedConfig = config;
    }

   public void startService() throws Exception
   {
      boolean debug = log.isDebugEnabled();

      log.info("Starting " + NAME + "....");

      // Start create the embeded catalina container but don't let it overwrite the thread class loader
      URLClassLoader cl = (URLClassLoader) Thread.currentThread().getContextClassLoader();
      URLClassLoader parent = cl;
      while( parent != null )
      {
         log.trace(parent);
         URL[] urls = parent.getURLs();
         for(int u = 0; u < urls.length; u ++)
            log.trace("  "+urls[u]);
         parent = (URLClassLoader) parent.getParent();
      }

      // Determine the catalina debug level from the enabled priority
      debugLevel = 0;
      if( log.isTraceEnabled() )
         debugLevel = 2;
      if (debug)
         log.debug("Setting catalina debug level to: "+debugLevel);

      try
      {
         // Set the catalina.home property from the Embedded class location
         ProtectionDomain pd = Embedded.class.getProtectionDomain();
         URL catalinaHome = pd.getCodeSource().getLocation();
         String homePath = catalinaHome.getFile();
         File homeDir = new File(homePath, "../../..");
         homePath = homeDir.getCanonicalPath();
         if (debug)
            log.debug("Setting catalina.home to: " + homePath);
         System.setProperty("catalina.home", homePath);
         System.setProperty("catalina.base", homePath);
         initCatalina(cl);
         catalina.start();
      }
      finally
      {
         Thread.currentThread().setContextClassLoader(cl);
      }
      log.info("OK");
   }

   public void stopService()
   {
      if( catalina != null )
      {
         try
         {
            catalina.stop();
         }
         catch(Exception e)
         {
         }
      }
   }

   /** Perform the tomcat specific deployment steps.
    */
   protected WebApplication performDeploy(String ctxPath, String warUrl,
      WebDescriptorParser webAppParser) throws Exception
   {
      if( ctxPath.equals("/") )
      {
          log.info("Changing root context path from '/' to ''");
          ctxPath = "";
      }
      log.info("deploy, ctxPath="+ctxPath+", warUrl="+warUrl);
      ClassLoader ctxClassLoader = Thread.currentThread().getContextClassLoader();

      URL url = new URL(warUrl);
      WebApplication appInfo = createWebContext(ctxPath, url, webAppParser);
      if (log.isDebugEnabled())
         log.debug("Initialized: "+appInfo);
      return appInfo;
   }

   /** Perform the tomcat specific deployment steps.
    */
   public void performUndeploy(String warUrl) throws Exception
   {
      // find the javax.servlet.ServletContext in the repository
      WebApplication appInfo = getDeployedApp(warUrl);
      Context context = null;
      if( appInfo != null )
         context = (Context) appInfo.getAppData();
     
      if(context == null)
         throw new DeploymentException("URL " + warUrl + " is not deployed");

      String ctxPath = context.getPath();
      Deployer deployer = (Deployer) catalinaHost;
      deployer.remove(ctxPath);
   }

   /** Create and configure a org.apache.catalina.startup.Embedded
    instance. We do not use the server.xml file as we obtain all
    of the required customization from our mbean properties.
    */
   private void initCatalina(ClassLoader parent) throws Exception
   {
      Logger jbossLog = new Log4jLogger(this.log);
      Realm jbossRealm = new JBossSecurityMgrRealm();
      catalina = new Embedded(jbossLog, jbossRealm);
      catalina.setDebug(debugLevel);
      catalina.setUseNaming(false);

      // Setup the primary engine and connector
      if( connectorType.equals("http") )
         initHttpConnector(parent, jbossLog, jbossRealm);
      else if( connectorType.equals("warp") )
         initWarpConnector(parent, jbossLog, jbossRealm);
      else if( connectorType.equals("ajp") )
         initAjpConnector(parent, jbossLog, jbossRealm);
      else
         throw new IllegalStateException("No valid connectorType");

      // Apply any extended configuration
      ConfigHandler handler = new ConfigHandler();
      handler.applyHostConfig(extendedConfig, catalina, debugLevel > 0);
   }

   private void initHttpConnector(ClassLoader parent, Logger jbossLog, Realm jbossRealm)
      throws IOException, NamingException
   {
      log.info("Building Http engine and connector");
      String hostname = "localhost";
      if( bindAddress != null )
         hostname = bindAddress.getHostName();
      // Create the engine
      Engine engine = catalina.createEngine();
      engine.setDefaultHost(hostname);
      engine.setParentClassLoader(parent);
      // Create a host for the root context
      catalinaHost = catalina.createHost(hostname, "");
      engine.addChild(catalinaHost);

      // Install the engine
      catalina.addEngine(engine);

      // Create the warp connector and set its properties
      HttpConnector connector = (HttpConnector) catalina.createConnector(bindAddress, port, false);
      connector.setAcceptCount(acceptCount);
      connector.setMinProcessors(minProcessors);
      connector.setMaxProcessors(maxProcessors);
      connector.setEnableLookups(enableLookups);
      // Enable a secure connector if a security domain was specified
      if( securityDomain != null )
      {
         connector.setScheme("https");
         connector.setSecure(true);
         InitialContext iniCtx = new InitialContext();
         SecurityDomain domain = (SecurityDomain) iniCtx.lookup(securityDomain);
         connector.setFactory(new SSLServerSocketFactory(domain));
      }
      catalina.addConnector(connector);
   }
   private void initWarpConnector(ClassLoader parent, Logger jbossLog, Realm jbossRealm)
   {
      log.info("Building Warp engine and connector");
      String hostname = "localhost";
      if( bindAddress != null )
         hostname = bindAddress.getHostName();
      // Create the engine
      WarpEngine warpEngine = new WarpEngine();
      warpEngine.setDebug(debugLevel);
      warpEngine.setLogger(jbossLog);
      warpEngine.setRealm(jbossRealm);
      warpEngine.setDefaultHost(hostname);
      warpEngine.setParentClassLoader(parent);
      // Create a host for the root context     
      WarpHost warpHost = new WarpHost();
      warpHost.setAppBase("");
      warpHost.setName(hostname);
      warpHost.setDebug(debugLevel);
      warpEngine.addChild(warpHost);

      // Install the engine
      catalina.addEngine(warpEngine);

      // Create the warp connector and set its properties
      WarpConnector warpConnector = new WarpConnector();
      warpConnector.setAcceptCount(acceptCount);
      warpConnector.setEnableLookups(enableLookups);
      warpConnector.setPort(port);
      if (bindAddress != null)
         warpConnector.setAddress(hostname);
      catalina.addConnector(warpConnector);
      catalinaHost = warpHost;
   }
  
   private void initAjpConnector(ClassLoader parent, Logger jbossLog, Realm jbossRealm)
      throws IOException, NamingException
   {
      log.info("Building Ajp engine and connector");
      String hostname = "localhost";
      if( bindAddress != null )
         hostname = bindAddress.getHostName();
      // Create the engine
      Engine engine = catalina.createEngine();
      engine.setDefaultHost(hostname);
      engine.setParentClassLoader(parent);
      // Create a host for the root context
      catalinaHost = catalina.createHost(hostname, "");
      engine.addChild(catalinaHost);

      // Install the engine
      catalina.addEngine(engine);

      // Create the ajp connector and set its properties
      Ajp13Connector ajpconnector = new Ajp13Connector();
      ajpconnector.setAcceptCount(acceptCount);
      ajpconnector.setMinProcessors(minProcessors);
      ajpconnector.setMaxProcessors(maxProcessors);
      ajpconnector.setEnableLookups(enableLookups);
      ajpconnector.setPort(port);
      //catalina.addConnector(ajpconnector);
      // Enable a secure connector if a security domain was specified
      if( securityDomain != null )
      {
         ajpconnector.setScheme("https");
         ajpconnector.setSecure(true);
         InitialContext iniCtx = new InitialContext();
         SecurityDomain domain = (SecurityDomain) iniCtx.lookup(securityDomain);
         ajpconnector.setFactory(new SSLServerSocketFactory(domain));
      }
      catalina.addConnector(ajpconnector);
   }

   private WebApplication createWebContext(String ctxPath, URL warUrl,
      final WebDescriptorParser webAppParser) throws IOException
   {
      ClassLoader loader = Thread.currentThread().getContextClassLoader();
      final WebApplication appInfo = new WebApplication(warUrl.getPath(), warUrl, loader);
      final StandardContext context = (StandardContext) catalina.createContext(ctxPath, warUrl.getFile());
      context.setParentClassLoader(loader);

      /* We need to establish the JNDI ENC prior to the start of the web container
       so that init on startup servlets are able to interact with their ENC. We
       hook into the context lifecycle events to be notified of the start of the
       context as this occurs before the servlets are started.
       */
      context.addLifecycleListener(new LifecycleListener()
         {
            public void lifecycleEvent(LifecycleEvent event)
            {
               Object source = event.getSource();
               if( source == context && event.getType().equals(Lifecycle.START_EVENT) )
               {
                  if (log.isDebugEnabled())
                     log.debug("Context.lifecycleEvent, event="+event);
                  contextInit(context, appInfo, webAppParser);
               }
            }
         }
      );
      // A debug level of 1 is rather verbose so only enable debugging if trace priority is active
      if( debugLevel <= 1 )
         context.setDebug(0);
      catalinaHost.addChild(context);
      return appInfo;
   }

   /** Build the web application ENC.
    */
    private void contextInit(Context context, WebApplication appInfo, WebDescriptorParser webAppParser)
    {
        try
        {
           InputStream stream = null;
           ServletContext servletCtx = context.getServletContext();
           if (servletCtx == null)
              return;

            stream = servletCtx.getResourceAsStream("/WEB-INF/web.xml");
            if( stream == null )
            {
               log.warn("No /WEB-INF/web.xml descriptor found");
               return;
            }

            /* We need to go through the context valves and set the cache flag
             on any AuthenticatorBase to false or else the JBossSecurityMgrRealm
             is not asked to authenticate every request. This can result in
             an authenticated user thread not receiving its authenticated
             Subject and this results in an authorization failure.
             */
            Valve[] valves = ((StandardContext)context).getValves();
            for(int v = 0; v < valves.length; v ++)
            {
               Valve valve = valves[v];
               if( valve instanceof AuthenticatorBase )
               {
                  AuthenticatorBase auth = (AuthenticatorBase) valve;
                  auth.setCache(false);
               }
            }

            // Apply any extended configuration to the context

            // Get the web-app.xml and jboss-web.xml deployment descriptors
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            DocumentBuilder parser = factory.newDocumentBuilder();
            parser.setEntityResolver(new LocalResolver());
            Document webDoc = parser.parse(stream);
            Element web = webDoc.getDocumentElement();
            Element jbossWeb = null;
            stream = servletCtx.getResourceAsStream("/WEB-INF/jboss-web.xml");
            if( stream != null )
            {
                Document jbossWebDoc = parser.parse(stream);
                jbossWeb = jbossWebDoc.getDocumentElement();
            }
            // Setup the wep app JNDI java:comp/env namespace
            ClassLoader scl = context.getLoader().getClassLoader();
            webAppParser.parseWebAppDescriptors(scl, web, jbossWeb);
            // Create a web application info object
            appInfo.setWebApp(web);
            appInfo.setJbossWeb(jbossWeb);
            appInfo.setAppData(context);
        }
        catch(Exception e)
        {
            log.error("Failed to setup web application ENC", e);
        }
    }
   
    /** An implementation of EntityResolver for the web.dtd and jboss-web.dtd
     */
    private class LocalResolver implements EntityResolver
    {
        private HashMap dtds = new HashMap();

        public LocalResolver()
        {
            dtds.put("-//JBoss//DTD Web Application 2.2//EN", "org/jboss/metadata/jboss-web.dtd");
            dtds.put("-//JBoss//DTD Web Application 2.3//EN", "org/jboss/metadata/jboss-web.dtd");
            dtds.put("-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN", "javax/servlet/resources/web-app_2_2.dtd");
            dtds.put("-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN", "javax/servlet/resources/web-app_2_3.dtd");
        }

        public InputSource resolveEntity(String publicId, String systemId)
        {
            String dtd = (String) dtds.get(publicId);
            InputSource is = null;
            if (dtd != null)
            {
                try
                {
                    ClassLoader loader = Thread.currentThread().getContextClassLoader();
                    InputStream dtdStream = loader.getResourceAsStream(dtd);
                    if( dtdStream == null )
                        log.warn("Failed to find local dtd: "+dtd);
                    else
                        is = new InputSource(dtdStream);
                }
                catch(Exception ignore)
                {
                    // ignore
                }
            }
            return is;
        }
    }

}



--
Aaron Butler
Fairfield Language Technologies
http://www.RosettaStone.com
(800) 788-0822
(1) (540) 432-6166

Reply via email to