Chapter 9. Web Applications

Using Tomcat 5

In this chapter we'll discuss configuration of web applications in JBoss. We'll look at general issues specific to the JBoss/Tomcat 5 integration bundle.

9.1. The Tomcat Service

Tomcat 5 is the latest release of the Apache Java servlet container. It supports the Servlet 2.4 and JSP 2.0 specifications. Tomcat is distributed as a deployable service in jbossweb-tomcat-5.0.sar in the deploy directory. It is shipped in exploded directory form, so it's easy to inspect and update the configuration of your embedded Tomcat instance.

The main service file is META-INF/jboss-service.xml. It configures the org.jboss.web.tomcat.tc5.Tomcat5 MBean which controls Tomcat. Its configurable attributes include:

  • DefaultSecurityDomain: This specified the JAAS security domain to use in the absence of an explicit security-domain specification in the jboss-web.xml of a WAR file.

  • Java2ClassLoadingCompliance: This enables the standard Java2 parent delegation class loading model rather than the servlet model which loads from the WAR first. This is true by default as loading from WARs that include client JARs with classes used by EJBs causes class loading conflicts. If you enable the servlet class loading model by setting this flag to false, you will need to organize your deployment package to avoid duplicate classes in the deployment.

  • UseJBossWebLoader: This flag indicates that Tomcat should use a JBoss unified class loader as the web application class loader. The default is true, which means that the classes inside of the WEB-INF/classes and WEB-INF/lib directories of the WAR file are incorporated into the default shared class loader repository described in Chapter 2, The JBoss JMX Microkernel. This may not be what you want as its contrary to the default servlet class loading model and can result in sharing of classes/resources between web applications. You can disable this by setting this attribute to false.

  • LenientEjbLink: This flag indicates that ejb-link errors should be ignored in favor of trying the jndi-name in the jboss-web.xml. The default is true.

  • ManagerClass: This is the class to use as the session manager for replicating the state of web applications marked as distributable. The only provided implementation session manager is org.jboss.web.tomcat.tc5.session.JBossCacheManager, which uses JBossCache to track the distributed state.

  • SubjectAttributeName: If set, this represents request attribute name under which the JAAS Subject will be stored. There is no default value, meaning that the subject is not set in the request.

  • SessionIdAlphabet: This is the set of characters used to create a session ids. It must be made up of exactly 65 unique characters.

  • SnapshotMode: This sets the snapshot mode in a clustered environment. This must be one of instant or interval. In instant mode changes to a clustered session are instantly propagated whenever a modification is made. In interval mode all modifications are periodically propagated according to the SnapshotInterval.

  • SnapshotInterval: This sets the snapshot interval in milliseconds for the interval snapshot mode. The default is 1000ms, which is 1 second.

  • UseLocalCache: A flag indicating if the local HTTP session value should be used if it exists. When true, the existing local HTTP session values are used and updates are replicated, but updates to the same session on other nodes do not update the local session value. This mode is only useful for failover. When false, the session value is obtained from the distributed cache. This mode can be used with load balancing. The default is true.

  • UseJK: This specifies that you are using MOD_JK(2) for load balancing with sticky session combined with JvmRoute. If set to true, it will insert a JvmRouteFilter to intercept every request and replace the JvmRoute if it detects a failover. This additionally requires the JvmRoute to be set inside the engine definition in the Tomcat server.xml file. The default is false.

  • Domain: This is the JMX domain that Tomcat will register additional MBeans under. The default is jboss.web.

  • SecurityMangerService: This is a reference to the JAAS security manager for Tomcat to use. This defaults to jboss.security:service=JaasSecurityManager.

  • CacheName: This is a reference to the JBossCache to be used for HTTP session replication, if session replication is being used. The default is jboss.cache:service=TomcatClusteringCache.

9.2. The Tomcat server.xml file

While the jboss-service.xml file controls the JBoss/Tomcat integration, Tomcat has its own configuration file which guides its operation. This is the server.xml descriptor that you will find in the jbossweb-tomcat50.sar directory. Although this file doesn't have a formal DTD or schema definition, Figure 9.1, “An overview of the Tomcat server.xml file.” shows the general structure of the file.

An overview of the Tomcat server.xml file.

Figure 9.1. An overview of the Tomcat server.xml file.

We'll now look at some of the configuration options available in the server.xml file. The top level element is the Server element is the root element, which should contain a Service element representing the entire web subsystem. The only supported attribute is:

  • name: A unique name by which the service is known.

9.2.1. Connector

