/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 1997-2009 Sun Microsystems, Inc. All rights reserved.
* Copyright (c) Ericsson AB, 2004-2008. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can obtain
* a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
* or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
* Sun designates this particular file as subject to the "Classpath" exception
* as provided by Sun in the GPL Version 2 section of the License file that
* accompanied this code. If applicable, add the following below the License
* Header, with the fields enclosed by brackets [] replaced by your own
* identifying information: "Portions Copyrighted [year]
* [name of copyright owner]"
*
* Contributor(s):
*
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package com.ericsson.ssa.container.sim;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.spi.ServiceRegistry;
import javax.servlet.sip.ar.SipApplicationRouter;
import javax.servlet.sip.ar.spi.SipApplicationRouterProvider;
import org.jvnet.glassfish.comms.util.LogUtil;
/**
* The ApplicationDispatcher is the Layer in the sip-stack that is responsible
* for invoking applications. It uses the Application Router to determine which
* application to invoke.
*
* ApplicationDispatcher maintains two references to application routers.
* One is the application router that is present in the system by default,
* typically the AlphabeticalRouter (unless configured otherwise).
*
* The administrator has the possibility of deploying a custom application
* router by using the 'asadmin deploy' command line.
*
* @author Robert Handl, Per Pettersson, Kristoffer Gronowski, Yvo Bogers
*/
public class ApplicationRouterSelector {
/* Singleton instance */
private static ApplicationRouterSelector _instance = new ApplicationRouterSelector();
private static TreeMap<String, ServletDispatcher> m_contextMapping = new TreeMap<String, ServletDispatcher>();
private static final Logger m_logger = LogUtil.SIP_LOGGER.getLogger();
private ClassLoader saved = null;
/*
* The runtime router is the one used during traffic.
* At startup the runtimeRouter is determined. The system will not
* start without a runtimeRouter.
*/
private SipApplicationRouter runtimeRouter = null;
/*
* A default router reference is remembered when a custom router is deployed
* over it. The ApplicationDispatcher resorts to the default whenever the
* custom router is undeployed again.
*/
private SipApplicationRouter defaultRouter = null;
/**
* The customDeplyedRouter is a router that is explicitly deployed
* with an asadamin deploy command. However, it does not have to be
* the router runtime router.
*/
private SipApplicationRouter customDeployedRouter = null;
private transient Properties customDeployedRouterProperties;
private transient String customDeployedRouterName;
public static SipApplicationRouter findSPIRouter(String providerName) {
return findSPIRouter(providerName, Thread.currentThread().getContextClassLoader());
}
public static SipApplicationRouter findSPIRouter(String providerName, ClassLoader loader) {
for (Iterator<? extends SipApplicationRouterProvider> results = ServiceRegistry.lookupProviders(SipApplicationRouterProvider.class, loader); results.hasNext(); /*nothing*/) {
SipApplicationRouterProvider type = results.next();
if (providerName == null || providerName.equals(type.getClass().getName())) {
// first of specific is returned
if (m_logger.isLoggable(Level.FINEST)){
m_logger.log(Level.FINEST, "found SPI {0}", new Object[]{type});
}
return type.getSipApplicationRouter(); // return matched
}
}
return null; // not found
}
private void selectAndInitRouter() {
SipApplicationRouter newRouter = chooseApplicationRouter();
if (newRouter == runtimeRouter) {
if (m_logger.isLoggable(Level.FINEST)){
m_logger.log(Level.FINEST, "No change in router. Keep using {0}", new Object[]{newRouter.getClass().getName()});
}
// nothing changed
return;
} else if (newRouter == null) {
// not allowed
// do not change the runtimeRouter...
if (m_logger.isLoggable(Level.FINEST)){
m_logger.log(Level.FINEST, "Not changing runtime router", new Object[]{runtimeRouter.getClass().getName()});
}
return;
} else {
// new non-null router selected
if (runtimeRouter != null) {
setRouterContext();
try {
// allow the currently active runtime application router to cleanup
// its resources
if (runtimeRouter != null){
if (m_logger.isLoggable(Level.FINE)){
m_logger.log(Level.FINE,
"Overwriting previously active AR: {0} with new AR {1}",
new Object[]{runtimeRouter.getClass().getName(), newRouter.getClass().getName()});
}
runtimeRouter.destroy();
}
} finally {
resetRouterContext();
}
}
if (m_logger.isLoggable(Level.FINEST)){
m_logger.log(Level.FINEST,
"setting new AR {0}",
new Object[]{newRouter.getClass().getName()});
}
runtimeRouter = newRouter;
setRouterContext();
try {
// initialize the new application router with the currently
// active list of applications.
// XXX or other way around?
if ((newRouter == customDeployedRouter) && (customDeployedRouterProperties != null)) {
newRouter.init(customDeployedRouterProperties);
} else {
newRouter.init();
}
newRouter.applicationDeployed(new ArrayList<String>(m_contextMapping.keySet()));
} finally {
resetRouterContext();
}
}
}
private SipApplicationRouter chooseApplicationRouter() {
try {
String preferredRouter = System.getProperty("javax.servlet.sip.ar.spi.SipApplicationRouterProvider");
if (preferredRouter != null) {
// try to load the preferred router
if (customDeployedRouter != null && preferredRouter.equals(customDeployedRouter.getClass().getName())) {
if (m_logger.isLoggable(Level.FINEST)){
m_logger.log(Level.FINEST,
"choosing preferred/deployed router",
new Object[]{customDeployedRouter.getClass().getName()});
}
return customDeployedRouter;
} else {
// try the classpath
SipApplicationRouter cprouter = findSPIRouter(preferredRouter);
if (cprouter != null) {
if (m_logger.isLoggable(Level.FINEST)){
m_logger.log(Level.FINEST,
"choosing preferred router from the classpath",
new Object[]{cprouter.getClass().getName()});
}
return cprouter;
}
}
}
// no preferred router or preferred router not found
if (customDeployedRouter != null) {
if (m_logger.isLoggable(Level.FINEST)){
m_logger.log(Level.FINEST,
"choosing non preferred deployed router",
new Object[]{customDeployedRouter.getClass().getName()});
}
return customDeployedRouter; // prefer the custom router
} else {
SipApplicationRouter cprouter = findSPIRouter(null); // find first in CP
if (cprouter != null) {
if (m_logger.isLoggable(Level.FINEST)){
m_logger.log(Level.FINEST,
"choosing non preferred classpath router",
new Object[]{customDeployedRouter.getClass().getName()});
}
return cprouter;
}
}
} catch (Throwable t) {
if (m_logger.isLoggable(Level.INFO)){
m_logger.log(Level.INFO,
"choosing application router failed, falling back to default",
t);
}
}
// if all else fails fallback to the defaultRouter
if (m_logger.isLoggable(Level.FINEST)){
m_logger.log(Level.FINEST,
"choosing default router",
new Object[]{defaultRouter.getClass().getName()});
}
return defaultRouter;
}
/**
* Return an instance of <code>ApplicationRouterSelecor</code>.
*
* @return A <code>ApplicationRouterSelecor</code>.
*/
public static ApplicationRouterSelector getInstance() {
return _instance;
}
public synchronized void start() {
if(runtimeRouter == null) {
selectAndInitRouter();
}
if (runtimeRouter == null) {
throw new RuntimeException(
"Can not start without application router.");
}
if (m_logger.isLoggable(Level.INFO)) {
m_logger.log(Level.INFO,
"started Application Dispatcher with router {0}",
new Object[]{runtimeRouter.getClass().getName()});
}
}
public synchronized void stop() {
setRouterContext();
try {
runtimeRouter.destroy();
} finally {
resetRouterContext();
}
if (m_logger.isLoggable(Level.INFO)) {
m_logger.log(Level.INFO,
"stopped Application Dispatcher and router {0}",
new Object[]{runtimeRouter.getClass().getName()});
}
}
/**
* Registers a Servlet Dispatcher associated to a application Name
*
* @param applicationName
* The application name.
* @param servletDispatcher
* A <code>ServletDispatcher</code>.
*/
public void addServletDispatcher(String applicationName,
ServletDispatcher servletDispatcher) {
// Notify the application router
List<String> apps = new ArrayList<String>(1);
apps.add(applicationName);
setRouterContext();
try {
runtimeRouter.applicationDeployed(apps);
} finally {
resetRouterContext();
}
}
/**
* Deregisters a Servlet Dispatcher for a specified application
*
* @param applicationName
*/
public void removeServletDispatcher(String applicationName) {
// Notify the application router
List<String> apps = new ArrayList<String>(1);
apps.add(applicationName);
setRouterContext();
try {
runtimeRouter.applicationUndeployed(apps);
} finally {
resetRouterContext();
}
}
/**
* Set a custom Application Router. A custom deployed application router
* will take precedence over the default application router.
*
* @param aRouter
*/
public void setCustomApplicationRouter(String moduleName, SipApplicationRouter aRouter, Properties aCustomProps) {
m_logger.log(Level.FINE, "Setting custom Application Router: " + aRouter);
customDeployedRouter = aRouter;
customDeployedRouterProperties = aCustomProps;
customDeployedRouterName = moduleName;
selectAndInitRouter();
}
/**
* Fall back to the default application router
*
* @return false if no default is available, in which case custom router is
* not unset. true otherwise.
*/
public boolean unsetCustomApplicationRouter(String moduleName) {
if (moduleName.equals(customDeployedRouterName)) {
// undeploying the top most custome router
// XXX in the future we should set the customDeployedRouter
// to the next one in the stack instead of setting it to null
customDeployedRouterName = null;
customDeployedRouterProperties = null;
if (runtimeRouter == customDeployedRouter) {
customDeployedRouter = null;
// recalculate the new router
selectAndInitRouter();
} else {
customDeployedRouter = null;
}
} //else it is not the top most router
return true;
}
/**
* Specify the default Application Router classname. The Application
* Dispatcher will use java reflection to instantiate the default
* application router. The default application router is used for routing
* SIP traffic. It is possible to override this behaviour by calling
* setCustomApplicationRouter.
*
* This method will not initialize the router yet. This will happen when
* the start() method is called.
*
* @param className
*/
public void setApplicationRouterClass(String className) {
if (m_logger.isLoggable(Level.FINE)) {
m_logger.log(Level.FINE, "Instantiating default Application Router : " + className);
}
try {
Class ar = Class.forName(className);
defaultRouter = (SipApplicationRouter) ar.newInstance();
} catch (ClassNotFoundException e) {
m_logger.log(Level.WARNING,
"Failed to instantiate ApplicationRouter", e);
} catch (InstantiationException e) {
m_logger.log(Level.WARNING,
"Failed to instantiate ApplicationRouter", e);
} catch (IllegalAccessException e) {
m_logger.log(Level.WARNING,
"Failed to instantiate ApplicationRouter", e);
}
}
/**
* Utility method for setting the context classloader.
* Move this to common module?
*/
@SuppressWarnings("unchecked")
private ClassLoader _setContextClassLoader(ClassLoader newClassLoader) {
// Can reference only final local variables from dopriveleged block
final ClassLoader classLoaderToSet = newClassLoader;
final Thread currentThread = Thread.currentThread();
ClassLoader originalClassLoader = currentThread.getContextClassLoader();
if (classLoaderToSet != originalClassLoader) {
if (System.getSecurityManager() == null) {
currentThread.setContextClassLoader(classLoaderToSet);
} else {
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction() {
public java.lang.Object run() {
currentThread.setContextClassLoader
(classLoaderToSet);
return null;
}
}
);
}
}
return originalClassLoader;
}
/**
* Sets the context for invoking the application router
*/
public void setRouterContext() {
try {
if(runtimeRouter == null) {
selectAndInitRouter();
}
ClassLoader routerCL = runtimeRouter.getClass().getClassLoader();
saved = _setContextClassLoader(routerCL);
} catch (Exception e) {
if (m_logger.isLoggable(Level.WARNING)) {
m_logger.log(Level.WARNING, e.getMessage(), e);
}
}
}
/**
* Resets the context after invoking the application router
*/
public void resetRouterContext() {
try {
_setContextClassLoader(saved);
} catch (Exception e) {
if (m_logger.isLoggable(Level.WARNING)) {
m_logger.log(Level.WARNING, e.getMessage(), e);
}
}
}
public SipApplicationRouter getRuntimeRouter(){
return runtimeRouter;
}
}