org.jboss.web.tomcat.tc5.TomcatDeployer.java Source code

Java tutorial

Introduction

Here is the source code for org.jboss.web.tomcat.tc5.TomcatDeployer.java

Source

/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2006, Red Hat Middleware LLC, and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.jboss.web.tomcat.tc5;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.security.CodeSource;
import java.security.cert.Certificate;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import javax.management.Attribute;
import javax.management.ObjectInstance;
import javax.management.ObjectName;

import org.apache.catalina.Loader;
import org.jboss.deployment.DeploymentException;
import org.jboss.deployment.DeploymentInfo;
import org.jboss.metadata.WebMetaData;
import org.jboss.web.AbstractWebContainer;
import org.jboss.web.AbstractWebDeployer;
import org.jboss.web.WebApplication;
import org.jboss.web.tomcat.security.CustomPrincipalValve;
import org.jboss.web.tomcat.security.JaccContextValve;
import org.jboss.web.tomcat.security.RunAsListener;
import org.jboss.web.tomcat.security.SecurityAssociationValve;
import org.jboss.web.tomcat.tc5.session.AbstractJBossManager;
import org.jboss.web.tomcat.tc5.session.ClusteringNotSupportedException;
import org.jboss.web.tomcat.tc5.session.JBossCacheManager;

/**
 * The tomcat web application deployer
 *
 * @author Scott.Stark@jboss.org
 * @author Costin Manolache
 * @version $Revision: 57206 $
 */
public class TomcatDeployer extends AbstractWebDeployer {
    /**
     * The name of the war level context configuration descriptor
     */
    private static final String CONTEXT_CONFIG_FILE = "WEB-INF/context.xml";

    private DeployerConfig config;
    private String[] javaVMs = { " jboss.management.local:J2EEServer=Local,j2eeType=JVM,name=localhost" };
    private String serverName = "jboss";
    private HashMap vhostToHostNames = new HashMap();

    public void init(Object containerConfig) throws Exception {
        this.config = (DeployerConfig) containerConfig;
        super.setJava2ClassLoadingCompliance(config.isJava2ClassLoadingCompliance());
        super.setUnpackWars(config.isUnpackWars());
        super.setLenientEjbLink(config.isLenientEjbLink());
        super.setDefaultSecurityDomain(config.getDefaultSecurityDomain());
    }

    /**
     * Perform the tomcat specific deployment steps.
     */
    protected void performDeploy(WebApplication appInfo, String warUrl,
            AbstractWebContainer.WebDescriptorParser webAppParser) throws Exception {
        WebMetaData metaData = appInfo.getMetaData();
        String hostName = null;
        // Get any jboss-web/virtual-hosts
        Iterator vhostNames = metaData.getVirtualHosts();
        // Map the virtual hosts onto the configured hosts
        Iterator hostNames = mapVirtualHosts(vhostNames);
        if (hostNames.hasNext()) {
            hostName = hostNames.next().toString();
        }
        performDeployInternal(hostName, appInfo, warUrl, webAppParser);
        while (hostNames.hasNext()) {
            String additionalHostName = hostNames.next().toString();
            performDeployInternal(additionalHostName, appInfo, warUrl, webAppParser);
        }
    }

