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



JBoss 4. 0(c) The Official Guide
JBoss 4.0 - The Official Guide
ISBN: B003D7JU58
EAN: N/A
Year: 2006
Pages: 137

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net