A Connector element configures a transport mechanism that allows clients to send requests and receive responses from the Service it is associated with. Connectors forward requests to the engine and return the results to the requesting client. Connectors support these attributes:

  • enableLookups: A flag that enables DNS resolution of the client hostname as accessed via the ServletRequest.getRemoteHost method. This flag defaults to false.

  • redirectPort: The port to which non-SSL requests will be redirected when a request for content secured under a transport confidentiality or integrity constraint is received. This defaults to the standard HTTPS port of 443.

  • secure: Sets the ServletRequest.isSecure method value flag to indicate whether or not the transport channel is secure. This flag defaults to false.

  • scheme: Sets the protocol name as accessed by the ServletRequest.getScheme method. The scheme defaults to http.

  • acceptCount: The maximum queue length for incoming connection requests when all possible request processing threads are in use. Any requests received when the queue is full will be refused. The default value is 10.

  • address. For servers with more than one IP address, this attribute specifies which address will be used for listening on the specified port. By default, this port will be used on all IP addresses associated with the server.

  • bufferSize: The size (in bytes) of the buffer to be provided for input streams created by this connector. By default, buffers of 2048 bytes will be provided.

  • connectionTimeout: The number of milliseconds this connector will wait, after accepting a connection, for the request URI line to be presented. The default value is 60000 (i.e. 60 seconds).

  • debug: The debugging detail level of log messages generated by this component, with higher numbers creating more detailed output. If not specified, this attribute is set to zero (0). Whether or not this shows up in the log further depends on the log4j category org.jboss.web.tomcat.tc5.Tomcat5 threshold.

  • maxThreads: The maximum number of request processing threads to be created by this connector, which therefore determines the maximum number of simultaneous requests that can be handled. If not specified, this attribute is set to 200.

  • maxSpareThreads: The maximum number of unused request processing threads that will be allowed to exist until the thread pool starts stopping the unnecessary threads. The default value is 50.

  • minSpareThreads: The number of request processing threads that will be created when this connector is first started. The connector will also make sure it has the specified number of idle processing threads available. This attribute should be set to a value smaller than that set for maxThreads. The default value is 4.

  • port: The TCP port number on which this connector will create a server socket and await incoming connections. Your operating system will allow only one server application to listen to a particular port number on a particular IP address.

  • proxyName: If this connector is being used in a proxy configuration, configure this attribute to specify the server name to be returned for calls to request.getServerName().

  • proxyPort: If this connector is being used in a proxy configuration, configure this attribute to specify the server port to be returned for calls to request.getServerPort().

  • tcpNoDelay: If set to true, the TCP_NO_DELAY option will be set on the server socket, which improves performance under most circumstances. This is set to true by default.

Additional attribute descriptions can be found in the Tomcat documentation at http://jakarta.apache.org/tomcat/tomcat-5.0-doc/config/http11.html

9.2.2. Engine

Each Service must have a single Engine configuration. An engine handles the requests submitted to a service via the configured connectors. The child elements supported by the embedded service include Host, Logger, DefaultContext, Valve and Listener. The supported attributes include:

  • className: The fully qualified class name of the org.apache.catalina.Engine interface implementation to use. If not specifies this defaults to org.apache.catalina.core.StandardEngine.

  • defaultHost: The name of a Host configured under the Engine that will handle requests with host names that do not match a Host configuration.

  • name: A logical name to assign the Engine. It will be used in log messages produced by the Engine.

Additional information on the Engine element can be found in the Tomcat documentation at http://jakarta.apache.org/tomcat/tomcat-5.1-doc/config/engine.html.

9.2.3. Host

A Host element represents a virtual host configuration. It is a container for web applications with a specified DNS hostname. The child elements supported by the embedded service include Alias, Logger, DefaultContext, Valve and Listener. The supported attributes include:

  • className: The fully qualified class name of the org.apache.catalina.Host interface implementation to use. If not specifies this defaults to org.apache.catalina.core.StandardHost.

  • name: The DNS name of the virtual host. At least one Host element must be configured with a name that corresponds to the defaultHost value of the containing Engine.

The Alias element is an optional child element of the Host element. Each Alias content specifies an alternate DNS name for the enclosing Host.

Additional information on the Host element can be found in the Tomcat documentation at http://jakarta.apache.org/tomcat/tomcat-4.1-doc/config/host.html.

9.2.4. DefaultContext

The DefaultContext element is a configuration template for web application contexts. It may be defined at the Engine or Host level. The child elements supported by the embedded service include WrapperLifecycle, InstanceListener, WrapperListener, and Manager. The supported attributes include:

  • className: The fully qualified class name of the org.apache.catalina.core.DefaultContext implementation. This defaults to org.apache.catalina.core.DefaultContext and if overridden must be a subclass of DefaultContext .

  • cookies: A flag indicating if sessions will be tracked using cookies. The default is true.

  • crossContext: A flag indicating if the ServletContext.getContext(String path) method should return contexts for other web applications deployed in the calling web application's virtual host. The default is false.

9.2.5. Logger

The Logger element specifies a logging configuration the Tomcat instance. The supported attributes include:

  • className: The fully qualified class name of the org.apache.catalina.Logger interface implementation. For integration with JBoss logging this should be set to org.jboss.web.tomcat.Log4jLogger.

  • verbosity: The default log level.

  • category: The default log category.

9.2.6. Valve

A Valve element configures a hook into the request processing pipeline for the web container. Valves must implement the org.apache.catalina.Valve interface. There is only one required configuration attribute:

  • className: The fully qualified class name of the org.apache.catalina.Valve interface implementation.

