Chapter 9. Web Applications

Using Tomcat 5

This chapter discusses the configuration of web applications in JBoss. It looks at general issues specific to the JBoss/Tomcat 5 integration bundle.

9.1. The Tomcat Service

Tomcat 5.5, the latest release of the Apache Java servlet container, supports the Servlet 2.4 and JSP 2.0 specifications. Tomcat is distributed as a deployable service in jbossweb-tomcat-55.sar in the deploy directory. It is shipped in exploded directory form, so it's easy to inspect and update the configuration of the 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 specifies 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. It is true by default, otherwise 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 need to organize yodeployment package to avoid having 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 allows classes and resources to be shared between web applications. If this is not what you want, you can disable this behaviour 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 the 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: This is a flag that indicates whether the local HTTP session value should be used if it exists. When it is 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 it is 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 under which Tomcat will register additional MBeans. The default domain is jboss.web.

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

9.2. The server.xml file

While the jboss-service.xml file controls the Tomcat integration service, Tomcat itself has its own configuration file which guides its operation. This is the server.xml descriptor that you will find in the jbossweb-tomcat55.sar directory.

The server.xml file doesn't have a formal DTD or schema definition, so we'll just cover the major configurable elements available. The top-level element is the Server element. It should contain a Service element representing the entire web subsystem. The supported attributes are:

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

  • className: The name of the class that provides the service implementation.

9.2.1. The Connector element

A Service element should have one or more connectors under it. A connector 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. Each connector is configured using Connector element. Connectors support these attributes:

  • className: the fully qualified name of the class of the connector implementation. The class must implement the org.apache.catalina.Connector interface. The embedded service defaults to the org.apache.catalina.connector.http.HttpConnector, which is the HTTP connector implementation.

  • acceptCount: This is 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: This is 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: This is 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: This is 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.

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

  • maxThreads: This is 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: This is 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: This is 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: This is the TCP port number on which this connector will create a server socket and await incoming connections. Only one server application can listen to a particular port number on a particular IP address at a time.

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

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

  • redirectPort: This is the port that non-SSL requests will be redirected to 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: This sets the ServletRequest.isSecure method value flag to indicate whether or not the transport channel is secure. This flag defaults to false.

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

  • tcpNoDelay: If this attribute is 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.

You can find attribute descriptions in the Tomcat documentation at http://jakarta.apache.org/tomcat/tomcat-5.5-doc/config/http.html.

9.2.2. The Engine element

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, Valve and Listener. The supported attributes include:

  • className: This is 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: This is he name of a Host configured under the Engine that will handle requests with host names that do not match a Host configuration.

  • name: This is a logical name assigned to the Engine. It is used in log messages produced by the Engine.

You can find additional information on the Engine element in the Tomcat documentation at http://jakarta.apache.org/tomcat/tomcat-5.5-doc/config/engine.html.

9.2.3. The Host element

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, Valve and Listener. The supported attributes include:

  • className: This is 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: This is 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 specifies an alternate DNS name for the enclosing Host.

You can find additional information on the Host element in the Tomcat documentation at http://jakarta.apache.org/tomcat/tomcat-5.5-doc/config/host.html.

9.2.4. The Valve element

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: This is 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 additional attributes its supports include:

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

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

  • prefix: This is the prefix to add to each log file name. It defaults to access_log.

  • suffix: This is the suffix to add to each log file name. It defaults to an empty string, meaning that no suffix will be added.

You can find additional information on the Valve element and the available valve implementations in the Tomcat documentation at http://jakarta.apache.org/tomcat/tomcat-5.5-doc/config/valve.html.

9.3. The context.xml file

The context.xml file contains the default Context element used for all web applications in the system. The supported attributes include:

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

  • crossContext: This is a flag indicating whether the ServletContext.getContext(String path) method should return contexts for other web applications deployed in the calling web application's virtual host.

You can find additional information on the Context element in the Tomcat documentation at http://jakarta.apache.org/tomcat/tomcat-5.5-doc/config/context.html.

9.4. Using SSL with the JBoss/Tomcat bundle

There are a few ways you can configure HTTP over SSL for the embedded Tomcat servlet container depending on whether or not you use the JBoss specific connector socket factory, which allows you 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 use 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.

<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>

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. You can test this configuration by accessing the JMX console web application over HTTPS using this URL: https://localhost/jmx-console.

Note: if you are running on a system that requires special permissions to open ports below 1024, it might be easier need to change the port number to one above 1024. Port 8443 is commonly used because of this.

The configurable attributes are as follows:

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

  • className: This is 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: This attribute should be 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: This is 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: This is 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 we tested the first configuration example, IE 5.5 showed the initial security alert dialog listed in Figure 9.1, “The Internet Explorer 5.5 security alert dialog.”. Figure 9.2, “The Internet Explorer 5.5 SSL certificate details dialog.” shows the server certificate details. This warning is important because anyone can generate a self-signed certificate with any information desired. 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.1. The Internet Explorer 5.5 security alert dialog.

