How to Integrate a Web Container into JBoss

Author:Scott Stark <[email protected]>

Introduction

This HowTo 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. Example servlet containers include Tomcat and Jetty. Integrating a servlet container into JBoss consists of mapping web-app.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. These tasks are simplified by the org.jboss.web.AbstractWebContainer class. The remainder of this HowTo describes howto integrate a web container using the AbstractWebContainer class.

AbstractWebContainer Overview

The org.jboss.web.AbstractWebContainer class is an implementation of a template pattern for web container integration into JBoss. This class should be subclassed by web container providers wishing to integrate their container into a JBoss server. AbstractWebContainer provides support for parsing the standard J2EE web-app.xml web application deployment descriptor JNDI and security elements as well as support for parsing the JBoss specific jboss-web.xml descriptor. The AbstractWebContainer is an abstract class that implements the AbstractWebContainerMBean JMX mbean interface used by the JBoss deployer when war files need to be deployed. Figure 11.1 presents some of the key AbstractWebContainer methods.

Figure 11.1. Key AbstractWebContainer Class Methods

package org.jboss.web;
...

public abstract class AbstractWebContainer extends ServiceMBeanSupport implements AbstractWebContainerMBean
{
1
    public synchronized void deploy(String ctxPath, String warUrl) throws DeploymentException
    {
            WebApplication warInfo = performDeploy(ctxPath, warUrl);
            ClassLoader loader = warInfo.getClassLoader();
            Element webApp = warInfo.getWebApp();
            Element jbossWeb = warInfo.getJbossWeb();
            parseWebAppDescriptors(loader, webApp, jbossWeb);
            deploymentMap.put(warUrl, warInfo);
    }
2
    protected abstract WebApplication performDeploy(String ctxPath, String warUrl) throws Exception;

3
    public synchronized void undeploy(String warUrl) throws DeploymentException
    {
            performUndeploy(warUrl);
            // Remove the web application ENC...
            deploymentMap.remove(warUrl);
    }
4
    protected abstract void performUndeploy(String warUrl) throws Exception;

    public boolean isDeployed(String warUrl)
    {
        return deploymentMap.containsKey(warUrl);
    }

    public WebApplication getDeployedApp(String warUrl)
    {
        WebApplication appInfo = (WebApplication) deploymentMap.get(warUrl);
        return appInfo;
    }

5
    private void parseWebAppDescriptors(ClassLoader loader, Element webApp, Element jbossWeb) throws Exception
    {
        WebMetaData metaData = new WebMetaData();
        metaData.importXml(webApp);
        if( jbossWeb != null )
            metaData.importXml(jbossWeb);
        ...
        addEnvEntries(envEntries, envCtx);
        Iterator resourceRefs = metaData.getResourceReferences();
        linkResourceRefs(resourceRefs, envCtx);
        Iterator ejbRefs = metaData.getEjbReferences();
        linkEjbRefs(ejbRefs, envCtx);
        String securityDomain = metaData.getSecurityDomain();
        linkSecurityDomain(securityDomain, envCtx);
    }

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

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

8
    protected void linkEjbRefs(Iterator ejbRefs, Context envCtx)
        throws NamingException
    {
        ...
    }

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

}

public class WebApplication
{
    /** Class loader of this application */
    ClassLoader classLoader = null;
    /** name of this application */
    String name = "";
    /** URL where this application was deployed from */
    URL url;
    /** The root element of thw web-app.xml descriptor. */
    Element webApp;
    /** The root element of thw jboss-web.xml descriptor. */
    Element jbossWeb;
    /** Arbitary data object for storing application specific data */
    Object data;
...
	// WebApplication property getters/setters...
}
1

