/*
* 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.router;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.Serializable;
import java.text.ParseException;
import java.util.HashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import javax.servlet.sip.SipServletRequest;
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 org.apache.log4j.Logger;
/**
* Implementation of the default application router as defined per JSR 289 Appendix C
*
* <p>
* As an application router component is essential for the functioning of the
* container the following application router logic SHOULD be available with
* every container compliant with this specification. The container
* implementations MAY chose to provide a much richer application router
* component. For the purpose of this discussion the application router defined
* in this appendix is termed as Default Application Router (DAR).
* </p>
*
* <p>
* The Application Router and the Container have a simple contract defined by
* the <code>SipApplicationRouter</code> interface. <br />
* The DAR MUST implement all the methods of that interface as described in this
* document.
* <p>
* <b>The DAR Configuration File</b><br />
*
* The DAR works off a simple configuration text file which is modeled as a Java
* properties file:
*
* <ul>
* <li>The properties file MUST be made available to the DAR and the
* location/content of this file MUST be accessible from a hierarchical URI
* which itself is to be supplied as a system property "javax.servlet.sip.dar"
* </li>
* <li>The propeties file has a simple format in which the name of the property
* is the SIP method and the value is a simple comma separated stringified value
* for the SipRouterInfo object.
*
* <pre>
* INVITE: (sip-router-info-1), (sip-router-info-2)..
* SUBSCRIBE: (sip-router-info-3), (sip-router-info-4)..
* </pre>
*
* </li>
* <li> The properties file is first read by the DAR when the
* <code>init()</code> is first called on the DAR. The arguments passed in the
* <code>init()</code> are ignored. </li>
*
* <li> The properties file is refreshed each time
* <code>applicationDeployed()</code> or <code>applicationUndeployed()</code>
* is called, similar to init the argument of these two invocations are ignored,
* these callbacks act just as a trigger to read the file afresh. </li>
* </ul>
* </p>
* <p>
* The sip-router-info data that goes in the properties file is a stringified
* version of the SipRouterInfo object. It consists of the following information :
* <ul>
* <li>The name of the application as known to the container</li>
* <li>The identity of the subscriber that the DAR returns. It can return any
* header in the SIP request using the DAR directive DAR:<i>SIP_HEADER</i> e.g
* "DAR:From" would return the sip uri in From header. Or alternatively it can
* return any string.</li>
*
* <li>The routing region, one of the strings "ORIGINATING", "TERMINATING" or
* "NEUTRAL"</li>
* <li>A SIP URI indicating the route as returned by the application route, it
* can be an empty string. </li>
* <li>A route modifier which can be any one of the strings "ROUTE",
* "NO_ROUTE", or "CLEAR_ROUTE" </li>
* <li>A string representing stateInfo. As stateInfo is for application
* router's internal use only, what goes in this is up to the individual DAR
* implementations. As a hint the stateInfo could contain the index into the
* list of sip-router-info that was returned last. </li>
* </ul>
*
* </p>
* <p>
* Following is an example of the DAR configuration file:
*
* <pre>
* INVITE: ("OriginatingCallWaiting", "DAR:From", "ORIGINATING", "",
* "NO_ROUTE", "0"), ("CallForwarding", "DAR:To", "TERMINATING", "",
* NO_ROUTE", "1")
* </pre>
*
* In this example the DAR is setup to invoke two applications on INVITE request
* one each in the originating and the terminating half. The applications are
* identified by their names as defined in the application deployment
* descriptors and used here. The subscriber identity returned in this case is
* the URI from the From and To header respectively for the two applications.
* The DAR does not return any route to the container and maintains the
* invocation state in the stateInfo as the index of the last application in the
* list.
*
* <p>
* <b>The DAR Operation</b><br />
* The key interaction point between the Container and the Application Router is
* the method
*
* <pre>
* SipApplicationRouterInfo getNextApplication(SipServletRequest initialRequest,
* SipApplicationRoutingRegion region,
* SipApplicationRoutingDirective directive, java.lang.String stateInfo);
* </pre>
*
* This method is invoked when an initial request is received by the container.
* When this method is invoked on DAR it will make use of the stateInfo and the
* initialRequest parameters and find out what SIP method is in the request.
* Next it will create the object <code>
* SipApplicationRouterInfo</code> from
* the sip-router-info information in the properties file, starting from the
* first in the list. The stateInfo could contain the index of the last
* sip-router-info returned so on next invocation of getNextApplication the DAR
* proceeds to the next sip-router- info in the list. The order of declaration
* of sip-router-info becomes the priority order of invocation.
*
* <p>
* As you would notice this is a minimalist application router with no
* processing logic besides the declaration of the application order. It is
* expected that in the real world deployments the application router shall play
* an extremely important role in application orchestration and composition and
* shall make use of complex rules and diverse data repositories. The DAR is
* specified as being a very simple implementation that shall be available as
* part of the reference implementation of this specification and can be used in
* place of an application router.
*
* </p>
*/
public class DefaultApplicationRouter implements SipApplicationRouter, ManageableApplicationRouter{
// the logger
private static Logger log = Logger.getLogger(DefaultApplicationRouter.class);
//the prefix used in the dar configuration file to specify the subscriber URI to use
//when reusing the information from one header
private static final String DAR_SUSCRIBER_PREFIX = "DAR:";
private static final String FROM = "From";
private static final String TO = "To";
private static final int DAR_SUSCRIBER_PREFIX_LENGTH = DAR_SUSCRIBER_PREFIX.length();
private static final String METHOD_WILDCARD = "ALL";
//the parser for the properties file
private DefaultApplicationRouterParser defaultApplicationRouterParser;
//Applications deployed within the container
Set<String> containerDeployedApplicationNames = null;
//List of applications defined in the defautl application router properties file
Map<String, List<DefaultSipApplicationRouterInfo>> defaultSipApplicationRouterInfos;
/**
* Default Constructor
*/
public DefaultApplicationRouter() {
containerDeployedApplicationNames = new HashSet<String>();
defaultApplicationRouterParser = new DefaultApplicationRouterParser();
defaultSipApplicationRouterInfos = new ConcurrentHashMap<String, List<DefaultSipApplicationRouterInfo>>();
}
/**
* {@inheritDoc}
*/
public void applicationDeployed(List<String> newlyDeployedApplicationNames) {
init();
synchronized (containerDeployedApplicationNames) {
containerDeployedApplicationNames.addAll(newlyDeployedApplicationNames);
}
}
/**
* {@inheritDoc}
*/
public void applicationUndeployed(List<String> undeployedApplicationNames) {
init();
synchronized (containerDeployedApplicationNames) {
containerDeployedApplicationNames.removeAll(undeployedApplicationNames);
}
}
/**
* {@inheritDoc}
*/
public void destroy() {
synchronized (containerDeployedApplicationNames) {
containerDeployedApplicationNames.clear();
}
}
/**
* {@inheritDoc}
*/
public SipApplicationRouterInfo getNextApplication(
SipServletRequest initialRequest,
SipApplicationRoutingRegion region,
SipApplicationRoutingDirective directive,
SipTargetedRequestInfo targetedRequestInfo,
Serializable stateInfo) {
// Minimalist application router implementation with no processing logic
// besides the declaration of the application order as specified in JSR 289 - Appendix C
SipApplicationRouterInfo sipApplicationRouterInfo = null;
if(initialRequest != null) {
if(log.isDebugEnabled()) {
log.debug(this + " checking for next application for request " + initialRequest
+ " , region=" + region + " , directive=" + directive +
", targetedRequestInfo="+ targetedRequestInfo + ", stateinfo=" + stateInfo + " with following dar " + defaultApplicationRouterParser.getProperties());
}
List<DefaultSipApplicationRouterInfo> defaultSipApplicationRouterInfoList =
defaultSipApplicationRouterInfos.get(initialRequest.getMethod());
sipApplicationRouterInfo = getNextApplication(initialRequest, stateInfo,
defaultSipApplicationRouterInfoList);
if(sipApplicationRouterInfo == null) {
defaultSipApplicationRouterInfoList =
defaultSipApplicationRouterInfos.get(METHOD_WILDCARD);
sipApplicationRouterInfo = getNextApplication(initialRequest, stateInfo,
defaultSipApplicationRouterInfoList);
}
if(sipApplicationRouterInfo != null) {
return sipApplicationRouterInfo;
}
}
return new SipApplicationRouterInfo(null,null,null,null,null,null);
}
private SipApplicationRouterInfo getNextApplication(
SipServletRequest initialRequest,
Serializable stateInfo,
List<DefaultSipApplicationRouterInfo> defaultSipApplicationRouterInfoList) {
if(defaultSipApplicationRouterInfoList != null && defaultSipApplicationRouterInfoList.size() > 0) {
int previousAppOrder = 0;
if(stateInfo != null) {
previousAppOrder = (Integer) stateInfo;
if(log.isDebugEnabled()) {
log.debug("The previous app order was : " + previousAppOrder);
}
}
ListIterator<DefaultSipApplicationRouterInfo> defaultSipApplicationRouterInfoIt = defaultSipApplicationRouterInfoList.listIterator(previousAppOrder++);
while (defaultSipApplicationRouterInfoIt.hasNext() ) {
DefaultSipApplicationRouterInfo defaultSipApplicationRouterInfo = defaultSipApplicationRouterInfoIt.next();
boolean isApplicationPresentInContainer = false;
synchronized (containerDeployedApplicationNames) {
if(containerDeployedApplicationNames.contains(defaultSipApplicationRouterInfo.getApplicationName())) {
isApplicationPresentInContainer = true;
if(log.isDebugEnabled()) {
log.debug(defaultSipApplicationRouterInfo.getApplicationName() + " is present in the container.");
}
}
}
if(log.isDebugEnabled()) {
log.debug("Route Modifier : " + defaultSipApplicationRouterInfo.getRouteModifier());
}
//if application is deployed in the container or if the intention is to route outside even if the application is not deployed
if(isApplicationPresentInContainer || !SipRouteModifier.NO_ROUTE.equals(defaultSipApplicationRouterInfo.getRouteModifier())) {
//prevents to route to the same application twice in a row
if(initialRequest.getSession(false) == null ||
!defaultSipApplicationRouterInfo.getApplicationName().equals(
initialRequest.getSession(false).getApplicationSession().getApplicationName())) {
String subscriberIdentity = defaultSipApplicationRouterInfo.getSubscriberIdentity();
if(subscriberIdentity.indexOf(DAR_SUSCRIBER_PREFIX) != -1) {
String headerName = subscriberIdentity.substring(
DAR_SUSCRIBER_PREFIX_LENGTH);
if(FROM.equalsIgnoreCase(headerName)) {
subscriberIdentity = initialRequest.getFrom().getURI().toString();
} else if(TO.equalsIgnoreCase(headerName)) {
subscriberIdentity = initialRequest.getTo().getURI().toString();
} else {
subscriberIdentity = initialRequest.getHeader(headerName);
}
}
return new SipApplicationRouterInfo(
defaultSipApplicationRouterInfo.getApplicationName(),
defaultSipApplicationRouterInfo.getRoutingRegion(),
subscriberIdentity,
defaultSipApplicationRouterInfo.getRoutes(),
defaultSipApplicationRouterInfo.getRouteModifier(),
defaultSipApplicationRouterInfo.getOrder());
}
}
}
}
return null;
}
/**
* load the configuration file as defined in appendix C of JSR289
*/
public void init() {
defaultApplicationRouterParser.init();
try {
defaultSipApplicationRouterInfos = defaultApplicationRouterParser.parse();
} catch (ParseException e) {
log.fatal("Impossible to parse the default application router configuration file",e);
throw new IllegalArgumentException("Impossible to parse the default application router configuration file",e);
}
}
/**
* {@inheritDoc}
*/
public void init(Properties properties) {
init();
}
/* (non-Javadoc)
* @see org.mobicents.servlet.sip.router.ManageableApplicationRouter#configure(java.lang.Object)
*/
public synchronized void configure(Object configuration) {
if(!(configuration instanceof Properties))
throw new IllegalArgumentException("Configuration for DAR must be of type Properties.");
Properties properties = (Properties) configuration;
try {
defaultSipApplicationRouterInfos =
this.defaultApplicationRouterParser.parse(properties);
} catch (ParseException e1) {
throw new IllegalArgumentException("Failed to parse the new DAR properties", e1);
}
String configFileLocation = defaultApplicationRouterParser.getDarConfigurationFileLocation();
if(configFileLocation.startsWith("file:/")) {
int i = 5;
while(configFileLocation.charAt(i) == '/') i++;
configFileLocation = configFileLocation.substring(i-1);
} else {
log.warn("Can not write persist DAR configuration to " + configFileLocation + ". Make sure you have write permissions and make sure it's a local file. NOTE THAT THE NEW DAR CONFIGURATION IS LOADED AND EFFECTIVE.");
return;
}
if(configFileLocation == null || configFileLocation.length()<1)
throw new IllegalStateException("Configuration file name is empty.");
File configFile = new File(configFileLocation);
FileOutputStream fis = null;
try {
fis = new FileOutputStream(configFile);
properties.store(fis, "Application Router Configuration");
} catch (Exception e) {
throw new IllegalStateException("Failed to store configuration file.", e);
} finally {
if(fis != null) {
try {
fis.close();
} catch (IOException e) {
log.error("fail to close the following file " + configFile.getAbsolutePath(), e);
}
}
}
log.info("Stored DAR configuration in " + configFile.getAbsolutePath());
}
/* (non-Javadoc)
* @see org.mobicents.servlet.sip.router.ManageableApplicationRouter#getCurrentConfiguration()
*/
public synchronized Object getCurrentConfiguration() {
return defaultApplicationRouterParser.getProperties();
}
}