The Internet Explorer 5.5 SSL certificate details dialog.

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

9.5. Setting the context root of a web application

The context root of a 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, 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 to 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 an EAR file, the context root can be specified in two ways. First, the context root can be specified in the WEB-INF/jboss-web.xml file. The following example shows what the jboss-web.xml file would look like 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 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 an application under the root context, you simply name it ROOT.war. JBoss already contains a ROOT.war web application in the jbossweb-tomcat55.sar directory. 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.6. 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, “A 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. A 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 is associated by default 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 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 you 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.7. 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-tomcat55.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 in ROOT.war and place the images there. You could then access an image named myimage.jpg at http://localhost:8080/images/myimage.jpg.

9.8. Using Apache with 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.9. Session Replication with JBoss Cache

In a clustered web application, session state can be replicated between nodes in order to support seemless fail-over of clients. The architecture is simple. A front-end hardware or software load balancer should be used to provide fail-over of clients. Make sure that the load balancer provides sticky sessions. This means that if a user that starts a session on a node, all subsequent requests are forwarded to that node as it is up and running. If the node fails, the load balancer will choose another node in the cluster. Session replication insures that the application state on the second node is preserved so the external client will see no disruption of service.

9.9.1. Configuring session replication

Clustering services are provided in the all configuration. If you are not running from the all configuration, session replication can be enabled by copying deploy/tc5-cluster-service.xml, lib/jgroups.jar and lib/jboss-cache.jar from server/all into your server configuration directory.

The tc5-cluster-service.xml file contains the configuration for the org.cache.TreeCache MBean. The configurable attributes are as follows:

  • TransactionManagerLookupClass: This sets the transaction manager factory. The default value is org.jboss.cache.JBossTransactionManagerLookup. It expects to find the transaction manager under java:/TransactionManager.

  • IsolationLevel: JBoss Cache is transactional, and this attribute sets the isolation level for updates to the distributed cache. The valid values are SERIALIZABLE, REPEATABLE_READ, READ_COMMITTED, READ_UNCOMMITTED, and NONE. These isolation levels mean the same thing as isolation levels on the database. The default isolation of REPEATABLE_READ makes sense for most web applications.

  • CacheMode: This attribute controls how the cache is replicated. The valid values are REPL_SYNC and REPL_ASYNC, which determine whether changes are made synchronously or asynchronously. Using synchronous replication makes sure changes propagated to the cluster before the web request completes. However, synchronous replication is much slower. For asyncrhonous access, you will want to enable and tune the replication queue.

  • ClusterName: This is the name of the partition that the cache works within. The default cluster name is Tomcat-Cluster. All the nodes should use the same cluster name. Although session replication can share the same channel (multicast address and port) with other clustered services in JBoss, replication should have it's own partition name.

  • ClusterConfig: This attribute configures the underlying JGroups stack. The most import configuration elements are the muliticast adress and port, mcast_addr and mcast_port respectively, to use for clustered communication. These values should make sense for your network. See the JGroups documentation at http://www.jgroups.org/ for more complete instructions for configuring and tuning clustered communication.

  • LockAcquisitionTimeout: This attribute sets the maximum number of milliseconds to wait for a lock acquisition. The default value is 15000.

  • UseReplQueue: When using asynchronous replication, setting this attribute to true enables the replication queue. This allows multiple cache updates to be bundled together to improve performance. The replication queue properties ReplQueueInterval and ReplQueueMaxElements control whe

  • ReplQueueInterval: This is the time in milliseconds JBoss Cache will wait before sending items in the replication queue.

  • ReplQueueMaxElements: This is the maximum number of elements allowed in the replication queue before JBoss Cache will send an update.

9.9.2. Enabling session replication in your application

To enable clustering of your web application you must it as distributable in the web.xml descriptor. Here's an example:

<?xml version="1.0"?> 
<web-app  xmlns="http://java.sun.com/xml/ns/j2ee"
          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/web-app_2_4.xsd" 
          version="2.4">
    <distributable/>
    <!-- ... -->
</web-app>

You can futher configure session replication using the replication-config element in the jboss-web.xml file. Here is an example:

<jboss-web>
    <replication-config>
        <replication-trigger>SET_AND_NON_PRIMITIVE_GET</replication-trigger>
        <replication-granularity>SESSION</replication-granularity>
    </replication-config>
</jboss-web>