    protected void performDeployInternal(String hostName, WebApplication appInfo, String warUrl,
            AbstractWebContainer.WebDescriptorParser webAppParser) throws Exception {

        WebMetaData metaData = appInfo.getMetaData();
        String ctxPath = metaData.getContextRoot();
        if (ctxPath.equals("/") || ctxPath.equals("/ROOT") || ctxPath.equals("")) {
            log.debug("deploy root context=" + ctxPath);
            ctxPath = "/";
            metaData.setContextRoot(ctxPath);
        }

        log.info("deploy, ctxPath=" + ctxPath + ", warUrl=" + shortWarUrlFromServerHome(warUrl));

        URL url = new URL(warUrl);

        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        /* If we are using the jboss class loader we need to augment its path
        to include the WEB-INF/{lib,classes} dirs or else scoped class loading
        does not see the war level overrides. The call to setWarURL adds these
        paths to the deployment UCL.
        */
        Loader webLoader = null;
        if (config.isUseJBossWebLoader()) {
            WebCtxLoader jbossLoader = new WebCtxLoader(loader);
            jbossLoader.setWarURL(url);
            webLoader = jbossLoader;
        } else {
            String[] pkgs = config.getFilteredPackages();
            WebAppLoader jbossLoader = new WebAppLoader(loader, pkgs);
            jbossLoader.setDelegate(getJava2ClassLoadingCompliance());
            webLoader = jbossLoader;
        }

        // We need to establish the JNDI ENC prior to the start 
        // of the web container so that init on startup servlets are able 
        // to interact with their ENC. We hook into the context lifecycle 
        // events to be notified of the start of the
        // context as this occurs before the servlets are started.
        if (appInfo.getAppData() == null)
            webAppParser.parseWebAppDescriptors(loader, appInfo.getMetaData());

        appInfo.setName(url.getPath());
        appInfo.setClassLoader(loader);
        appInfo.setURL(url);

        String objectNameS = config.getCatalinaDomain() + ":j2eeType=WebModule,name=//"
                + ((hostName == null) ? "localhost" : hostName) + ctxPath + ",J2EEApplication=none,J2EEServer=none";

        ObjectName objectName = new ObjectName(objectNameS);

        if (server.isRegistered(objectName)) {
            log.debug("Already exists, destroying " + objectName);
            server.invoke(objectName, "destroy", new Object[] {}, new String[] {});
        }

        server.createMBean("org.apache.commons.modeler.BaseModelMBean", objectName,
                new Object[] { config.getContextClassName() }, new String[] { "java.lang.String" });

        // Find and set config file on the context
        // If WAR is packed, expand config file to temp folder
        String ctxConfig = null;
        try {
            ctxConfig = findConfig(url);
        } catch (IOException e) {
            log.debug("No " + CONTEXT_CONFIG_FILE + " in " + url, e);
        }

        server.setAttribute(objectName, new Attribute("docBase", url.getFile()));

        server.setAttribute(objectName, new Attribute("configFile", ctxConfig));

        server.setAttribute(objectName, new Attribute("defaultContextXml", "context.xml"));
        server.setAttribute(objectName, new Attribute("defaultWebXml", "conf/web.xml"));

        server.setAttribute(objectName, new Attribute("javaVMs", javaVMs));

        server.setAttribute(objectName, new Attribute("server", serverName));

        server.setAttribute(objectName, new Attribute("saveConfig", Boolean.FALSE));

        if (webLoader != null) {
            server.setAttribute(objectName, new Attribute("loader", webLoader));
        } else {
            server.setAttribute(objectName, new Attribute("parentClassLoader", loader));
        }

        server.setAttribute(objectName, new Attribute("delegate", new Boolean(getJava2ClassLoadingCompliance())));

        String[] jspCP = getCompileClasspath(loader);
        StringBuffer classpath = new StringBuffer();
        for (int u = 0; u < jspCP.length; u++) {
            String repository = jspCP[u];
            if (repository == null)
                continue;
            if (repository.startsWith("file://"))
                repository = repository.substring(7);
            else if (repository.startsWith("file:"))
                repository = repository.substring(5);
            else
                continue;
            if (repository == null)
                continue;
            // ok it is a file.  Make sure that is is a directory or jar file
            File fp = new File(repository);
            if (!fp.isDirectory()) {
                // if it is not a directory, try to open it as a zipfile.
                try {
                    // avoid opening .xml files
                    if (fp.getName().toLowerCase().endsWith(".xml"))
                        continue;

                    ZipFile zip = new ZipFile(fp);
                    zip.close();
                } catch (IOException e) {
                    continue;
                }

            }
            if (u > 0)
                classpath.append(File.pathSeparator);
            classpath.append(repository);
        }

        server.setAttribute(objectName, new Attribute("compilerClasspath", classpath.toString()));

        // Set the session cookies flag according to metadata
        switch (metaData.getSessionCookies()) {
        case WebMetaData.SESSION_COOKIES_ENABLED:
            server.setAttribute(objectName, new Attribute("cookies", new Boolean(true)));
            log.debug("Enabling session cookies");
            break;
        case WebMetaData.SESSION_COOKIES_DISABLED:
            server.setAttribute(objectName, new Attribute("cookies", new Boolean(false)));
            log.debug("Disabling session cookies");
            break;
        default:
            log.debug("Using session cookies default setting");
        }

        // Add a valve to estalish the JACC context before authorization valves
        Certificate[] certs = null;
        CodeSource cs = new CodeSource(url, certs);
        JaccContextValve jaccValve = new JaccContextValve(metaData.getJaccContextID(), cs);
        server.invoke(objectName, "addValve", new Object[] { jaccValve },
                new String[] { "org.apache.catalina.Valve" });

        // Pass the metadata to the RunAsListener via a thread local
        RunAsListener.metaDataLocal.set(metaData);

        try {
            // Init the container; this will also start it
            server.invoke(objectName, "init", new Object[] {}, new String[] {});
        } finally {
            RunAsListener.metaDataLocal.set(null);
        }

        // make the context class loader known to the WebMetaData, ws4ee needs it
        // to instanciate service endpoint pojos that live in this webapp
        Loader ctxLoader = (Loader) server.getAttribute(objectName, "loader");
        metaData.setContextLoader(ctxLoader.getClassLoader());

        // Clustering
        if (metaData.getDistributable()) {
            // Try to initate clustering, fallback to standard if no clustering is available
            try {
                AbstractJBossManager manager = null;
                String managerClassName = config.getManagerClass();
                Class managerClass = Thread.currentThread().getContextClassLoader().loadClass(managerClassName);
                manager = (AbstractJBossManager) managerClass.newInstance();

                if (manager instanceof JBossCacheManager) {
                    // TODO either deprecate snapshot mode or move its config
                    // into jboss-web.xml.
                    String snapshotMode = config.getSnapshotMode();
                    int snapshotInterval = config.getSnapshotInterval();
                    JBossCacheManager jbcm = (JBossCacheManager) manager;
                    jbcm.setSnapshotMode(snapshotMode);
                    jbcm.setSnapshotInterval(snapshotInterval);
                }

                String name = "//" + ((hostName == null) ? "localhost" : hostName) + ctxPath;
                manager.init(name, metaData, config.isUseJK(), config.isUseLocalCache());

                // Don't assign the manager to the context until all config
                // is done, or else the manager will be started without the config
                server.setAttribute(objectName, new Attribute("manager", manager));

                log.debug("Enabled clustering support for ctxPath=" + ctxPath);
            } catch (ClusteringNotSupportedException e) {
                // JBAS-3513 Just log a WARN, not an ERROR
                log.warn("Failed to setup clustering, clustering disabled. ClusteringNotSupportedException: "
                        + e.getMessage());
            } catch (NoClassDefFoundError ncdf) {
                // JBAS-3513 Just log a WARN, not an ERROR
                log.debug("Classes needed for clustered webapp unavailable", ncdf);
                log.warn("Failed to setup clustering, clustering disabled. NoClassDefFoundError: "
                        + ncdf.getMessage());
            } catch (Throwable t) {
                // TODO consider letting this through and fail the deployment
                log.error("Failed to setup clustering, clustering disabled. Exception: ", t);
            }
        }

        /* Add security association valve after the authorization
        valves so that the authenticated user may be associated with the
        request thread/session.
        */
        SecurityAssociationValve valve = new SecurityAssociationValve(metaData, config.getSecurityManagerService());
        valve.setSubjectAttributeName(config.getSubjectAttributeName());
        server.invoke(objectName, "addValve", new Object[] { valve }, new String[] { "org.apache.catalina.Valve" });

        // Retrieve the state, and throw an exception in case of a failure
        Integer state = (Integer) server.getAttribute(objectName, "state");
        if (state.intValue() != 1) {
            throw new DeploymentException("URL " + warUrl + " deployment failed");
        }

        appInfo.setAppData(objectName);

        // Create mbeans for the servlets
        DeploymentInfo di = webAppParser.getDeploymentInfo();
        di.deployedObject = objectName;
        ObjectName servletQuery = new ObjectName(config.getCatalinaDomain() + ":j2eeType=Servlet,WebModule="
                + objectName.getKeyProperty("name") + ",*");
        Iterator iterator = server.queryMBeans(servletQuery, null).iterator();
        while (iterator.hasNext()) {
            di.mbeans.add(((ObjectInstance) iterator.next()).getObjectName());
        }

        log.debug("Initialized: " + appInfo + " " + objectName);

    }

