/*
* 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.io.Serializable;
import java.io.UnsupportedEncodingException;
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.Servlet;
import javax.servlet.sip.Address;
import javax.servlet.sip.ServletParseException;
import javax.servlet.sip.SipURI;
import javax.servlet.sip.TooManyHopsException;
import javax.servlet.sip.ar.SipApplicationRouter;
import javax.servlet.sip.ar.SipApplicationRouterInfo;
import javax.servlet.sip.ar.SipApplicationRoutingDirective;
import javax.servlet.sip.ar.SipApplicationRoutingRegion;
import javax.servlet.sip.ar.SipRouteModifier;
import javax.servlet.sip.ar.SipTargetedRequestInfo;
import javax.servlet.sip.ar.SipTargetedRequestType;
import javax.servlet.sip.ar.spi.SipApplicationRouterProvider;
import org.apache.catalina.ContainerEvent;
import org.apache.catalina.ContainerListener;
import org.apache.catalina.LifecycleEvent;
import org.apache.catalina.LifecycleListener;
import org.jvnet.glassfish.comms.util.LogUtil;
import com.ericsson.ssa.container.reporter.ReporterResolver;
import com.ericsson.ssa.container.reporter.Reporter;
import com.ericsson.ssa.container.reporter.ReporterSet;
import com.ericsson.ssa.sip.AddressImpl;
import com.ericsson.ssa.sip.DialogManager;
import com.ericsson.ssa.sip.Dispatcher;
import com.ericsson.ssa.sip.Header;
import com.ericsson.ssa.sip.Layer;
import com.ericsson.ssa.sip.LayerHelper;
import com.ericsson.ssa.sip.RemoteLockException;
import com.ericsson.ssa.sip.RemoteLockRuntimeException;
import com.ericsson.ssa.sip.ServiceHandler;
import com.ericsson.ssa.sip.SipApplicationSessionImpl;
import com.ericsson.ssa.sip.SipApplicationSessionUtil;
import com.ericsson.ssa.sip.SipFactoryImpl;
import com.ericsson.ssa.sip.SipServletRequestImpl;
import com.ericsson.ssa.sip.SipServletResponseImpl;
import com.ericsson.ssa.sip.SipSessionBase;
import com.ericsson.ssa.sip.SipSessionManagerRegistry;
import com.ericsson.ssa.sip.SipURIImpl;
import com.ericsson.ssa.sip.URIImpl;
import com.ericsson.ssa.sip.UriUtil;
import com.ericsson.ssa.sip.dns.SipTransports;
import com.ericsson.ssa.sip.dns.TargetTuple;
import com.ericsson.ssa.sip.persistence.IncompleteDialogException;
/**
* 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 ApplicationDispatcher implements Layer,
ContainerListener, LifecycleListener, ServiceHandler {
public static final String APPLICATION_DISPATCHER = "com.ericsson.ssa.container.sim.ApplicationDispatcher/1.0";
private static final String AR_STATE = "x-ar-state";
/* Singleton instance */
private static ApplicationDispatcher m_singletonInstance = new ApplicationDispatcher();
private static TreeMap<String, ServletDispatcher> m_contextMapping = new TreeMap<String, ServletDispatcher>();
private static final Logger m_logger = LogUtil.SIP_LOGGER.getLogger();
private Layer m_nextLayer = null;
private ClassLoader saved = null;
private Reporter _layerReporterSet;
private Reporter _servletReporterSet;
public void setReporters(String reporters) {
ReporterResolver resolver = ReporterResolver.getInstance();
ReporterSet repSet = resolver.getReporter(reporters);
_layerReporterSet = repSet.getFilteredSet(Reporter.InterceptionType.LAYER);
_servletReporterSet = repSet.getFilteredSet(Reporter.InterceptionType.SERVLET);
}
public Reporter getReporter() {
return _layerReporterSet;
}
public Reporter getServletReporters() {
return _servletReporterSet;
}
protected ApplicationDispatcher() {
super();
}
public synchronized void start() {
ApplicationRouterSelector.getInstance().start();
}
public synchronized void stop() {
ApplicationRouterSelector.getInstance().stop();
}
public void lifecycleEvent(LifecycleEvent event) {
if (m_logger.isLoggable(Level.FINE)) m_logger.log(Level.FINE, "Got lifecycle event!");
}
public void containerEvent(ContainerEvent event) {
if (m_logger.isLoggable(Level.FINER)) m_logger.log(Level.FINER, "Got Container event!");
}
/**
* The method is a part of the dispatch chain. For the initial request the
* method will try to call the ServletDispatcher to invoke some servlets,
* otherwise it will pop up the next dispatcher to invoke.
*
* @param request
* A <code>SipServletRequestImpl</code>.
*/
public void dispatch(SipServletRequestImpl request) {
// Check if request is initial
if (request.isInitial()) {
// JSR 289, ask the Application Router which application to invoke.
invokeServletDispatchers(request);
} else {
// Subsequent request, should go through the default proxy.
dispatchViaStatelessProxy(request);
}
}
/*
* This method implements section 15.4.1 of JSR289.
*/
private void invokeServletDispatchers(SipServletRequestImpl request) {
request.setSentOnThread(false);
SipApplicationRouter runtimeRouter = ApplicationRouterSelector.getInstance().getRuntimeRouter();
// this resets the flag that the routing directive was explicitly set
// e.g., in a B2B case the app explicitly sets the directive after
// we set it implicitly
// then if we clone the request after the AD was invoked we no longer
// want this flag to be set, so implicit setting of the directive becomes
// possible again.
request.resetExplicit();
SipSessionBase session = request.getSessionImpl();
if (session != null){
session.access(); // modify the access times
}
SipApplicationRoutingRegion region = null;
Serializable newStateInfo = null;
SipApplicationRouterInfo info = null;
boolean targetedApp = false;
// If routing directive is NEW, we should treat this as a new request
// i.e. reset the Application Router state.
if (SipApplicationRoutingDirective.NEW.equals(
request.getRoutingDirective())){
Address initialPoppedRoute = request.getInitialPoppedRoute();
if (initialPoppedRoute != null && initialPoppedRoute.getParameter(AR_STATE) != null) {
try {
ApplicationRouterSelector.getInstance().setRouterContext();
ArInfo restoredArInfo = ArInfo.fromString(initialPoppedRoute.getParameter(AR_STATE));
newStateInfo = restoredArInfo.getStateInfo();
region = restoredArInfo.getRegion();
request.setInternalRoutingDirective(SipApplicationRoutingDirective.CONTINUE, null);
} finally {
ApplicationRouterSelector.getInstance().resetRouterContext();
}
if (m_logger.isLoggable(Level.FINEST)) {
m_logger.log(Level.FINEST, "routing based on restored AR routing info region {0} and stateinfo {1} ", new Object[] {region, newStateInfo});
}
} else {
// try the targeted request first
SipTargetedRequestInfo stri = TargettedRequestHandler.get(request);
//TODO. Complete formatting after review is done.
if (stri != null) {
String appName = stri.getApplicationName();
info = runtimeRouter.getNextApplication(
request,
region,
SipApplicationRoutingDirective.CONTINUE,
stri,
newStateInfo);
if (appName.equals(info.getNextApplicationName())) {
targetedApp = true;
} else {
if (m_logger.isLoggable(Level.FINEST)) {
m_logger.log(Level.FINEST,
"AR selected app {0} expected {1}, continuing non-targeted",
new Object[]{info.getNextApplicationName(), appName});
}
// no match in what the AR returns.
// let's skip this appname and continue with the normal selection.
// remove the parameter from the request to ensure that the request is no longer
// targeted
request.getRequestURI().removeParameter(URIImpl.SASID_PARAM);
}
}
// clear the stateInfo
request.setArInfo(new ArInfo(null,null));
region = determineRegion(request);
newStateInfo = null;
if (m_logger.isLoggable(Level.FINEST)) {
m_logger.log(Level.FINEST, "clearing the state info and setting intial region {0}", new Object[] {region});
}
}
} else {
// CONTINUE or REVERSE
// TODO: what about reverse, should we also reverse the regions?
region = request.getArInfo().getRegion();
newStateInfo = request.getArInfo().getStateInfo();
if (m_logger.isLoggable(Level.FINEST)) {
m_logger.log(Level.FINEST, "continueing internal routing: region {0} and stateinfo {1} ", new Object[] {region, newStateInfo});
}
}
SipApplicationRoutingDirective rDirective = request.getRoutingDirective();
boolean appSelectionComplete = true;
do {
// No match, cleanup and take another round in the Application Router
ApplicationRouterSelector.getInstance().setRouterContext();
// issue 192, guard for malicious application routers
try {
if (!targetedApp) {
if (m_logger.isLoggable(Level.FINEST)) {
m_logger.log(Level.FINEST, "non-targeted request to AR with region {0} and directive {1} ", new Object[] {region, rDirective});
}
info = runtimeRouter.getNextApplication(
request,
region,
rDirective,
null,
newStateInfo);
} else {
targetedApp = false; //only the first one is targeted
}
} catch (RuntimeException e) {
m_logger.log(Level.SEVERE,
"Application Router threw RuntimeException: " + e.getMessage(),
e);
sendARFailed(request);
// give up
return;
} finally {
ApplicationRouterSelector.getInstance().resetRouterContext();
}
if (info != null) {
newStateInfo = info.getStateInfo();
region = info.getRoutingRegion();
} else {
if (m_logger.isLoggable(Level.FINEST)) {
m_logger.log(Level.FINEST, "AR did not return any info. no application deployed??");
}
break;
}
rDirective = SipApplicationRoutingDirective.CONTINUE;
request.setArInfo(new ArInfo(newStateInfo, region));
request.setRegion(region);
if (modifyRouteSet(request, info, region)) {
break;
}
// else treat as NO_ROUTE and look for application name
String application = info.getNextApplicationName();
if (application == null) {
// if (m_logger.isLoggable(Level.FINE)){
// m_logger.log(Level.FINE,
// "Application Dispatcher: no application and NO_ROUTE");
// }
break;
}
ServletDispatcher sd = m_contextMapping.get(application);
if (sd==null){
m_logger.log(
Level.SEVERE, "Unable to invoke application " + application
+ ", check your AR configuration!");
sendARFailed(request);
return;
}
//used to remember to put back the popped route when the last application in case no more application was actually visited
boolean wasRoutePopped = false;
try {
request.activateSentOnThread(); //Activate the function
if (appSelectionComplete) {
if (!request.isFirstInvocation()) {
modifyRemoteAddress(request);
wasRoutePopped = popTopRoute(request);
} else {
request.setFirstInvocation(false);
}
}
// Push the ApplicationDispatcher on to the application stack
// so we come back into the dispatch() method when the request
// is proxied, for example.
request.pushApplicationDispatcher(this);
appSelectionComplete = sd.invoke(request, info);
} catch (TooManyHopsException tmhe) {
if (m_logger.isLoggable(Level.FINE)) {
m_logger.log(Level.FINE, "Too Many Hops Exception ", tmhe);
}
sendResponse(request, SipServletResponseImpl.SC_TOO_MANY_HOPS);
return;
} catch (RemoteLockRuntimeException e) {
if (m_logger.isLoggable(Level.FINE)) {
m_logger.log(Level.FINE, "Dialog was remotely locked ", e);
}
SipServletResponseImpl response = request.createTerminatingResponse(SipServletResponseImpl.SC_SERVER_INTERNAL_ERROR);
response.setHeader(Header.RETRY_AFTER, "5");
response.popDispatcher().dispatch(response);
return;
} catch (IncompleteDialogException e) {
if (m_logger.isLoggable(Level.FINE)) {
m_logger.log(Level.FINE, "Dialog was incomplete", e);
}
sendResponse(request, SipServletResponseImpl.SC_CALL_LEG_DONE);
return;
} catch (Exception e) {
if (m_logger.isLoggable(Level.WARNING)) {
m_logger.log(Level.WARNING, "Error invoking service ", e);
}
sendARFailed(request);
return;
} finally {
request.clearSentOnThread(); //Important not to leak mem
}
if (appSelectionComplete) {
// The invoked application has either proxied the request, or
// replied with a response.
// the proxies request or any new request by a B2B will enter this
// loop in a new thread
if (m_logger.isLoggable(Level.FINE)){
m_logger.log(Level.FINE,
"Application Dispatcher did invoke a servlet in app {0}, stopping this loop", new Object[]{application});
}
return;
}
//pop the ApplicationDispatcher since the application was not invoked
request.popDispatcher();
//also take back the popped header
if (wasRoutePopped) {
request.pushRoute(request.getPoppedRoute());
}
} while (info != null);
//No apps matching, doing stateless SIP proxying. If not the correct behavior
//then it is up to the user to deploy an app that always returns 404!
dispatchViaStatelessProxy(request);
}
private SipApplicationRoutingRegion determineRegion(
SipServletRequestImpl anRequest) {
switch (anRequest.getSessionCase()) {
case ORIGINATING:
return SipApplicationRoutingRegion.ORIGINATING_REGION;
case TERMINATING:
case TERMINATING_UNREGISTERED:
return SipApplicationRoutingRegion.TERMINATING_REGION;
default:
// internal or external
// we do not know which region.
// just choose one
// TODO we could make this configurable
// OR we could look at other indications like the port or host
// on which the request is received or the domain used...
return SipApplicationRoutingRegion.TERMINATING_REGION;
}
}
private void modifyRemoteAddress(SipServletRequestImpl request) {
TargetTuple remoteHop = null;
// TODO verify if this is correct
if (request.getInitialRemote() != null) {
if (request.getLocal() != null) {
remoteHop = new TargetTuple(
request.getInitialRemote().getProtocol(),
request.getLocal().getAddress().getHostAddress(), 0);
} else {
// initialremote is set, but local is not?
// should not happen
}
} else {
// initialRemote not set
if (request.getLocal() != null) {
// remote not set and local is
remoteHop = new TargetTuple(
SipTransports.TCP_PROT,
request.getLocal().getAddress().getHostAddress(), 0);
} else {
// both not set
// should this happen?
}
}
request.setRemoteHop(remoteHop);
}
private boolean popTopRoute(SipServletRequestImpl request) {
if (request.isLocalRoute()) {
Address popped = request.popRouteHeader();
request.setPoppedRoute(popped);
if (popped != null) {
return true;
}
} else {
//reset the popped route
request.setPoppedRoute(null);
}
return false;
}
/**
* modify the routes based on the routerinfo returned by the AR
* @param request the request that was passed to the AR
* @param info the router info returned by the AR
* @return true if the routerinfo indicates that the request must be
* routed externally.
*/
private boolean modifyRouteSet(SipServletRequestImpl request,
SipApplicationRouterInfo info,
SipApplicationRoutingRegion region) {
SipRouteModifier mod = info.getRouteModifier();
String[] routes = info.getRoutes();
if (mod == null) {
return false;
}
try {
switch (mod) {
case ROUTE:
if (routes.length==0 || routes[0] == null) {
// what is the intention.
// assume route external without push
if (m_logger.isLoggable(Level.FINEST)) {
m_logger.log(Level.FINEST, "AR returned empty route. Routing externally");
}
return true;
} else if (isInternal(routes[0])) {
// internal route indicated. Push this
// TODO or setInitialPoppedRoute???
Address popRoute = SipFactoryImpl.getInstance().createAddress(routes[0]);
request.setPoppedRoute(popRoute);
if (m_logger.isLoggable(Level.FINEST)) {
m_logger.log(Level.FINEST, "AR returned internal route {0}. Setting popped route. Continuing.", new Object[] {popRoute});
}
return false;
} else {
// External routes indicated; push in reversed order
pushRoutesReversed(request,routes);
if (m_logger.isLoggable(Level.FINEST)) {
m_logger.log(Level.FINEST, "AR returned external routes {0}. Routing externally", new Object[] {routes});
}
return true;
}
case ROUTE_BACK:
SipURIImpl routebackUri = (SipURIImpl) DialogManager.getInstance().getVipSipUri();
routebackUri.setLrParam(true);
Address routeBackAddress = SipFactoryImpl.getInstance().createAddress(routebackUri);
routeBackAddress.setParameter(AR_STATE, new ArInfo(info.getStateInfo(), region).toString());
String hashKey = request.getBeKeyFromSession();
URIImpl uriImpl = (URIImpl) routeBackAddress.getURI();
if(uriImpl != null){
uriImpl.encodeBeKey(hashKey);
}
request.pushRoute(routeBackAddress);
pushRoutesReversed(request, routes);
if (m_logger.isLoggable(Level.FINEST)) {
m_logger.log(Level.FINEST, "AR indicated routeback with external routes {0}. Routing externally", new Object[] {routes});
}
return true;
default: // NO_ROUTE
// if (m_logger.isLoggable(Level.FINEST)) {
// m_logger.log(Level.FINEST, "AR indicated no route {0}. Continuing", new Object[] {mod});
// }
return false;
}
} catch (ServletParseException e) {
m_logger.log(Level.SEVERE, e.getMessage(), e);
return true;
}
}
private boolean isInternal(String aRoute) throws ServletParseException {
// precondition: aRoute != null
AddressImpl adr = (AddressImpl)SipFactoryImpl.getInstance().createAddress(aRoute);
return adr.isLocal();
}
private void pushRoutesReversed(SipServletRequestImpl aRequest,
String[] aRoutes) throws ServletParseException {
for (int i = aRoutes.length-1; i >= 0; i--) {
if (aRoutes[i] != null) {
aRequest.pushRoute(
SipFactoryImpl.getInstance().createAddress(aRoutes[i]));
}
}
}
private void sendResponse(SipServletRequestImpl request,
int code) {
SipServletResponseImpl response =
request.createTerminatingResponse(code);
response.sendError(); // fix for 1816 -- call sendError() instead of doing direct pop dispatch.
}
private void sendARFailed(SipServletRequestImpl request) {
//sendResponse(request, SipServletResponseImpl.SC_SERVER_INTERNAL_ERROR,
//"Application Router error");
sendResponse(request, SipServletResponseImpl.SC_SERVER_INTERNAL_ERROR);
}
/**
* The method will pop up and invoke the next <code>Dispatcher</code>.
*
* @param response
* A <code>SipServletResponseImpl</code>.
*/
public void dispatch(SipServletResponseImpl response) {
// The transaction layer populates the dispatchers
// for the request, so here we pop the dispatchers
try {
response.popDispatcher().dispatch(response);
} catch (Exception ex) {
m_logger.log(Level.SEVERE, "Unhandled exception ", ex);
}
}
private void dispatchViaStatelessProxy(SipServletRequestImpl request) {
// Internal stateless proxy, decrease Max-Forwards by one.
int maxForwards = request.getMaxForwards();
--maxForwards;
if (maxForwards <= 0) {
SipServletResponseImpl response = request.createTerminatingResponse(
SipServletResponseImpl.SC_TOO_MANY_HOPS);
if (response != null) {
dispatch(response);
}
return;
}
if (m_logger.isLoggable(Level.FINER)){
m_logger.log(Level.FINER,
"ApplicationDispatcher: dispatching " + request.getMethod() +
" request via internal stateless proxy. The Max-forwards " +
"header value is decreased by one.");
}
request.setMaxForwards(maxForwards);
SipSessionBase session = request.getSessionImpl();
if (session != null){
session.access(); // modify the access times
}
Dispatcher dispatcher = request.popDispatcher();
dispatcher.dispatch(request);
}
/**
* 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) {
m_contextMapping.put(applicationName, servletDispatcher);
// Notify the application router
ApplicationRouterSelector.getInstance().addServletDispatcher(applicationName, servletDispatcher);
}
/**
* Deregisters a Servlet Dispatcher for a specified application
*
* @param applicationName
*/
public void removeServletDispatcher(String applicationName) {
ServletDispatcher sd = m_contextMapping.remove(applicationName);
// Notify the application router
ApplicationRouterSelector.getInstance().removeServletDispatcher(applicationName);
}
// Helper method for serialization
public ServletDispatcher getServletDispatcher(String applicationName) {
return m_contextMapping.get(applicationName);
}
/**
* Returns the SipServlet of the application name and SipSession handler
*
* @param applicationName
* the application name
* @param handler
* the SipSession handler
* @return SipServlet of the application name and SipSession handler
*/
public Servlet getHandler(String applicationName, String handler) {
ServletDispatcher disp = m_contextMapping.get(applicationName);
if (disp != null) {
return disp.getHandler(handler);
}
return null;
}
/**
* @param request
*/
public void next(SipServletRequestImpl request) {
LayerHelper.next(request, this, m_nextLayer);
}
/**
* @param response
*/
public void next(SipServletResponseImpl response) {
LayerHelper.next(response, this, m_nextLayer);
}
/**
* @param layer
*/
public void registerNext(Layer layer) {
m_nextLayer = layer;
}
/**
* Return an instance of <code>ApplicationDispatcher</code>.
*
* @return A <code>ApplicationDispatcher</code>.
*/
public static ApplicationDispatcher getInstance() {
return m_singletonInstance;
}
/**
* 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) {
ApplicationRouterSelector.getInstance().setApplicationRouterClass(className);
}
/**
* Push a service dispatcher on top of the application stack.
*
* @param request
* On which the service dispatcher will be pushed.
*/
// public void pushServiceDispatcher(SipServletRequestImpl request) {
// request.pushApplicationDispatcher(m_serviceDispatcher);
// }
/**
* Return descriptive information about this Valve implementation.
*/
public String getInfo() {
return (APPLICATION_DISPATCHER);
}
public void logActiveCaches() {
for (ServletDispatcher sd : m_contextMapping.values()) {
sd.logActiveCaches();
}
}
}