More Books
JBoss 4.0 The Official Guide
JBoss® 4.0 The Official Guide
Table of Contents
Copyright
About the Authors
We Want to Hear from You!
Introduction
What This Book Covers
About JBoss
About Open Source
About Professional Open Source
What's New in JBoss 4.0
Chapter 1.  Installing and Building the JBoss Server
Getting the Binary Files
Installing the Binary Package
Basic Installation Testing
Booting from a Network Server
Building the Server from Source Code
Chapter 2.  The JBoss JMX Microkernel
JMX
The JBoss JMX Implementation Architecture
Connecting to the JMX Server
Using JMX as a Microkernel
The JBoss Deployer Architecture
Exposing MBean Events via SNMP
Remote Access to Services, Detached Invokers
Chapter 3.  Naming on JBoss
An Overview of JNDI
The JBossNS Architecture
Chapter 4.  Transactions on JBoss
Transaction and JTA Overview
JBoss Transaction Internals
Chapter 5.  EJBs on JBoss
The EJB Client-Side View
The EJB Server-Side View
The EJB Container
Entity Bean Locking and Deadlock Detection
Chapter 6.  Messaging on JBoss
JMS Examples
JBossMQ Overview
JBossMQ Configuration and MBeans
Specifying the MDB JMS Provider
Chapter 7.  Connectors on JBoss
JCA Overview
An Overview of the JBossCX Architecture
Configuring JDBC Datasources
Configuring Generic JCA Adaptors
Chapter 8.  Security on JBoss
J2EE Declarative Security Overview
An Introduction to JAAS
The JBoss Security Model
The JBossSX Architecture
The Secure Remote Password (SRP) Protocol
Running JBoss with a Java 2 Security Manager
Using SSL with JBoss and JSSE
Configuring JBoss for Use Behind a Firewall
Securing the JBoss Server
Chapter 9.  Web Applications
The Tomcat Service
The Tomcat server.xml File
The Engine Element
The Host Element
Using SSL with the JBoss/Tomcat Bundle
Setting the Context Root of a Web Application
Setting Up Virtual Hosts
Serving Static Content
Using Apache with Tomcat
Using Clustering
Integrating Third-Party Servlet Containers
Chapter 10.  MBean Services Miscellany
System Properties Management
Property Editor Management
Services Binding Management
Scheduling Tasks
The Log4j Service MBean
RMI Dynamic Class Loading
Chapter 11.  The CMP Engine
Example Code
The jbosscmp-jdbc Structure
Entity Beans
CMP Fields
Container-Managed Relationships
Declaring Queries
Optimized Loading
The Loading Process
Transactions
Optimistic Locking
Entity Commands and Primary Key Generation
JBoss Global Defaults
Datasource Customization
Chapter 12.  Web Services
JAX-RPC Service Endpoints
Enterprise JavaBean Endpoints
Web Services ClientsA JAX-RPC Client
Service References
Chapter 13.  Hibernate
The Hibernate MBean
Hibernate Archives
Using Hibernate Objects
Using a HAR File Inside an EAR File
The HAR Deployer
Chapter 14.  Aspect-Oriented Programming (AOP) Support
JBoss AOP: EJB-Style Services for Plain Java Objects
Why AOP?
Basic Concepts of AOP
Building JBoss AOP Applications
The JBoss AOP Deployer
Packaging and Deploying AOP Applications to JBoss
Appendix A.  The GNU Lesser General Public License (LGPL)
GNU General Public License
Appendix B.  Example Installation
Index
SYMBOL
A
B
C
D
E
F
G
H
I
J
K
L
M
N
O
P
Q
R
S
T
U
V
W
X

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.

The AbstractWebContainer Class

The org.jboss.web.AbstractWebContainer class is an implementation of a template pattern for web container integration in JBoss. Web container providers wishing to integrate a 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 done to generate an integrated JNDI environment and security context.

The AbstractWebContainer Contract

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 that need to be deployed. We'll look at some of the key methods of the AbstractWebContainer next.

Here's the accepts method:

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

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

The following section is 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) by using the WAR UnifiedClassLoader as its parent. This warLoader is used to ensure that a unique JNDI enterprise naming context (ENC) for the WAR will be created. 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 war-Loader 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 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.

The following is the signature for the abstract performDeploy method:

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

The performDeploy method is called by the start method and must be overridden by subclasses to perform the web containerspecific deployment steps. 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 standalone 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's performDeploy method implementation needs to be arranged so that it can call parseWebAppDescriptors before starting any servlets that need to access JNDI for JBoss resources such as 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 containerspecific 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 containerspecific 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.

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:

public void setConfig(Element config)
{
}

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 parseWebAppDescriptors method is invoked from within the subclass performDeploy method when it invokes the webAppParser.parseWebAppDescriptors callback to set up 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:

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 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 it 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 addEnvEntries method creates the java:comp/env web application env-entry bindings specified in the web.xml descriptor:

protected void addEnvEntries(Iterator envEntries, Context envCtx)
    throws ClassNotFoundException, 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 linkResourceEnvRefs(Iterator resourceEnvRefs, 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 linkResourceRefs(Iterator resourceRefs, Context envCtx)
    throws NamingException
{
}

The linkEjbRefs method maps the java:comp/env/ejb web application JNDI ENC ejbref 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 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 linkEjbLocalRefs(Iterator ejbRefs, Context envCtx,
                                DeploymentInfo di)
    throws NamingException
{
}

The linkSecurityDomain method creates a java: comp/env/security context that contains a securityMgr binding that points to the AuthenticationManager implementation and a realmMapping binding that points to the RealmMapping implementation that is associated with the security domain for the web application:

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

The linkSecurityDomain method 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, or they are subcontexts of this name. If there is no security-domain element, the bindings are to an org.jboss.security.plugins.NullSecurityManager instance that simply allows all authentication and authorization checks.

The getCompileClasspath method is a utility method that is 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:

public String[] getCompileClasspath(ClassLoader loader)
{
}

This is needed by some JSP compiler implementations (Jasper, for one) that expect to be given a complete classpath for compilation.

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 additional integration points described in the following sections should be considered as well.

Using the Thread Context Class Loader

Although this issue is noted in the performDeploy method description earlier in this chapter, 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 containerspecific 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.

Integrating Logging by 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.

Delegating 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 a 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 earlier in this chapter. 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 Listing 9.2 in quasi pseudo-code. Listing 9.3 provides the equivalent process for the authorization of a user.

Listing 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 made by the servlet
    SecurityAssociation.setPrincipal(principal);
    SecurityAssociation.setCredential(password.toCharArray());
} else {
    // Deny access...
}

Listing 9.3. A Pseudo-code Description of Authorizing 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...
}