    /**
     * Called as part of the undeploy() method template to ask the
     * subclass for perform the web container specific undeployment steps.
     */
    protected void performUndeploy(String warUrl, WebApplication appInfo) throws Exception {
        if (appInfo == null) {
            log.debug("performUndeploy, no WebApplication found for URL " + warUrl);
            return;
        }

        log.info("undeploy, ctxPath=" + appInfo.getMetaData().getContextRoot() + ", warUrl="
                + shortWarUrlFromServerHome(warUrl));

        WebMetaData metaData = appInfo.getMetaData();
        String hostName = null;
        Iterator hostNames = metaData.getVirtualHosts();
        if (hostNames.hasNext()) {
            hostName = hostNames.next().toString();
        }
        performUndeployInternal(hostName, warUrl, appInfo);
        while (hostNames.hasNext()) {
            String additionalHostName = hostNames.next().toString();
            performUndeployInternal(additionalHostName, warUrl, appInfo);
        }

    }

    protected void performUndeployInternal(String hostName, String warUrl, WebApplication appInfo)
            throws Exception {

        WebMetaData metaData = appInfo.getMetaData();
        String ctxPath = metaData.getContextRoot();

        // If the server is gone, all apps were stopped already
        if (server == null)
            return;

        ObjectName objectName = new ObjectName(config.getCatalinaDomain() + ":j2eeType=WebModule,name=//"
                + ((hostName == null) ? "localhost" : hostName) + ctxPath
                + ",J2EEApplication=none,J2EEServer=none");

        if (server.isRegistered(objectName)) {
            // Contexts should be stopped by the host already
            server.invoke(objectName, "destroy", new Object[] {}, new String[] {});
        }

    }

