/* * JBoss, the OpenSource WebOS * * Distributable under LGPL license. * See terms of license at gnu.org. */ package org.jboss.web.tomcat.tc5; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.Iterator; 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.commons.digester.Digester; 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.SecurityAssociationValve; import org.jboss.web.tomcat.security.CustomPrincipalValve; import org.jboss.web.tomcat.tc5.session.*; /** * The tomcat web application deployer * * @author Scott.Stark@jboss.org * @author Costin Manolache * @version $Revision: 1.1.2.22 $ */ 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"; 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; Iterator hostNames = metaData.getVirtualHosts(); 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=" + 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. */ WebCtxLoader webLoader = null; if (config.isUseJBossWebLoader()) { webLoader = new WebCtxLoader(loader); webLoader.setWarURL(url); } // 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"}); InputStream ctxConfig = null; try { ctxConfig = findConfig(url); } catch (IOException e) { log.debug("No " + CONTEXT_CONFIG_FILE + " in " + url, e); } if (ctxConfig != null) { Digester digester = new Digester(); digester.setValidating(false); digester.addRuleSet(new ContextConfigRuleSet()); try { digester.push(server.getAttribute(objectName, "managedResource")); digester.parse(ctxConfig); if (config.isAllowSelfPrivilegedWebApps() == false) { Boolean priv = (Boolean) server.getAttribute(objectName, "privileged"); if (priv.booleanValue()) { log.warn("Webapp " + ctxPath + " attempted to set privileged flag in context config."); server.setAttribute(objectName, new Attribute("privileged", Boolean.FALSE)); } } log.debug("Loaded context config from " + CONTEXT_CONFIG_FILE + " in " + url); } catch (Exception e) { log.error("Error parsing " + CONTEXT_CONFIG_FILE + " for " + ctxPath + " in " + url, e); } finally { digester.clear(); } } server.setAttribute(objectName, new Attribute("docBase", url.getFile())); server.setAttribute(objectName, new Attribute ("defaultWebXml", "conf/web.xml")); server.setAttribute(objectName, new Attribute("javaVMs", javaVMs)); server.setAttribute(objectName, new Attribute("server", serverName)); 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 { if (repository.toLowerCase().endsWith(".xml")) // JBOSSHACK (do not open an XML file as a ZIP file) { // JBOSSHACK continue; // JBOSSHACK } // JBOSSHACK 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())); 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(); manager.init(ctxPath, metaData, config.isUseJK(), config.isUseLocalCache()); server.setAttribute(objectName, new Attribute("manager", manager)); if (manager instanceof AbstractJBossManager) { // choose the snapshot manager SnapshotManager snap = null; String snapshotMode = config.getSnapshotMode(); int snapshotInterval = config.getSnapshotInterval(); if (snapshotMode.equals("instant")) { snap = new InstantSnapshotManager(manager, ctxPath); } else if (snapshotMode.equals("interval")) { snap = new IntervalSnapshotManager(manager, ctxPath, snapshotInterval); } else { log.error("Snapshot mode must be 'instant' or 'interval' - using 'instant'"); snap = new InstantSnapshotManager(manager, ctxPath); } // Adding session snapshot valve Object valve = new ClusteredSessionValve(snap); server.invoke(objectName, "addValve", new Object[]{valve}, new String[]{"org.apache.catalina.Valve"}); } else { throw new ClusteringNotSupportedException("managerClass " + managerClassName + " not known"); } log.debug("Enabled clustering support for ctxPath=" + ctxPath); } catch (ClusteringNotSupportedException e) { log.error("Failed to setup clustering, clustering disabled"); } } // 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"); } // Init the container; this will also start it server.invoke(objectName, "init", new Object[]{}, new String[]{}); /* Install a CustomPrincipalValve after the standard valves to allow any request custom principal to be seen by the web app */ SecurityAssociationValve valve = new SecurityAssociationValve(metaData, config.getSecurityManagerService()); valve.setSubjectAttributeName(config.getSubjectAttributeName()); server.invoke(objectName, "addValve", new Object[]{valve}, new String[]{"org.apache.catalina.Valve"}); /* Install a CustomPrincipalValve after the standard valves to allow any request custom principal to be seen by the web app */ CustomPrincipalValve cpvalve = new CustomPrincipalValve(); server.invoke(objectName, "addValve", new Object[]{cpvalve}, 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=" + 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[]{}); } } private InputStream findConfig(URL warURL) throws IOException { InputStream configIS = 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) configIS = new FileInputStream(webDD); } 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; ByteArrayOutputStream baos = new ByteArrayOutputStream(); while ((bytes = zipIS.read(buffer)) > 0) { baos.write(buffer, 0, bytes); } configIS = new ByteArrayInputStream(baos.toByteArray()); } zipFile.close(); } return configIS; } }