The replication-trigger element determines what triggers a session replication (or when is a session is considered dirty). It has 4 options:

  • SET: With this policy, the session is considered dirty only when an attribute is set in the session. If your application always writes changed value back into the session, this option will be most optimized in term of performance. If an object is retrieved from the session and modified without being written back into the session, the change to that object will not be replicated.

  • SET_AND_GET: With this policy, any attribute that is get or set will be marked as dirty. If an object is retrieved from the session and modified without being written back into the session, the change to that object will be replicated. This option can have significant performance implications.

  • SET_AND_NON_PRIMITIVE_GET: This policy is similar to the SET_AND_GET policy except that retrieving primitve w Session get and non-primitive get are considered dirty. For example, the http session request may retrieve a non-primitive object instance from the attribute and then modify the instance. If we don't specify that non-primitive get is considered dirty, then the modification will not be replication properly. This is the default value.

  • ACCESS: This option causes the session to be marked as dirty whenever it is accessed. Since a the session is accedded during each HTTP request, it will be replicated with each request. The access time stamp in the session instance will be updated as well. Since the time stamp may not be updated in other clustering nodes because of no replication, the session in other nodes may expire before the active node if the HTTP request does not retrieve or modify any session attributes. When this option is set, the session timestamps will be synchronized throughout the cluster nodes. Note that use of this option can have a significant performance impact, so use it with caution.

The replication-granularity element controls the size of the replication units. The supported values are:

  • session: Replication is per session instance. As long as it is considered modified when the snapshot manager is called, the whole session object will be serialized.

  • attribute: Replication is only for the dirty attributes plus some session data, like, lastAccessTime. For session that carries large amount of data, this option can increase replication performance.

If your sessions are generally small, session is the better policy. If your session is larger and some partsare infrequently accessed, attribute replication will be more effective.

9.9.3. Monitoring session replication

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

/JSESSION

/quote

/FB04767C454BAB3B2E462A27CB571330
VERSION: 6
FB04767C454BAB3B2E462A27CB571330: org.jboss.invocation.MarshalledValue@1f13a81c

/AxCI8Ovt5VQTfNyYy9Bomw**
VERSION: 4
AxCI8Ovt5VQTfNyYy9Bomw**: org.jboss.invocation.MarshalledValue@e076e4c8

This output shows two separate web sessions, in one application named quote, that are being shared via JBossCache. This example uses a replication-granularity of session. Had attribute level replication been used, there would be additional entries showing each replicated session attribute. In either case, the replicated values are stored in an opaque MarshelledValue container. There aren't currently any tools that allow you to inspect the contents of the replicated session values. 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. The org.jboss.cache and org.jboss.web logging categories provide additional insight into session replication useful for debugging purposes.

9.10. Using Single Sign On

JBoss supports clustered single sign-on, allowing a user to authenticate to one application on a JBoss server and to be recognized on all applications, on that same machine or on another node in the cluster, that are deployed on the same virtual host. Authentication replication is handled by the HTTP session replication service. Although session replication does not need to be explicitly enabled for the applications in question, the tc5-cluster-service.xml does need to be deployed as shown in Section 9.9, “Session Replication with JBoss Cache”.

To enable single sign-on, you must add the ClusteredSingleSignOn valve to the appropriate Host elements of the tomcat server.xml file. The valve configuration is shown here:

<Valve className="org.jboss.web.tomcat.tc5.sso.ClusteredSingleSignOn" /> 

9.11. 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, which 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.11.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. The deployment descriptors are parsed to generate an integrated JNDI environment and security context.

9.11.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. We'll look at some of the key methods of the AbstractWebContainer next, starting with the accepts method.

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

Deployers implement the accepts method to indicate which type of deployments they accept. The AbstractWebContainer handles the deployments of WARs as JARs or unpacked directories.

This following section corresponds to the start method.

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);
    }
}

The start 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 that the WAR has a unique JNDI enterprise naming context (ENC). Chapter 3, Naming on JBoss mentions that the java:comp context's uniqueness is determined by the class loader that creates 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.

This is the signature for the abstract performDeploy method.

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

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, from 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.

This is the stop method.

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);
    }
}

The stop method 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. This is the signature of the abstract performUndeploy method, which is called from the stop method.

protected abstract void performUndeploy(String warUrl) throws Exception;

A call to performUndeploy asks the subclass to perform the Web container-specific undeployment steps.

This is the setConfig method.

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.

The following is the parseWebAppDescriptors method.

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 parseWebAppDescriptors uses the metadata information from the WAR deployment descriptors and then creates the JNDI ENC bindings.

The following is the addEnvEntries method.

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.

This is the linkResourceEnvRefs method.

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.

This is the linkResourceRefs method.

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.

The following is the linkEjbRefs method.

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.

This is the linkEjbLocalRefs method.

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.

Here is the linkSecurityDomain method.

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. It 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 contains a security-domain element, the bindings are javax.naming.LinkRefs to the JNDI name specified by the security-domain element. If there is no security-domain element, the default security domain, java:/jaas/other, is used.

Finally, here is the getCompileClasspath method.

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.11.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.11.1.2.1. Use the Thread Context Class Loader

Although this issue has been noted already, we repeat it here because 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.11.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 and 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.11.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 earlier. 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...
}