    /**
     * Resolve the input virtual host names to the names of the configured Hosts
     * @param vhostNames Iterator<String> for the jboss-web/virtual-host elements 
     * @return Iterator<String> of the unique Host names
     * @throws Exception
     */
    protected synchronized Iterator mapVirtualHosts(Iterator vhostNames) throws Exception {
        if (vhostToHostNames.size() == 0) {
            // Query the configured Host mbeans
            String hostQuery = config.getCatalinaDomain() + ":type=Host,*";
            ObjectName query = new ObjectName(hostQuery);
            Set hosts = server.queryNames(query, null);
            Iterator iter = hosts.iterator();
            while (iter.hasNext()) {
                ObjectName host = (ObjectName) iter.next();
                String name = host.getKeyProperty("host");
                if (name != null) {
                    vhostToHostNames.put(name, name);
                    String[] aliases = (String[]) server.invoke(host, "findAliases", null, null);
                    int count = aliases != null ? aliases.length : 0;
                    for (int n = 0; n < count; n++) {
                        vhostToHostNames.put(aliases[n], name);
                    }
                }
            }
        }

        // Map the virtual host names to the hosts
        HashSet hosts = new HashSet();
        while (vhostNames.hasNext()) {
            String vhost = (String) vhostNames.next();
            String host = (String) vhostToHostNames.get(vhost);
            if (host == null) {
                log.warn("Failed to map vhost: " + vhost);
                // This will cause a new host to be created
                host = vhost;
            }
            hosts.add(host);
        }
        return hosts.iterator();
    }

    private String findConfig(URL warURL) throws IOException {
        String result = null;
        // See if the warUrl is a dir or a file
        File warFile = new File(warURL.getFile());
        if (warURL.getProtocol().equals("file") && warFile.isDirectory() == true) {
            File webDD = new File(warFile, CONTEXT_CONFIG_FILE);
            if (webDD.exists() == true)
                result = webDD.getAbsolutePath();
        } else {
            ZipFile zipFile = new ZipFile(warFile);
            ZipEntry entry = zipFile.getEntry(CONTEXT_CONFIG_FILE);
            if (entry != null) {
                InputStream zipIS = zipFile.getInputStream(entry);
                byte[] buffer = new byte[512];
                int bytes;
                result = warFile.getAbsolutePath() + "-context.xml";
                FileOutputStream fos = new FileOutputStream(result);
                while ((bytes = zipIS.read(buffer)) > 0) {
                    fos.write(buffer, 0, bytes);
                }
                zipIS.close();
                fos.close();
            }
            zipFile.close();
        }
        return result;
    }

    private String shortWarUrlFromServerHome(String warUrl) {
        String serverHomeUrl = System.getProperty(org.jboss.system.server.ServerConfig.SERVER_HOME_URL);

        if (warUrl == null || serverHomeUrl == null)
            return warUrl;

        if (warUrl.startsWith(serverHomeUrl))
            return ".../" + warUrl.substring(serverHomeUrl.length());
        else
            return warUrl;
    }

}