The most commonly used valve is the AccessLogValve, which keeps a standard HTTP access log of incoming requests. The className for the access log value is org.jboss.web.catalina.valves.AccessLogValue. The addition Valve attributes supported by it include:

  • directory: The directory path into which the access log files will be created.

  • pattern: A pattern specifier that defines the format of the log messages. This defaults to common.

  • prefix: The prefix to add to each log file name. This defaults to access_log.

  • suffix: The suffix to add to each log file name. This default to an empty string, meaning that no suffix will be added.

Additional information on the Valve element and the available valve implementations can be found in the Tomcat documentation at http://jakarta.apache.org/tomcat/tomcat-5.0-doc/config/valve.html.

9.3. Using SSL with the JBoss/Tomcat bundle

There are a few ways one can configure HTTP over SSL for the embedded Tomcat servlet container. The main difference is whether or not you use the JBoss specific connector socket factory that allows one to obtain the JSSE server certificate information from a JBossSX SecurityDomain. This requires establishing a SecurityDomain using the org.jboss.security.plugins.JaasSecurityDomain MBean. These two steps are similar to the procedure we used in Chapter 8, Security on JBoss to enable RMI with SSL encryption. A server.xml configuration file that illustrates the setup of only an SSL connector via this approach is given below . This configuration includes the same JaasSecurityDomain setup as Chapter 8, Security on JBoss, but since the descriptor is not being deployed as part of a SAR that includes the chap8.keystore, you need to copy the chap8.keystore to the server/default/conf directory.

<Server>
    <Service name="jboss.web" className="org.jboss.web.tomcat.tc5.StandardService">

        <Connector port="8080" address="${jboss.bind.address}" maxThreads="150"
            minSpareThreads="25" maxSpareThreads="75" enableLookups="false"
            redirectPort="443" acceptCount="100" connectionTimeout="20000" 
            disableUploadTimeout="true"/>

        <Connector port="443" address="${jboss.bind.address}" maxThreads="100"
            minSpareThreads="5" maxSpareThreads="15" scheme="https"
            secure="true" clientAuth="false"
            keystoreFile="${jboss.server.home.dir}/conf/chap8.keystore"
            keystorePass="rmi+ssl" sslProtocol="TLS"/>

        <Engine name="jboss.web" defaultHost="localhost">
            <Realm
                className="org.jboss.web.tomcat.security.JBossSecurityMgrRealm" 
                certificatePrincipal="org.jboss.securia.Log4jLogger"
                verbosityLevel="WARNING" category="org.jboss.web.localhost.Engine"/>
            <Host name="localhost" autoDeploy="false" deployOnStartup="false" 
                  deployXML="false">
                <DefaultContext cookies="true" crossContext="true" override="true"/>
            </Host>
        </Engine>
    </Service>
</Server>

A quick test of this config can be made by accessing the JMX console web application using this URL https://localhost/jmx-console/index.jsp.

Note: if your running on a Unix system (Linux, Solaris, OS X) that only allows root to open ports below 1024 you will need to change the port number above to something like 8443.

Factory configuration attributes:

  • algorithm: The certificate encoding algorithm to be used. If not specified, the default value is SunX509.

  • className: The fully qualified class name of the SSL server socket factory implementation class. You must specify org.apache.coyote.tomcat4.CoyoteServerSocketFactory here. Using any other socket factory will not cause an error, but the server socket will not be using SSL.

  • clientAuth: Set to true if you want the SSL stack to require a valid certificate chain from the client before accepting a connection. A false value (which is the default) will not require a certificate chain unless the client requests a resource protected by a security constraint that uses CLIENT-CERT authentication.

  • keystoreFile: The pathname of the keystore file where you have stored the server certificate to be loaded. By default, the pathname is the file .keystore in the operating system home directory of the user that is running Tomcat.

  • keystorePass: The password used to access the server certificate from the specified keystore file. The default value is changeit.

  • keystoreType: The type of keystore file to be used for the server certificate. If not specified, the default value is JKS.

  • protocol: The version of the SSL protocol to use. If not specified, the default is "TLS".

Note that if you try to test this configuration using the self-signed certificate from the Chapter 8, Security on JBoss chap8.keystore and attempt to access content over an HTTPS connection, your browser should display a warning dialog indicating that it does not trust the certificate authority that signed the certificate of the server you are connecting to. For example, when the first configuration example was tested, IE 5.5 showed the initial security alert dialog listed in Figure 9.2, “The Internet Explorer 5.5 security alert dialog.”. Figure 9.3, “The Internet Explorer 5.5 SSL certificate details dialog.” shows the server certificate details. This warning is important as anyone can generate a self-signed certificate with any information they want. Your only way to verify that the system on the other side really represents the party it claim to is by verifying that it is signed by a trusted third party.

The Internet Explorer 5.5 security alert dialog.

Figure 9.2. The Internet Explorer 5.5 security alert dialog.

The Internet Explorer 5.5 SSL certificate details dialog.

