/* * JBoss, Home of Professional Open Source * Copyright 2008, Red Hat Middleware LLC, and individual contributors * by the @authors tag. See the copyright.txt 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.service.deployers; 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.Iterator; import java.util.zip.ZipFile; import javax.management.Attribute; import javax.management.ObjectName; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; import javax.servlet.sip.SipSessionsUtil; import javax.servlet.sip.TimerService; import org.apache.catalina.LifecycleEvent; import org.apache.catalina.Loader; import org.apache.catalina.core.StandardContext; import org.apache.tomcat.util.modeler.Registry; import org.jboss.deployers.spi.DeploymentException; import org.jboss.deployers.structure.spi.DeploymentUnit; import org.jboss.ejb3.EJBContainer; import org.jboss.ejb3.Ejb3Deployment; import org.jboss.logging.Logger; import org.jboss.metadata.sip.jboss.JBossConvergedSipMetaData; import org.jboss.metadata.web.jboss.JBossWebMetaData; import org.jboss.naming.NonSerializableFactory; import org.jboss.security.SecurityUtil; import org.jboss.virtual.VirtualFile; import org.jboss.web.WebApplication; 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.security.SecurityContextEstablishmentValve; import org.jboss.web.tomcat.service.TomcatConvergedSipInjectionContainer; import org.jboss.web.tomcat.service.TomcatInjectionContainer; import org.jboss.web.tomcat.service.session.AbstractJBossManager; import org.jboss.web.tomcat.service.session.distributedcache.spi.ClusteringNotSupportedException; import org.mobicents.servlet.sip.message.SipFactoryFacade; import org.mobicents.servlet.sip.startup.SipContext; import org.mobicents.servlet.sip.startup.jboss.SipJBossContextConfig; /** * A tomcat converged sip application deployment that will be able to deploy web applications, sip applications and converged sip/web applications. * * It extends the TomcatDeployment JBoss 5 class so that the config class for the context becomes org.mobicents.servlet.sip.startup.jboss.SipJBossContextConfig * and that a ConvergedEncListener is set to the context to inject SipFactory, TimerService and SipSessionUtils into the private jndi of the context. * * @author jean.deruelle@gmail.com * */ public class TomcatConvergedDeployment extends TomcatDeployment { private static final Logger log = Logger .getLogger(TomcatConvergedDeployment.class); /** * The name of the war level context configuration descriptor */ private static final String CONTEXT_CONFIG_FILE = "WEB-INF/context.xml"; public static final String SIP_SUBCONTEXT = "sip"; public static final String SIP_FACTORY_JNDI_NAME = "SipFactory"; public static final String SIP_SESSIONS_UTIL_JNDI_NAME = "SipSessionsUtil"; public static final String TIMER_SERVICE_JNDI_NAME = "TimerService"; protected DeployerConfig config; private final String[] javaVMs = { " jboss.management.local:J2EEServer=Local,j2eeType=JVM,name=localhost" }; private final String serverName = "jboss"; @Override public void init(Object containerConfig) throws Exception { super.init(containerConfig); this.config = (DeployerConfig) containerConfig; } @Override protected void performDeployInternal( WebApplication webApp, String hostName, String warUrlStr) throws Exception { JBossWebMetaData metaData = webApp.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); URL warUrl = new URL(warUrlStr); ClassLoader loader = Thread.currentThread().getContextClassLoader(); metaData.setContextLoader(loader); StandardContext context = (StandardContext) Class.forName( config.getContextClassName()).newInstance(); DeploymentUnit depUnit = webApp.getDeploymentUnit(); TomcatConvergedSipInjectionContainer injectionContainer = new TomcatConvergedSipInjectionContainer( webApp, depUnit, context, getPersistenceUnitDependencyResolver()); Loader webLoader = depUnit.getAttachment( Loader.class); if (webLoader == null) webLoader = getWebLoader(depUnit, metaData, loader, warUrl, injectionContainer); webApp.setName(warUrl.getPath()); webApp.setClassLoader(loader); webApp.setURL(warUrl); String objectNameS = config.getCatalinaDomain() + ":j2eeType=WebModule,name=//" + ((hostName == null) ? "localhost" : hostName) + ctxPath + ",J2EEApplication=none,J2EEServer=none"; ObjectName objectName = new ObjectName(objectNameS); if (Registry.getRegistry(null, null).getMBeanServer().isRegistered( objectName)) throw new DeploymentException( "Web mapping already exists for deployment URL " + warUrlStr); Registry.getRegistry(null, null).registerComponent(context, objectName, config.getContextClassName()); context.setConfigFile(CONTEXT_CONFIG_FILE); context.setInstanceManager(injectionContainer); context.setDefaultContextXml("context.xml"); context.setDefaultWebXml("conf/web.xml"); context.setPublicId(metaData.getPublicID()); String docBase = depUnit.getAttachment("org.jboss.web.explicitDocBase", String.class); if (docBase == null) docBase = warUrl.getFile(); context.setDocBase(docBase); // If there is an alt-dd set it if (metaData.getAlternativeDD() != null) { log.debug("Setting altDDName to: " + metaData.getAlternativeDD()); context.setAltDDName(metaData.getAlternativeDD()); } context.setJavaVMs(javaVMs); context.setServer(serverName); context.setSaveConfig(false); if (webLoader != null) { context.setLoader(webLoader); } else { context.setParentClassLoader(loader); } context.setDelegate(webApp.getJava2ClassLoadingCompliance()); // Javac compatibility whenever possible 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); } context.setCompilerClasspath(classpath.toString()); // Set the session cookies flag according to metadata switch (metaData.getSessionCookies()) { case JBossWebMetaData.SESSION_COOKIES_ENABLED: context.setCookies(true); log.debug("Enabling session cookies"); break; case JBossWebMetaData.SESSION_COOKIES_DISABLED: context.setCookies(false); log.debug("Disabling session cookies"); break; default: log.debug("Using session cookies default setting"); } String metaDataSecurityDomain = metaData.getSecurityDomain(); if (metaDataSecurityDomain != null) metaDataSecurityDomain = metaDataSecurityDomain.trim(); // TODO : add the security valve again with SecurityActions enabled. It was commented due to IllegalAccessError, it is a regression from regular JBoss 5 // Add a valve to establish security context // SecurityContextEstablishmentValve scevalve = new SecurityContextEstablishmentValve( // metaDataSecurityDomain, SecurityUtil // .unprefixSecurityDomain(config // .getDefaultSecurityDomain()), SecurityActions // .loadClass(config.getSecurityContextClassName()), // getSecurityManagement()); SecurityContextEstablishmentValve scevalve = new SecurityContextEstablishmentValve( metaDataSecurityDomain, SecurityUtil .unprefixSecurityDomain(config .getDefaultSecurityDomain()), Class.forName(config.getSecurityContextClassName()), getSecurityManagement()); context.addValve(scevalve); // Add a valve to estalish the JACC context before authorization valves Certificate[] certs = null; CodeSource cs = new CodeSource(warUrl, certs); JaccContextValve jaccValve = new JaccContextValve(metaData, cs); context.addValve(jaccValve); // Set listener context.setConfigClass("org.mobicents.servlet.sip.startup.jboss.SipJBossContextConfig"); // context.setConfigClass("org.jboss.web.tomcat.service.deployers.JBossContextConfig"); context.addLifecycleListener(new ConvergedEncListener(hostName, loader, webLoader, injectionContainer, webApp)); // Pass the metadata to the RunAsListener via a thread local RunAsListener.metaDataLocal.set(metaData); SipJBossContextConfig.metaDataLocal.set(metaData); SipJBossContextConfig.metaDataShared.set(config.getSharedMetaData()); SipJBossContextConfig.deployerConfig.set(config); SipJBossContextConfig.kernelLocal.set(kernel); SipJBossContextConfig.deploymentUnitLocal.set(unit); try { // Start it context.start(); // Build the ENC injectSipUtilitiesIntoEJBs(context, metaData); } catch (Exception e) { context.destroy(); DeploymentException.rethrowAsDeploymentException("URL " + warUrlStr + " deployment failed", e); } finally { RunAsListener.metaDataLocal.set(null); SipJBossContextConfig.metaDataLocal.set(null); SipJBossContextConfig.metaDataShared.set(null); SipJBossContextConfig.deployerConfig.set(null); SipJBossContextConfig.kernelLocal.set(null); SipJBossContextConfig.deploymentUnitLocal.set(null); } if (context.getState() != 1) { context.destroy(); throw new DeploymentException("URL " + warUrlStr + " deployment failed"); } // Clustering if (metaData.getDistributable() != null) { // 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(); String name = "//" + ((hostName == null) ? "localhost" : hostName) + ctxPath; manager.init(name, metaData); 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" }); /* * TODO: 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"); } */ webApp.setAppData(objectName); /* * TODO: Create mbeans for the servlets 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: " + webApp + " " + objectName); } /** * If we are deploying a sip servlet application, * check the deployment unit in which we are to check if there is any EJB JAR present. * For each EJB Container present in the EJB-JAR, get the private JNDI context (comp/env), * create the sip servlets JNDI Tree (sip/<appname>/SipFactory & sip/'appname'/TimerService & sip/'appname'/SipSessionsUtil) * and inject the SIP utilities (TimerService, SipFactory and SipSessionsUtil). * @param context the sip context from which the Sip Utilities will be fecthed * @param metaData the meta Data from which we get the application Name to create the JNDI tree */ private void injectSipUtilitiesIntoEJBs(StandardContext context, JBossWebMetaData metaData) { if(context instanceof SipContext && metaData instanceof JBossConvergedSipMetaData) { JBossConvergedSipMetaData convergedMetaData = (JBossConvergedSipMetaData) metaData; SipContext sipContext = (SipContext) context; DeploymentUnit parent = unit.getTopLevel(); Iterator<DeploymentUnit> it = parent.getChildren() .iterator(); while (it.hasNext()) { DeploymentUnit deploymentUnit = (DeploymentUnit) it.next(); Ejb3Deployment ejbDeployment = (Ejb3Deployment) deploymentUnit .getAttachment(Ejb3Deployment.class); if(ejbDeployment != null) { if(log.isInfoEnabled()) { log.info("Ejb Jar in which to inject SipUtilities " + deploymentUnit.getName()); } for(Object container : ejbDeployment.getEjbContainers().values()) { EJBContainer ejbContainer = (EJBContainer) container; if(log.isDebugEnabled()) { log.debug("Ejb Container in which to inject SipUtilities " + ejbContainer); } try { Context envCtx = (Context)ejbContainer.getEnc().lookup("env"); Context sipSubcontext = envCtx.createSubcontext(SIP_SUBCONTEXT); Context applicationNameSubcontext = sipSubcontext.createSubcontext(convergedMetaData.getApplicationName()); SipFactoryFacade sipFactoryFacade = (SipFactoryFacade) sipContext.getSipFactoryFacade(); TimerService timerService = (TimerService) sipContext.getTimerService(); SipSessionsUtil sipSessionsUtil = (SipSessionsUtil) sipContext.getSipSessionsUtil(); NonSerializableFactory.rebind( applicationNameSubcontext, SIP_FACTORY_JNDI_NAME, sipFactoryFacade); NonSerializableFactory .rebind( applicationNameSubcontext, SIP_SESSIONS_UTIL_JNDI_NAME, sipSessionsUtil); NonSerializableFactory .rebind( applicationNameSubcontext, TIMER_SERVICE_JNDI_NAME, timerService); if (log.isDebugEnabled()) { log .debug("Sip Objects made available to global JNDI under following conetxt : java:comp/env/sip/" + convergedMetaData.getApplicationName() + "/<ObjectName>"); } } catch (NamingException e) { log.error("Unexpected exception while trying to inject Sip Utilities into following EJB Container : " + ejbContainer, e); } } } } } } public class ConvergedEncListener extends EncListener { protected String hostName; public ConvergedEncListener(String hostName, ClassLoader loader, Loader webLoader,TomcatInjectionContainer tomcatInjectionContainer, WebApplication webApp) { super(loader, webLoader, tomcatInjectionContainer, webApp); this.hostName = hostName; } public void lifecycleEvent(LifecycleEvent event) { super.lifecycleEvent(event); if (event.getType().equals(StandardContext.AFTER_START_EVENT)) { JBossConvergedSipMetaData convergedMetaData = (JBossConvergedSipMetaData) metaData ; Thread currentThread = Thread.currentThread(); ClassLoader currentLoader = currentThread.getContextClassLoader(); currentThread.setContextClassLoader(webLoader.getClassLoader()); try { InitialContext iniCtx = new InitialContext(); Context envCtx = (Context) iniCtx.lookup("java:comp/env"); Context sipSubcontext = envCtx.createSubcontext(SIP_SUBCONTEXT); Context applicationNameSubcontext = sipSubcontext.createSubcontext(convergedMetaData.getApplicationName()); if(event.getSource() instanceof SipContext) { SipContext sipContext = (SipContext) event.getSource(); SipFactoryFacade sipFactoryFacade = (SipFactoryFacade) sipContext.getSipFactoryFacade(); TimerService timerService = (TimerService) sipContext.getTimerService(); SipSessionsUtil sipSessionsUtil = (SipSessionsUtil) sipContext.getSipSessionsUtil(); NonSerializableFactory.rebind( applicationNameSubcontext, SIP_FACTORY_JNDI_NAME, sipFactoryFacade); NonSerializableFactory .rebind( applicationNameSubcontext, SIP_SESSIONS_UTIL_JNDI_NAME, sipSessionsUtil); NonSerializableFactory .rebind( applicationNameSubcontext, TIMER_SERVICE_JNDI_NAME, timerService); if (log.isDebugEnabled()) { log .debug("Sip Objects made available to global JNDI under following conetxt : java:comp/env/sip/" + convergedMetaData.getApplicationName() + "/<ObjectName>"); } } } catch (Throwable t) { log.error("ENC setup failed", t); throw new RuntimeException(t); } finally { currentThread.setContextClassLoader(currentLoader); } } } } }