A template pattern implementation of the deploy() method. This method calls the performDeploy() method to perform the container specific deployment steps and registers the returned WebApplication in the deployment map. The steps performed are: WebApplication warInfo = performDeploy(ctxPath, warUrl); ClassLoader loader = warInfo.getClassLoader(); Element webApp = warInfo.getWebApp(); Element jbossWeb = warInfo.getJbossWeb(); parseWebAppDescriptors(loader, webApp, jbossWeb); deploymentMap.put(warUrl, warInfo); @param ctxPath, The context-root element value from the J2EE application/module/web application.xml descriptor. This may be null if war was is not being deployed as part of an enterprise application. @param warUrl, The string for the URL of the web application war.

2

The method is called by the deploy() method and must be overriden by subclasses to perform the web container specific deployment steps. A WebApplication object must be returned that contains the web application class loader, web-app.xml web-app document element and the jboss-web.xml jboss-web document element if a jboss-web.xml existed in the war. @param ctxPath, The context-root element value from the J2EE application/module/web application.xml descriptor. This may be null if war was is not being deployed as part of an enterprise application. @param warUrl, The string for the URL of the web application war. @return WebApplication, the web application information required by the AbstractWebContainer class to setup the JNDI ENC and track the war deployment status.

3

A template pattern implementation of the undeploy() method. This method calls the subclass performUndeploy() method to perform the container specific undeployment steps and unregisters the the warUrl from the deployment map.

4

performUndeploy

5

This method is called as part of the deploy() method template to parse the web-app.xml and jboss-web.xml deployment descriptors from a war deployment. The method creates the ENC(java:comp/env) env-entry, resource-ref, and ejb-ref element values. The creation of the env-entry values does not require a jboss-web.xml descriptor. The creation of the 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 class loader is used because each war typically requires a unique class loader to isolate the web application classes/resources. This means that the ClassLoader passed to this method must be the thread context ClassLoader seen by the server/jsp pages during init/destroy/service/etc. method invocations if these methods interace with the JNDI ENC context. @param loader, the ClassLoader for the web application. May not be null. @param webApp, the root element of thw web-app.xml descriptor. May not be null. @param jbossWeb, the root element of thw jboss-web.xml descriptor. May be null to indicate that no jboss-web.xml descriptor exists.

6

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

7

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

8

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

9

This creates a java:comp/env/security context that contains a securityMgr binding pointing to an EJBSecurityMgr implementation and a realmMapping binding pointing to a RealmMapping implementation. If the jboss-web.xml descriptor contained a security-domain element then the bindings are LinkRefs to the jndi name specified by the security-domain element. If there was no security-domain element then the bindings are to NullSecurityManager instance which simply allows all access.

Integrating a Web Container by Subclassing AbstractWebContainer

To integrate your web container into JBoss create a subclass of AbstractWebContainer and implement the required performDeploy(String, String) and performUndeploy(String) methods. See the performDeploy and performUndeploy method notes in the preceeding section for details of the method implementations.

Delegating Web Container Authentication and Authorization to JBoss

Ideally both web application and ejb authentication and authorization is handled by the same security code. 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 calls. Integration with the JBossSX security framework is based on the establishment of a java:comp/env/security context as described in the linkSecurityDomain(String, Context) method comments in the overview section. The security context provides access to the JBossSX security mgr interface implementations for use by subclass request interceptors. A outline of the steps for authenticating a user is:

Example 11.1. Authentication Steps

    // Get the username and password from the request context...
    String username = f(request);
    String password = f(request);
    // Get the JBoss security manager from the ENC context
    InitialContext iniCtx = new InitialContext();
    EJBSecurityManager securityMgr = (EJBSecurityManager) 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...
    }
An outline of the steps for authorizing a user with the JBossSX api is:

Example 11.2. Authorization Steps

    // Get the username and required roles from the request context...
    String username = f(request);
    String[] roles = f(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(Arrays.asList(roles));
    if( securityMgr.doesUserHaveRole(principal, requiredRoles) )
    {
        // Indicate the user has the required roles for the web content...
    }
    else
    {
        // Deny access...
    }