Figure 9.3. The Internet Explorer 5.5 SSL certificate details dialog.

9.4. Setting the context root of a web application

The context root of your web application determines which URLs Tomcat will delegate to your web application. If your application's context root is myapp then any request for /myapp or /myapp/* will be handled by your application unless a more specific context root exists. If a second web application were assigned the context root myapp/help then a request for /myapp/help/help.jsp would be handled by the second web application, not the first.

This relationship also holds when the context root is set to /, which is known as the root context. When an application is assigned to the root context, it will respond to all requests no handled by a more specific context root.

The context root for an application is determined by how it is deployed. When a web application is deployed inside an EAR file, the context root is specified in the application.xml file of the EAR using a context-root element inside of a web module. In the following example, the context root of the web-client.war application is set bank.

<application xmlns="http://java.sun.com/xml/ns/j2ee" version="1.4"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://java.sun.com /xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/application_1_4.xsd">
    <display-name>JBossDukesBank</display-name>

    <module>
        <ejb>bank-ejb.jar</ejb>
    </module>
    <module>
        <web>
            <web-uri>web-client.war</web-uri>
            <context-root>bank</context-root>
        </web>
    </module>

</application>

For web applications that are deployed outside of an EAR file, the context root can be specified in two ways. First, the context root can be specified inside of the WEB-INF/jboss-web.xml file. The following example shows what the jboss-web.xml file would look like for the same web-client.war file if it weren't bundled inside of an EAR file.

<jboss-web>
    <context-root>bank</context-root>
</jboss-web>

Finally, if no context root specification exists, the context root will be the base the name of the WAR file. For web-client.war, the context root would default to web-client. The only special case to this naming special name ROOT. To deploy your application under the root context, simply name it ROOT.war. JBoss already contains a ROOT.war web application in the jbossweb-tomcat50.sar directory. So, you will need to remove or rename that one to create your own root application.

Naming your WAR file after the context root they are intended to handle is a very good practice. Not only does it reduce the number of configuration settings to manage, but it improves the maintainability of the application by making the intended function of the web application clear.

9.5. Setting up Virtual Hosts

Virtual hosts allow you to group web applications according to the various DNS names by which the machine running JBoss is known. As an example, consider the server.xml configuration file given in Example 9.1, “An example virtual host configuration.”. This configuration defines a default host named vhost1.mydot.com and a second host named vhost2.mydot.com, which also has the alias www.mydot.com associated with it.

Example 9.1. An example virtual host configuration.

<Server>
   <Service name="jboss.web"
      className="org.jboss.web.tomcat.tc5.StandardService">
       
      <!-- A HTTP/1.1 Connector on port 8080 -->
      <Connector port="8080" address="${jboss.bind.address}"
                 maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
                 enableLookups="false" redirectPort="8443" acceptCount="100"
                 connectionTimeout="20000" disableUploadTimeout="true"/>

      <Engine name="jboss.web" defaultHost="vhost1">
         <Realm className="org.jboss.web.tomcat.security.JBossSecurityMgrRealm"
                certificatePrincipal="org.jboss.security.auth.certs.SubjectDNMapping"
            />
         <Logger className="org.jboss.web.tomcat.Log4jLogger"
                 verbosityLevel="WARNING"
                 category="org.jboss.web.localhost.Engine"/>

            <Host name="vhost1" autoDeploy="false"
                  deployOnStartup="false" deployXML="false">
                <Alias>vhost1.mydot.com</Alias>
                <Valve className="org.apache.catalina.valves.AccessLogValve"
                       prefix="vhost1" suffix=".log" pattern="common"
                       directory="${jboss.server.home.dir}/log"/>
            
  
                <DefaultContext cookies="true" crossContext="true" override="true"/>
            </Host>   
            <Host name="vhost2" autoDeploy="false" 
                  deployOnStartup="false" deployXML="false">
                <Alias>vhost2.mydot.com</Alias>
                <Alias>www.mydot.com</Alias>  

                <Valve className="org.apache.catalina.valves.AccessLogValve"
                       prefix="vhost2" suffix=".log" pattern="common" 
                       directory="${jboss.server.home.dir}/log"/>

                <DefaultContext cookies="true" crossContext="true" override="true"/>
            </Host>
      </Engine>
   </Service>
</Server>

When a WAR file is deployed, it will be by default associated with the virtual host whose name matches the defaultHost attribute of the containing Engine. To deploy a WAR to a specific virtual host you need to specify an appropriate virtual-host definition in your e jboss-web.xml descriptor. The following jboss-web.xml descriptor demonstrates how to deploy a WAR to the virtual host www.mydot.com. Note that we can use either the virtual host name in the config file or the actual host name.

<jboss-web>
    <context-root>/</context-root>
    <virtual-host>www.mydot.com</virtual-host>
</jboss-web>

9.6. Serving Static Content

JBoss provides a default application that serves content for the root application context. This default context is the ROOT.war application in the jbossweb-tomcat50.sar directory. You can serve static files not associated with any other application by adding that content to the ROOT.war directory. For example, if you want to have a shared image directory you could create an image subdirectory inside of ROOT.war and place the images there. You could then access an image named myimage.jpg at http://localhost:8080/images/myimage.jpg.

