/*
* 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.mobicents.servlet.sip.startup.jboss;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Iterator;
import java.util.Properties;
import java.util.Set;
import javax.management.Attribute;
import javax.management.JMException;
import javax.management.MBeanServer;
import javax.management.Notification;
import javax.management.NotificationListener;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import javax.security.jacc.PolicyContext;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.connector.Connector;
import org.apache.tomcat.util.modeler.Registry;
import org.jboss.deployment.DeploymentInfo;
import org.jboss.deployment.SubDeployerExt;
import org.jboss.metadata.WebMetaData;
import org.jboss.mx.util.MBeanProxyExt;
import org.jboss.security.plugins.JaasSecurityManagerServiceMBean;
import org.jboss.system.ServiceControllerMBean;
import org.jboss.system.server.Server;
import org.jboss.system.server.ServerImplMBean;
import org.jboss.web.AbstractWebDeployer;
import org.jboss.web.tomcat.security.HttpServletRequestPolicyContextHandler;
import org.jboss.web.tomcat.service.DeployerConfig;
import org.jboss.web.tomcat.service.JBossWebMBean;
import org.jboss.web.tomcat.service.session.SessionIDGenerator;
import org.mobicents.servlet.sip.annotations.SipApplicationAnnotationUtils;
import org.mobicents.servlet.sip.startup.SipContext;
import org.mobicents.servlet.sip.startup.SipHostConfig;
/**
* An implementation of the AbstractConvergedContainer for the Jakarta Tomcat5
* servlet container. It has no code dependency on tomcat - only the new JMX
* model is used.
* <p/>
* Tomcat5 is organized as a set of mbeans - just like jboss.
*
* @author Scott.Stark@jboss.org
* @author Costin Manolache
* @author Wonne.Keysers@realsoftware.be
* @author Dimitris.Andreadis@jboss.org
* @author Anil.Saldhana@jboss.org
* @version $Revision: 57206 $
* @see org.jboss.web.AbstractWebContainer
*/
public class JBossSip extends AbstractConvergedContainer
implements JBossWebMBean, NotificationListener
{
// Constants -----------------------------------------------------
public static final String NAME = "JBossSip";
/**
* Default value for property <code>cacheName</code>. This name will be used by JBossCache exclusively
* for Tomcat clustering, e.g., session and sso replication.
*/
public static final String DEFAULT_CACHE_NAME =
"jboss.cache:service=TomcatClusteringCache";
// XXX We could make this configurable - so it can support other containers
// that provide JMX-based deployment.
private String contextClassName =
"org.apache.catalina.core.StandardContext";
/**
* Configurable map of tomcat authenticators
* Keyed in by the http auth method that gets
* plugged into the Context Config and then into the StandardContext
*/
private Properties authenticators = null;
/**
* Domain for tomcat5 mbeans
*/
private String catalinaDomain = "Catalina";
/**
* ObjectName of a shared TreeCache used for clustered session replication
* and clustered single-sign-on
*/
private String cacheName = DEFAULT_CACHE_NAME;
/**
* The fully qualified name of the class that will be used for session
* management if <tt>distributable</tt> is set to true.
*/
protected String managerClass = "org.jboss.web.tomcat.service.session.JBossCacheManager";
/**
* With IntervalSnapshotManager use this interval (in ms)
* for snapshotting
*/
private int snapshotInterval = 1000;
/**
* Which snapshot mode should be used in clustered environment?
* Default: instant
*/
private String snapshotMode = "instant"; // instant or interval
/**
* Should the clustering code use a local cache for the sessions?
*/
private boolean useLocalCache = true;
/**
* Maximum interval a clustered session should allow request to not
* replicate the session timestamp.
*/
private int maxUnreplicatedInterval = WebMetaData.DEFAULT_MAX_UNREPLICATED_INTERVAL;
/**
* Whether we are using Apache MOD_JK(2) module or not
*/
private boolean useJK = false;
/**
* A flag indicating if the JBoss Loader should be used
*/
private boolean useJBossWebLoader = true;
/**
* JBAS-3358: Work directory shouldn't be deleted on Context Destroy
*/
private boolean deleteWorkDirOnContextDestroy = false;
/**
* JBAS-2283: Provide custom header based auth support
*/
private String httpHeaderForSSOAuth = null;
private String sessionCookieForSSOAuth = null;
/**
* The server xml configuration file name
*/
private String serverConfigFile = "server.xml";
/**
* Get the request attribute name under which the JAAS Subject is store
*/
private String subjectAttributeName = null;
/**
* Flag indicating whether web-app specific context xmls may set the privileged flag.
*/
private boolean allowSelfPrivilegedWebApps = false;
/** The service used to flush authentication cache on session invalidation. */
private JaasSecurityManagerServiceMBean secMgrService;
/** */
private String[] filteredPackages;
/** Hold a proxy reference to myself, used when registering to MainDeployer */
private SubDeployerExt thisProxy;
public String getName()
{
return NAME;
}
public String getManagerClass()
{
return managerClass;
}
public void setManagerClass(String managerClass)
{
this.managerClass = managerClass;
}
public String getDomain()
{
return this.catalinaDomain;
}
public Properties getAuthenticators()
{
return this.authenticators;
}
public void setAuthenticators(Properties prop)
{
this.authenticators = prop;
log.debug("Passed set of authenticators=" + prop);
}
/**
* The most important atteribute - defines the managed domain.
* A catalina instance (engine) corresponds to a JMX domain, that's
* how we know where to deploy webapps.
*
* @param catalinaDomain the domain portion of the JMX ObjectNames
*/
public void setDomain(String catalinaDomain)
{
this.catalinaDomain = catalinaDomain;
}
public void setContextMBeanCode(String className)
{
this.contextClassName = className;
}
public String getContextMBeanCode()
{
return contextClassName;
}
/**
* Set the snapshot interval in milliseconds for snapshot mode = interval
*/
public void setSnapshotInterval(int interval)
{
this.snapshotInterval = interval;
}
/**
* Get the snapshot interval
*/
public int getSnapshotInterval()
{
return this.snapshotInterval;
}
/**
* Set the snapshot mode. Currently supported: instant or interval
*/
public void setSnapshotMode(String mode)
{
this.snapshotMode = mode;
}
/**
* Get the snapshot mode
*/
public String getSnapshotMode()
{
return this.snapshotMode;
}
/**
* Gets the JMX object name of a shared TreeCache to be used for clustered
* single-sign-on.
*
* @see #DEFAULT_CACHE_NAME
* @see org.jboss.web.tomcat.service.sso.TreeCacheSSOClusterManager
*/
public String getCacheName()
{
return cacheName;
}
/**
* Gets the JMX object name of a shared TreeCache to be used for clustered
* single-sign-on.
* <p/>
* <b>NOTE:</b> TreeCache must be deployed before this service.
*
* @see #DEFAULT_CACHE_NAME
* @see org.jboss.web.tomcat.service.sso.TreeCacheSSOClusterManager
*/
public void setCacheName(String cacheName)
{
this.cacheName = cacheName;
}
public boolean isUseLocalCache()
{
return useLocalCache;
}
public void setUseLocalCache(boolean useLocalCache)
{
this.useLocalCache = useLocalCache;
}
public boolean isUseJK()
{
return useJK;
}
public void setUseJK(boolean useJK)
{
this.useJK = useJK;
}
public int getMaxUnreplicatedInterval()
{
return maxUnreplicatedInterval;
}
public void setMaxUnreplicatedInterval(int maxUnreplicatedInterval)
{
this.maxUnreplicatedInterval = maxUnreplicatedInterval;
}
public boolean getDeleteWorkDirOnContextDestroy()
{
return deleteWorkDirOnContextDestroy;
}
public void setDeleteWorkDirOnContextDestroy(boolean deleteFlag)
{
this.deleteWorkDirOnContextDestroy = deleteFlag;
}
public String getHttpHeaderForSSOAuth()
{
return httpHeaderForSSOAuth;
}
public void setHttpHeaderForSSOAuth(String httpHeader)
{
this.httpHeaderForSSOAuth = httpHeader;
}
public String getSessionCookieForSSOAuth()
{
return sessionCookieForSSOAuth;
}
public void setSessionCookieForSSOAuth(String sessionC)
{
this.sessionCookieForSSOAuth = sessionC;
}
/**
* The SessionIdAlphabet is the set of characters used to create a session Id
*/
public void setSessionIdAlphabet(String sessionIdAlphabet)
{
SessionIDGenerator.getInstance().setSessionIdAlphabet(sessionIdAlphabet);
}
/**
* The SessionIdAlphabet is the set of characters used to create a session Id
*/
public String getSessionIdAlphabet()
{
return SessionIDGenerator.getInstance().getSessionIdAlphabet();
}
public boolean getUseJBossWebLoader()
{
return useJBossWebLoader;
}
public void setUseJBossWebLoader(boolean flag)
{
this.useJBossWebLoader = flag;
}
public String getConfigFile()
{
return serverConfigFile;
}
public void setConfigFile(String configFile)
{
this.serverConfigFile = configFile;
}
public String getSubjectAttributeName()
{
return this.subjectAttributeName;
}
public void setSubjectAttributeName(String name)
{
this.subjectAttributeName = name;
}
public boolean isAllowSelfPrivilegedWebApps()
{
return allowSelfPrivilegedWebApps;
}
public void setAllowSelfPrivilegedWebApps(boolean allowSelfPrivilegedWebApps)
{
this.allowSelfPrivilegedWebApps = allowSelfPrivilegedWebApps;
}
public void setSecurityManagerService(JaasSecurityManagerServiceMBean mgr)
{
this.secMgrService = mgr;
}
public String[] getFilteredPackages()
{
return filteredPackages;
}
public void setFilteredPackages(String[] pkgs)
{
this.filteredPackages = pkgs;
}
public void startService()
throws Exception
{
System.setProperty("catalina.ext.dirs",
(System.getProperty("jboss.server.home.dir")
+ File.separator + "lib"));
String objectNameS = catalinaDomain + ":type=server";
ObjectName objectName = new ObjectName(objectNameS);
// Set the modeler Registry MBeanServer to the that of the tomcat service
Registry.getRegistry().setMBeanServer(server);
Registry.getRegistry().registerComponent(Class.forName("org.apache.catalina.startup.Catalina").newInstance(),
objectName, "org.apache.catalina.startup.Catalina");
server.setAttribute(objectName, new Attribute
("catalinaHome",
System.getProperty("jboss.server.home.dir")));
server.setAttribute(objectName, new Attribute
("configFile", serverConfigFile));
server.setAttribute(objectName, new Attribute
("useNaming", Boolean.valueOf(false)));
server.setAttribute(objectName, new Attribute
("useShutdownHook", Boolean.valueOf(false)));
server.setAttribute(objectName, new Attribute
("await", Boolean.valueOf(false)));
server.setAttribute(objectName, new Attribute
("redirectStreams", Boolean.valueOf(false)));
server.invoke(objectName, "create", new Object[]{},
new String[]{});
server.invoke(objectName, "start", new Object[]{},
new String[]{});
// Configure any SingleSignOn valves
ObjectName ssoQuery = new ObjectName("*:type=Valve,*");
Iterator iterator = server.queryMBeans(ssoQuery, null).iterator();
while (iterator.hasNext())
{
ObjectName ssoObjectName =
((ObjectInstance) iterator.next()).getObjectName();
String name = ssoObjectName.getKeyProperty("name");
/* Ensure that the SingleSignOn valve requires that each
request be reauthenticated to the security mgr. Should not
be neccessary now that we cache the principal in the session.
if ((name != null) && (name.indexOf("SingleSignOn") >= 0))
{
log.info("Turning on reauthentication of each request on " +
ssoObjectName);
server.setAttribute(ssoObjectName, new Attribute
("requireReauthentication", Boolean.TRUE));
}
*/
// If the valve is a ClusteredSingleSignOn and we have a shared
// TreeCache configured, configure the valve to use the shared one
if (cacheName != null && "ClusteredSingleSignOn".equals(name))
{
String tcName = (String) server.getAttribute(ssoObjectName,
"treeCacheName");
tcName = (tcName != null ? tcName : DEFAULT_CACHE_NAME);
ObjectName ssoCacheName = new ObjectName(tcName);
// Only override if the valve's cacheName property was not
// explicitly set in server.xml to a non-default value
if (ssoCacheName.equals(new ObjectName(DEFAULT_CACHE_NAME)))
{
log.info("Setting the cache name to " + cacheName +
" on " + ssoObjectName);
server.setAttribute(ssoObjectName,
new Attribute("treeCacheName", cacheName));
}
}
}
// Register the web container JACC PolicyContextHandlers
HttpServletRequestPolicyContextHandler handler = new HttpServletRequestPolicyContextHandler();
PolicyContext.registerHandler(HttpServletRequestPolicyContextHandler.WEB_REQUEST_KEY,
handler, true);
// The ServiceController used to control web app startup dependencies
serviceController = (ServiceControllerMBean)
MBeanProxyExt.create(ServiceControllerMBean.class, ServiceControllerMBean.OBJECT_NAME, server);
// make a proxy to myself, so that calls from the MainDeployer
// can go through the MBeanServer, so interceptors can be added
thisProxy = (SubDeployerExt)
MBeanProxyExt.create(SubDeployerExt.class, super.getServiceName(), super.getServer());
// Register with the main deployer
mainDeployer.addDeployer(thisProxy);
// If we are hot-deployed *after* the overall server is started
// we'll never receive Server.START_NOTIFICATION_TYPE, so check
// with the Server and start the connectors immediately, if this is the case.
// Otherwise register to receive the server start-up notification.
Boolean started = (Boolean)server.getAttribute(ServerImplMBean.OBJECT_NAME, "Started");
if (started.booleanValue() == true)
{
log.debug("Server '" + ServerImplMBean.OBJECT_NAME +
"' already started, starting connectors now");
startConnectors();
}
else
{
// Register for notification of the overall server startup
log.debug("Server '" + ServerImplMBean.OBJECT_NAME +
"' not started, registering for start-up notification");
server.addNotificationListener(ServerImplMBean.OBJECT_NAME, this, null, null);
}
}
public void stopService()
throws Exception
{
String objectNameS = catalinaDomain + ":type=server";
ObjectName objectName = new ObjectName(objectNameS);
server.invoke(objectName, "stop", new Object[]{},
new String[]{});
server.invoke(objectName, "destroy", new Object[]{},
new String[]{});
server.unregisterMBean(objectName);
MBeanServer server2 = server;
// deregister with MainDeployer
mainDeployer.removeDeployer(thisProxy);
// Unregister any remaining jboss.web or Catalina MBeans
ObjectName queryObjectName = new ObjectName
(catalinaDomain + ":*");
Iterator iterator =
server2.queryMBeans(queryObjectName, null).iterator();
while (iterator.hasNext())
{
ObjectInstance oi = (ObjectInstance) iterator.next();
ObjectName toRemove = oi.getObjectName();
// Exception: Don't unregister the service right now
if (!"WebServer".equals(toRemove.getKeyProperty("service")))
{
if (server2.isRegistered(toRemove))
{
server2.unregisterMBean(toRemove);
}
}
}
queryObjectName = new ObjectName("Catalina:*");
iterator = server2.queryMBeans(queryObjectName, null).iterator();
while (iterator.hasNext())
{
ObjectInstance oi = (ObjectInstance) iterator.next();
ObjectName name = oi.getObjectName();
server2.unregisterMBean(name);
}
}
public void startConnectors() throws Exception
{
ObjectName service = new ObjectName(catalinaDomain + ":type=Service,serviceName=jboss.web");
Object[] args = {};
String[] sig = {};
Connector[] connectors = (Connector[]) server.invoke(service,
"findConnectors", args, sig);
for (int n = 0; n < connectors.length; n++)
{
Lifecycle lc = (Lifecycle) connectors[n];
lc.start();
}
//There may be a need to start the connectors that are defined in
//the multiple services in tomcat server.xml
startAllConnectors();
// start the sip application disptacher after the connectors have been started
// so that serverl can act as UAC in servletInitialized callback
ObjectName sipApplicationDispatcher = new ObjectName(catalinaDomain + ":type=SipApplicationDispatcher");
server.invoke(sipApplicationDispatcher,"start", args, sig);
// Notify listeners that connectors have started processing requests
sendNotification(new Notification(TOMCAT_CONNECTORS_STARTED,
this, getNextNotificationSequenceNumber()));
}
public void stopConnectors() throws Exception
{
ObjectName service = new ObjectName(catalinaDomain + ":type=Service,serviceName=jboss.web");
Object[] args = {};
String[] sig = {};
Connector[] connectors = (Connector[]) server.invoke(service,
"findConnectors", args, sig);
for (int n = 0; n < connectors.length; n++)
{
Lifecycle lc = (Lifecycle) connectors[n];
lc.stop();
}
//There may be a need to stop the connectors that are defined in
//the multiple services in tomcat server.xml
stopAllConnectors();
// stop the sip application disptacher
ObjectName sipApplicationDispatcher = new ObjectName(catalinaDomain + ":type=SipApplicationDispatcher");
server.invoke(sipApplicationDispatcher,"stop", args, sig);
}
public void handleNotification(Notification msg, Object handback)
{
String type = msg.getType();
if (type.equals(Server.START_NOTIFICATION_TYPE))
{
log.debug("Saw " + type + " notification, starting connectors");
try
{
startConnectors();
}
catch (Exception e)
{
log.warn("Failed to startConnectors", e);
}
}
}
public AbstractWebDeployer getDeployer(DeploymentInfo di) throws Exception
{
ClassLoader loader = di.ucl;
Class deployerClass = loader.loadClass("org.mobicents.servlet.sip.startup.jboss.TomcatConvergedDeployer");
AbstractWebDeployer deployer = (AbstractWebDeployer) deployerClass.newInstance();
DeployerConfig config = new DeployerConfig();
config.setDefaultSecurityDomain(this.defaultSecurityDomain);
config.setSubjectAttributeName(this.subjectAttributeName);
config.setServiceClassLoader(getClass().getClassLoader());
config.setManagerClass(this.managerClass);
config.setJava2ClassLoadingCompliance(this.java2ClassLoadingCompliance);
config.setUnpackWars(this.unpackWars);
config.setLenientEjbLink(this.lenientEjbLink);
config.setCatalinaDomain(catalinaDomain);
if(isSipServletApplication(di)) {
config.setContextClassName(SipHostConfig.SIP_CONTEXT_CLASS);
}else {
config.setContextClassName(contextClassName);
}
config.setServiceName(serviceName);
config.setSnapshotInterval(this.snapshotInterval);
config.setSnapshotMode(this.snapshotMode);
config.setUseLocalCache(this.useLocalCache);
config.setUseJK(this.useJK);
config.setMaxUnreplicatedInterval(this.maxUnreplicatedInterval);
config.setSubjectAttributeName(this.subjectAttributeName);
config.setUseJBossWebLoader(this.useJBossWebLoader);
config.setAllowSelfPrivilegedWebApps(this.allowSelfPrivilegedWebApps);
config.setSecurityManagerService(this.secMgrService);
config.setFilteredPackages(filteredPackages);
deployer.setServer(server);
deployer.init(config);
return deployer;
}
/**
* Start all the connectors
*
* @throws JMException
* @throws LifecycleException
*/
private void startAllConnectors() throws JMException, LifecycleException
{
/**
* Not able to query the Catalina server for the services that it has
* registered. A usuable solution is to query the MBean server for the
* tomcat services.
* http://www.jboss.com/index.html?module=bb&op=viewtopic&t=75353
*/
ObjectName oname = new ObjectName("*:type=Service,*");
Set services = server.queryMBeans(oname,null);
Iterator iter = services.iterator();
while(iter.hasNext())
{
ObjectInstance oi = (ObjectInstance)iter.next();
ObjectName on = oi.getObjectName();
//Ignore jboss.web:*
if(this.catalinaDomain.equals(on.getDomain()) )
continue;
String key = on.getKeyProperty("serviceName");
if(key != null)
{
Connector[] connectors = (Connector[]) server.invoke(on,
"findConnectors", new Object[0], new String[0]);
for (int n = 0; n < connectors.length; n++)
{
Lifecycle lc = (Lifecycle) connectors[n];
lc.start();
}
}
}
}
/**
* Stop all the connectors
*
* @throws JMException
* @throws LifecycleException
*/
private void stopAllConnectors() throws JMException, LifecycleException
{
ObjectName oname = new ObjectName("*:type=Service,*");
Set services = server.queryMBeans(oname,null);
Iterator iter = services.iterator();
while(iter.hasNext())
{
ObjectInstance oi = (ObjectInstance)iter.next();
ObjectName on = oi.getObjectName();
//Ignore jboss.web:*
if(this.catalinaDomain.equals(on.getDomain()) )
continue;
String key = on.getKeyProperty("serviceName");
if(key != null)
{
Connector[] connectors = (Connector[]) server.invoke(on,
"findConnectors", new Object[0], new String[0]);
for (int n = 0; n < connectors.length; n++)
{
Lifecycle lc = (Lifecycle) connectors[n];
lc.stop();
}
}
}
}
/***** Code added by jean.deruelle regarding the original jboss file ********/
public static final String SAR_SUFFIX = ".sar2";
/**
* The suffixes we accept, along with their relative order.
* the accept method will return false if the sar2 doesn't contain a sip.xml file
*/
private static final String[] DEFAULT_ENHANCED_SUFFIXES =
new String[] { "600:" + SAR_SUFFIX};
/**
*
*/
public JBossSip() {
super();
String[] enhancedSuffixes = getEnhancedSuffixes();
String[] convergedEnhancedSuffixes = new String[enhancedSuffixes.length + DEFAULT_ENHANCED_SUFFIXES.length];
System.arraycopy(enhancedSuffixes, 0, convergedEnhancedSuffixes, 0, enhancedSuffixes.length);
System.arraycopy(DEFAULT_ENHANCED_SUFFIXES, 0, convergedEnhancedSuffixes, enhancedSuffixes.length, DEFAULT_ENHANCED_SUFFIXES.length);
setEnhancedSuffixes(convergedEnhancedSuffixes);
}
@Override
public boolean accepts(DeploymentInfo sdi) {
boolean accept = super.accepts(sdi);
String urlPath = sdi.url.getPath();
String shortName = sdi.shortName;
boolean checkDir = sdi.isDirectory && !(sdi.isXML || sdi.isScript);
//if this is a .sar2 file and it contains a sip.xml file then we accept it
if ((urlPath.endsWith(SAR_SUFFIX) ||
(checkDir && shortName.endsWith(SAR_SUFFIX))
|| urlPath.endsWith("war") ||
(checkDir && shortName.endsWith("war")))&&
isSipServletApplication(sdi)) {
return true;
}
return accept;
}
/**
* Check if the WEB-INF/sip.xml file can be found in the local class loader
* of the service deployment info. If it is then it means that a sip servlet application
* is trying to be deployed
* @param di the service deployment info
* @return true if the service being deployed contains WEB-INF/sip.xml, false otherwise
*/
public static boolean isSipServletApplication(DeploymentInfo di) {
URL url = di.localCl.findResource(SipContext.APPLICATION_SIP_XML);
if(url != null) {
try {
url.openStream();
return true;
} catch (IOException e) {
return false;
}
} else {
File deploymentPath;
try {
deploymentPath = new File(di.url.toURI());
} catch(URISyntaxException e) {
deploymentPath = new File(di.url.getPath());
}
if(deploymentPath.isDirectory())
return SipApplicationAnnotationUtils.findPackageInfoinDirectory(deploymentPath);
else
return SipApplicationAnnotationUtils.findPackageInfoInArchive(deploymentPath);
}
}
}