9.7. Using Apache with the Tomcat

In some architectures, it is useful to put an Apache web server in front of the JBoss server. External web clients talk to an Apache instance, which in turn speaks to the Tomcat instance on behalf of the clients. Apache needs to be configured to use the mod_jk module which speaks the AJP protocol to an AJP connector running in Tomcat. The provided server.xml file comes with this AJP connector enabled.

<Connector port="8009" address="${jboss.bind.address}"
           enableLookups="false" redirectPort="8443" debug="0"
           protocol="AJP/1.3" />

You'll need to consult the Apache and mod_jk documentation for complete installation instructions. Assuming you have a properly configured Apache instance, the following configuration fragment shows an example of how to connect with a WAR deployed with a context root of /jbosstest.

...
LoadModule jk_module libexec/mod_jk.so
AddModule mod_jk.c
           
<IfModule mod_jk.c>
    JkWorkersFile /tmp/workers.properties
    JkLogFile /tmp/mod_jk.log
    JkLogLevel debug
    JkMount /jbosstest/* ajp13
</IfModule>

The workers.properties file contains the details of how to contact the JBoss instance.

9.8. Using Clustering

JBoss supports clustering in the embedded Tomcat service. The steps to setup clustering of Tomcat embedded containers is:

  • If you are using a load balancer, make sure that your setup uses sticky sessions. This means that if a user that starts a session on node A, all subsequent requests are forwarded to node A as long node A is up and running. For configuration of Apache sticky sessions see http://www.ubeans.com/tomcat/ for details.

  • If you aren't using the all configuration, make sure that cluster-service.xml is in your deploy directory. If it isn't, copy cluster-service.xml from server/all/deploy into your deploy directory. You also need the jgroups.jar in your lib directory. This can be found in the server/all/lib directory.

  • Start JBoss to check if your setup works. Look at the JMX management console (http://localhost:8080/jmx-console/). Find the jboss.cache:service=TomcatClusteringCache MBean. The StateString must be Started. If it is Stopped look in the server's log file.

  • To enable clustering of your web applications you must mark them as distributable in the web.xml descriptor. For example:

    <?xml version="1.0"?> 
    <!DOCTYPE web-app PUBLIC
        "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
        "http://java.sun.com/dtd/web-app_2_3.dtd ">
    <web-app>
        <distributable/>
        <!-- ... -->
    </web-app>

  • Deploy your WAR as usual and it should now be clustered.

If you have deployed and accessed your application, go back to the jboss.cache:service=TomcatClusteringCache MBean and invoke the printDetails operation. You should see output resembling the following.

/JSESSION

/n6HywRwITbY-xvzaZ0LS5Q**
n6HywRwITbY-xvzaZ0LS5Q**: org.jboss.invocation.MarshalledValue@9c1dddab

/R1T4Dapn7c8T-+Ynd9v9MA**
R1T4Dapn7c8T-+Ynd9v9MA**: org.jboss.invocation.MarshalledValue@8c0f60b6

This output shows two separate web sessions that are being shared via JBossCache. If you don't see any output, either the application was not correctly marked as distributable or you haven't accessed the a part of application that places values in the HTTP session.

9.9. Integrating Third Party Servlet Containers

This section describes the steps for integrating a third party web container into the JBoss application server framework. A web container is a J2EE server component that enables access to servlets and JSP pages. The most widely used servlet container is Tomcat, and this is the default web container used by JBoss.

Integrating a servlet container into JBoss consists of mapping web.xml JNDI information into the JBoss JNDI namespace using an optional jboss-web.xml descriptor as well as delegating authentication and authorization to the JBoss security layer. The org.jboss.web.AbstractWebContainer class exists to simplify these tasks.

9.9.1. The AbstractWebContainer Class

The org.jboss.web.AbstractWebContainer class is an implementation of a template pattern for web container integration into JBoss. Web container providers wishing to integrate their container into a JBoss server should create a subclass of AbstractWebContainer and provide the web container specific setup and WAR deployment steps. The AbstractWebContainer provides support for parsing the standard J2EE web.xml web application deployment descriptor JNDI and security elements as well as support for parsing the JBoss specific jboss-web.xml descriptor. Parsing of these deployment descriptors is performed to generate an integrated JNDI environment and security context.

9.9.1.1. The AbstractWebContainer Contract

The AbstractWebContainer is an abstract class that implements the org.jboss.web.AbstractWebContainerMBean interface used by the JBoss J2EE deployer to delegate the task of installing war files needing to be deployed. We'll look at some of the key methods of the AbstractWebContainer below.

public boolean accepts(DeploymentInfo sdi)
{
    String warFile = sdi.url.getFile();
    return warFile.endsWith("war") || warFile.endsWith("war/");
}

The accepts method is implemented by JBoss deployers to indicate which type of deployments they accept. The AbstractWebContainer handles the deployments of WARs as JARs or unpacked directories.

public synchronized void start(DeploymentInfo di) throws DeploymentException
{
    Thread thread = Thread.currentThread();
    ClassLoader appClassLoader = thread.getContextClassLoader();

    try {
        // Create a classloader for the war to ensure a unique ENC
        URL[] empty = {};
        URLClassLoader warLoader = URLClassLoader.newInstance(empty, di.ucl);
        thread.setContextClassLoader(warLoader);
        WebDescriptorParser webAppParser = new DescriptorParser(di);

        String webContext = di.webContext;
        if (webContext != null) {
            if (webContext.length() > 0 && webContext.charAt(0) !=
                '/') {
                webContext = "/" + webContext;
            }
        }

        // Get the war URL
        URL warURL = di.localUrl != null ? di.localUrl : di.url;
        if (log.isDebugEnabled()) {
            log.debug("webContext: " + webContext);
            log.debug("warURL: " + warURL);
            log.debug("webAppParser: " + webAppParser);
        }

        // Parse the web.xml and jboss-web.xml descriptors
        WebMetaData metaData = (WebMetaData) di.metaData;
        parseMetaData(webContext, warURL, di.shortName, metaData);

        WebApplication warInfo = new WebApplication(metaData);
        warInfo.setDeploymentInfo(di);
        performDeploy(warInfo, warURL.toString(), webAppParser);
        deploymentMap.put(warURL.toString(), warInfo);

        // Generate an event for the startup
        super.start(di);
    } catch(DeploymentException e) {
        throw e;
    } catch(Exception e) {
        throw new DeploymentException("Error during deploy", e);
    } finally {
        thread.setContextClassLoader(appClassLoader);
    }
}

This section corresponds to the start method. This method is a template pattern method implementation. The argument to the deploy method is the WAR deployment info object. This contains the URL to the WAR, the UnifiedClassLoader for the WAR, the parent archive such as an EAR, and the J2EE application.xml context-root if the WAR is part of an EAR.

The first step of the start method is to save the current thread context class loader and then create another URLClassCloader (warLoader) using the WAR UnifiedClassLoader as its parent. This warLoader is used to ensure a unique JNDI ENC (enterprise naming context) for the WAR will be created. Chapter 3, Naming on JBoss mentioned that the java:comp context's uniqueness was determined by the class loader that created the java:comp context. The warLoader ClassLoader is set as the current thread context class loader before the performDeploy call is made. Next, the web.xml and jboss-web.xml descriptors are parsed by calling parseMetaData. Next, the web container-specific subclass is asked to perform the actual deployment of the WAR through the performDeploy call. The WebApplication object for this deployment is stored in the deployed application map using the warUrl as the key. The final step is to restore the thread context class loader to the one that existed at the start of the method.

protected abstract void performDeploy(WebApplication webApp, String warUrl,
                                      WebDescriptorParser webAppParser) 
    throws Exception;

This is the signature for the abstract performDeploy method. This method is called by the start method and must be overridden by subclasses to perform the web container specific deployment steps. A WebApplication is provided as an argument, and this contains the metadata from the web.xml descriptor, and the jboss-web.xml descriptor. The metadata contains the context-root value for the web module from the J2EE application.xml descriptor, or if this is a stand-alone deployment, the jboss-web.xml descriptor. The metadata also contains any jboss-web.xml descriptor virtual-host value. On return from performDeploy, the WebApplication must be populated with the class loader of the servlet context for the deployment. The warUrl argument is the string for the URL of the Web application WAR to deploy. The webAppParser argument is a callback handle the subclass must use to invoke the parseWebAppDescriptors method to set up the Web application JNDI environment. This callback provides a hook for the subclass to establish the web application JNDI environment before any servlets are created that are to be loaded on startup of the WAR. A subclass' performDeploy method implementation needs to be arranged so that it can call the parseWebAppDescriptors before starting any servlets that need to access JNDI for JBoss resources like EJBs, resource factories, and so on. One important setup detail that needs to be handled by a subclass implementation is to use the current thread context class loader as the parent class loader for any Web container-specific class loader created. Failure to do this results in problems for web applications that attempt to access EJBs or JBoss resources through the JNDI ENC.

public synchronized void stop(DeploymentInfo di)
    throws DeploymentException
{
    URL warURL = di.localUrl != null ? di.localUrl : di.url;
    String warUrl = warURL.toString();
    try {
        performUndeploy(warUrl);
        // Remove the web application ENC...
        deploymentMap.remove(warUrl);
        // Generate an event for the stop
        super.stop(di);
    } catch(DeploymentException e) {
        throw e;
    } catch(Exception e) {
        throw new DeploymentException("Error during deploy", e);
    }
}

This is the stop method. It calls the subclass performUndeploy method to perform the container-specific undeployment steps. After undeploying the application, the warUrl is unregistered from the deployment map. The warUrl argument is the string URL of the WAR as originally passed to the performDeploy method.

protected abstract void performUndeploy(String warUrl) throws Exception;

This is the signature of the abstract performUndeploy method, which is called from the stop method. A call to performUndeploy asks the subclass to perform the Web container-specific undeployment steps.

public void setConfig(Element config)
{
}

The setConfig method is a stub method that subclasses can override if they want to support an arbitrary extended configuration beyond that which is possible through MBean attributes. The config argument is the parent DOM element for an arbitrary hierarchy given by the child element of the Config attribute in the mbean element specification of the jboss-service.xml descriptor of the web container service. You'll see an example use of this method and config value when you look at the MBean that supports embedding Tomcat into JBoss.

protected void parseWebAppDescriptors(DeploymentInfo di, 
                                      ClassLoader loader,
                                      WebMetaData metaData)
    throws Exception
{
    log.debug("AbstractWebContainer.parseWebAppDescriptors, Begin");
    InitialContext iniCtx = new InitialContext();
    Context envCtx = null;
    Thread currentThread = Thread.currentThread();
    ClassLoader currentLoader = currentThread.getContextClassLoader();
    try {
        // Create a java:comp/env environment unique for the web application
        log.debug("Creating ENC using ClassLoader: "+loader);
        ClassLoader parent = loader.getParent();
        while (parent != null ) {
            log.debug(".."+parent);
            parent = parent.getParent();
        }
        currentThread.setContextClassLoader(loader);
        metaData.setENCLoader(loader);
        envCtx = (Context) iniCtx.lookup("java:comp");
        // Add a link to the global transaction manager
        envCtx.bind("UserTransaction", new LinkRef("UserTransaction"));
        log.debug("Linked java:comp/UserTransaction to JNDI name: UserTransaction");
        envCtx = envCtx.createSubcontext("env");
    } finally {
        currentThread.setContextClassLoader(currentLoader);
    }
                
    Iterator envEntries = metaData.getEnvironmentEntries();
    log.debug("addEnvEntries");
    addEnvEntries(envEntries, envCtx);
                
    Iterator resourceEnvRefs = metaData.getResourceEnvReferences();
    log.debug("linkResourceEnvRefs");
    linkResourceEnvRefs(resourceEnvRefs, envCtx);

    Iterator resourceRefs = metaData.getResourceReferences();
    log.debug("linkResourceRefs");
    linkResourceRefs(resourceRefs, envCtx);
              
    Iterator ejbRefs = metaData.getEjbReferences();
    log.debug("linkEjbRefs");
    linkEjbRefs(ejbRefs, envCtx, di);
                
    Iterator ejbLocalRefs = metaData.getEjbLocalReferences();
    log.debug("linkEjbLocalRefs");                
    linkEjbLocalRefs(ejbLocalRefs, envCtx, di);
               
    String securityDomain = metaData.getSecurityDomain();
    log.debug("linkSecurityDomain");
    linkSecurityDomain(securityDomain, envCtx);
                
    log.debug("AbstractWebContainer.parseWebAppDescriptors, End");
 }

The parseWebAppDescriptors method is invoked from within the subclass performDeploy method when it invokes the webAppParser.parseWebAppDescriptors callback to setup the web application ENC (java:comp/env) env-entry, resource-env-ref, resource-ref, local-ejb-ref and ejb-ref values declared in the web.xml descriptor. The creation of the env-entry values does not require a jboss-web.xml descriptor. The creation of the resource-env-ref, resource-ref, and ejb-ref elements does require a jboss-web.xml descriptor for the JNDI name of the deployed resources/EJBs. Because the ENC context is private to the web application, the web application class loader is used to identify the ENC. The loader argument is the class loader for the web application, and may not be null. The metaData argument is the WebMetaData argument passed to the subclass performDeploy method. The implementation of the parseWebAppDescriptors uses the metadata information from the WAR deployment descriptors and then creates the JNDI ENC bindings.

protected void addEnvEntries(Iterator envEntries, Context envCtx)
    throws ClassNotFoundException, NamingException
{
}

The addEnvEntries method creates the java:comp/env web application env-entry bindings that were specified in the web.xml descriptor.

protected void linkResourceEnvRefs(Iterator resourceEnvRefs, Context envCtx)
    throws NamingException
{
}

The linkResourceEnvRefs method maps the java:comp/env/xxx web application JNDI ENC resource-env-ref web.xml descriptor elements onto the deployed JNDI names using the mappings specified in the jboss-web.xml descriptor.

protected void linkResourceRefs(Iterator resourceRefs, Context envCtx)
    throws NamingException
{
}

The linkResourceRefs method maps the java:comp/env/xxx web application JNDI ENC resource-ref web.xml descriptor elements onto the deployed JNDI names using the mappings specified in the jboss-web.xml descriptor.

protected void linkEjbRefs(Iterator ejbRefs, Context envCtx, DeploymentInfo di)
    throws NamingException
{
}

The linkEjbRefs method maps the java:comp/env/ejb web application JNDI ENC ejb-ref web.xml descriptor elements onto the deployed JNDI names using the mappings specified in the jboss-web.xml descriptor.

protected void linkEjbLocalRefs(Iterator ejbRefs, Context envCtx,
                                DeploymentInfo di)
    throws NamingException
{
}

The linkEjbLocalRefs method maps the java:comp/env/ejb Web application JNDI ENC ejb-local-ref web.xml descriptor elements onto the deployed JNDI names using the ejb-link mappings specified in the web.xml descriptor.

protected void linkSecurityDomain(String securityDomain, Context envCtx)
    throws NamingException
{
}

The linkSecurityDomain method creates a java:comp/env/security context that contains a securityMgr binding pointing to the AuthenticationManager implementation and a realmMapping binding pointing to the RealmMapping implementation that is associated with the security domain for the web application. Also creates is a subject binding that provides dynamic access to the authenticated Subject associated with the request thread. If the jboss-web.xml descriptor contained a security-domain element, the bindings are javax.naming.LinkRefs to the JNDI name specified by the security-domain element, or subcontexts of this name. If there was no security-domain element, the bindings are to org.jboss.security.plugins.NullSecurityManager instance that simply allows all authentication and authorization checks.

public String[] getCompileClasspath(ClassLoader loader)
{
}

The getCompileClasspath method is a utility method available for web containers to generate a classpath that walks up the class loader chain starting at the given loader and queries each class loader for the URLs it serves to build a complete classpath of URL strings. This is needed by some JSP compiler implementations (Jasper for one) that expect to be given a complete classpath for compilation.

9.9.1.2. Creating an AbstractWebContainer Subclass

To integrate a web container into JBoss you need to create a subclass of AbstractWebContainer and implement the required performDeploy(WebApplication, String, WebDescriptorParser) and performUndeploy(String) methods as described in the preceding section. The following additional integration points should be considered as well.

9.9.1.2.1. Use the Thread Context Class Loader

Although this issue was noted in the performDeploy method description, we'll repeat it here since it is such a critical detail. During the setup of a WAR container, the current thread context class loader must be used as the parent class loader for any web container specific class loader that is created. Failure to do this will result in problems for web applications that attempt to access EJBs or JBoss resources through the JNDI ENC.

9.9.1.2.2. Integrate Logging Using log4j

JBoss uses the Apache log4j logging API as its internal logging API. For a web container to integrate well with JBoss it needs to provide a mapping between the web container logging abstraction to the log4j API. As a subclass of AbstractWebContainer, your integration class has access to the log4j interface via the super.log instance variable or equivalently, the superclass getLog() method. This is an instance of the org.jboss.logging.Logger class that wraps the log4j category. The name of the log4j category is the name of the container subclass.

9.9.1.2.3. Delegate web container authentication and authorization to JBossSX

Ideally both web application and EJB authentication and authorization are handled by the same security manager. To enable this for your web container you must hook into the JBoss security layer. This typically requires a request interceptor that maps from the web container security callouts to the JBoss security API. Integration with the JBossSX security framework is based on the establishment of a java:comp/env/security context as described in the linkSecurityDomain method comments in the previous section. The security context provides access to the JBossSX security manager interface implementations associated with the web application for use by subclass request interceptors. An outline of the steps for authenticating a user using the security context is presented in Example 9.2, “A pseudo-code description of authenticating a user via the JBossSX API and the java:comp/env/security JNDI context.” in quasi pseudo-code. Example 9.3, “A pseudo-code description of authorization a user via the JBossSX API and the java:comp/env/security JNDI context.” provides the equivalent process for the authorization of a user.

Example 9.2. A pseudo-code description of authenticating a user via the JBossSX API and the java:comp/env/security JNDI context.

// Get the username and password from the request context...
HttpServletRequest request = ...;
String username = getUsername(request);
String password = getPassword(request);

// Get the JBoss security manager from the ENC context
InitialContext iniCtx = new InitialContext();
AuthenticationManager securityMgr = (AuthenticationManager)
    iniCtx.lookup("java:comp/env/security/securityMgr");

SimplePrincipal principal = new SimplePrincipal(username);
if (securityMgr.isValid(principal, password)) {
    // Indicate the user is allowed access to the web content...
    // Propagate the user info to JBoss for any calls into made by the servlet
    SecurityAssociation.setPrincipal(principal);
    SecurityAssociation.setCredential(password.toCharArray());
} else {
    // Deny access...
}

Example 9.3. A pseudo-code description of authorization a user via the JBossSX API and the java:comp/env/security JNDI context.

// Get the username & required roles from the request context...
HttpServletRequest request = ...;
String username = getUsername(request);
String[] roles = getContentRoles(request);

// Get the JBoss security manager from the ENC context
InitialContext iniCtx = new InitialContext();
RealmMapping securityMgr = (RealmMapping)
    iniCtx.lookup("java:comp/env/security/realmMapping");

SimplePrincipal principal = new SimplePrincipal(username);
Set requiredRoles = new HashSet(java.util.Arrays.asList(roles));

if (securityMgr.doesUserHaveRole(principal, requiredRoles)) {
    // Indicate user has the required roles for the web content...
} else {
    // Deny access...
}