/*
* 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.core.dispatchers;
import gov.nist.javax.sip.SipStackExt;
import gov.nist.javax.sip.header.extensions.JoinHeader;
import gov.nist.javax.sip.header.extensions.ReplacesHeader;
import java.io.IOException;
import java.io.Serializable;
import java.text.ParseException;
import java.util.Iterator;
import java.util.ListIterator;
import javax.servlet.ServletException;
import javax.servlet.sip.SipURI;
import javax.servlet.sip.TelURL;
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.sip.Dialog;
import javax.sip.DialogState;
import javax.sip.ServerTransaction;
import javax.sip.SipException;
import javax.sip.SipProvider;
import javax.sip.address.Address;
import javax.sip.address.URI;
import javax.sip.header.Header;
import javax.sip.header.Parameters;
import javax.sip.header.RouteHeader;
import javax.sip.message.Request;
import javax.sip.message.Response;
import org.apache.log4j.Logger;
import org.mobicents.servlet.sip.JainSipUtils;
import org.mobicents.servlet.sip.SipFactories;
import org.mobicents.servlet.sip.address.GenericURIImpl;
import org.mobicents.servlet.sip.address.RFC2396UrlDecoder;
import org.mobicents.servlet.sip.address.SipURIImpl;
import org.mobicents.servlet.sip.address.TelURLImpl;
import org.mobicents.servlet.sip.core.SipSessionRoutingType;
import org.mobicents.servlet.sip.core.session.MobicentsSipApplicationSession;
import org.mobicents.servlet.sip.core.session.MobicentsSipSession;
import org.mobicents.servlet.sip.core.session.SessionManagerUtil;
import org.mobicents.servlet.sip.core.session.SipApplicationSessionKey;
import org.mobicents.servlet.sip.core.session.SipManager;
import org.mobicents.servlet.sip.core.session.SipSessionKey;
import org.mobicents.servlet.sip.message.SipFactoryImpl;
import org.mobicents.servlet.sip.message.SipServletMessageImpl;
import org.mobicents.servlet.sip.message.SipServletRequestImpl;
import org.mobicents.servlet.sip.message.TransactionApplicationData;
import org.mobicents.servlet.sip.startup.SipContext;
import org.mobicents.servlet.sip.startup.loading.SipServletMapping;
/**
* This class is responsible for implementing the logic for routing an initial request
* according to the following sections of the JSR 289 specification Section
* 15.4.1 Procedure for Routing an Initial Request.
* 15.11 Session Targeting
* 16 Mapping Requests To Servlets
*
* @author <A HREF="mailto:jean.deruelle@gmail.com">Jean Deruelle</A>
*
*/
public class InitialRequestDispatcher extends RequestDispatcher {
private static transient Logger logger = Logger.getLogger(InitialRequestDispatcher.class);
public InitialRequestDispatcher() {}
// public InitialRequestDispatcher(
// SipApplicationDispatcher sipApplicationDispatcher) {
// super(sipApplicationDispatcher);
//
// }
/**
* {@inheritDoc}
*/
public void dispatchMessage(SipProvider sipProvider, SipServletMessageImpl sipServletMessage) throws DispatcherException {
final SipApplicationRouter sipApplicationRouter = sipApplicationDispatcher.getSipApplicationRouter();
final SipFactoryImpl sipFactoryImpl = sipApplicationDispatcher.getSipFactory();
final SipServletRequestImpl sipServletRequest = (SipServletRequestImpl) sipServletMessage;
if(logger.isDebugEnabled()) {
logger.debug("Routing of Initial Request " + sipServletRequest);
}
final Request request = (Request) sipServletRequest.getMessage();
final RouteHeader poppedRoute = sipServletRequest.getPoppedRouteHeader();
if(logger.isDebugEnabled()) {
logger.debug("popped route : " + poppedRoute);
}
MobicentsSipSession sipSessionImpl = sipServletRequest.getSipSession();
//set directive from popped route header if it is present
Serializable stateInfo = null;
SipApplicationRouterInfo applicationRouterInfo = null;
//If request is received from an external SIP entity, directive is set to NEW.
SipApplicationRoutingDirective sipApplicationRoutingDirective = SipApplicationRoutingDirective.NEW;
if(poppedRoute != null) {
Parameters poppedAddress = (Parameters)poppedRoute.getAddress().getURI();
// get the state info associated with the request because it means
// that is has been routed back to the container
String directive = poppedAddress.getParameter(ROUTE_PARAM_DIRECTIVE);
if(directive != null && directive.length() > 0) {
//If request is received from an application, directive is set either implicitly
//or explicitly by the application.
if(logger.isDebugEnabled()) {
logger.debug("directive before the request has been routed back to container : " + directive);
}
sipApplicationRoutingDirective = SipApplicationRoutingDirective.valueOf(
SipApplicationRoutingDirective.class, directive);
String previousAppName = poppedAddress.getParameter(ROUTE_PARAM_PREV_APPLICATION_NAME);
String previousAppId = poppedAddress.getParameter(ROUTE_PARAM_PREV_APP_ID);
if(logger.isDebugEnabled()) {
logger.debug("application name before the request has been routed back to container : " + previousAppName);
logger.debug("application session id before the request has been routed back to container : " + previousAppId);
}
SipContext sipContext = sipApplicationDispatcher.findSipApplication(previousAppName);
SipSessionKey sipSessionKey = SessionManagerUtil.getSipSessionKey(previousAppId, previousAppName, request, false);
sipSessionImpl = sipContext.getSipManager().getSipSession(sipSessionKey, false, sipFactoryImpl, null);
//If request is received from an application, and directive is CONTINUE or REVERSE,
//stateInfo is set to that of the original request that this request is associated with.
stateInfo = sipSessionImpl.getStateInfo();
applicationRouterInfo = sipSessionImpl.getNextSipApplicationRouterInfo();
sipSessionImpl.setNextSipApplicationRouterInfo(null);
sipServletRequest.setSipSessionKey(sipSessionKey);
if(logger.isDebugEnabled()) {
logger.debug("state info before the request has been routed back to container : " + stateInfo);
logger.debug("router info before the request has been routed back to container : " + applicationRouterInfo);
}
}
} else if(sipSessionImpl != null) {
stateInfo = sipSessionImpl.getStateInfo();
sipApplicationRoutingDirective = sipServletRequest.getRoutingDirective();
if(logger.isDebugEnabled()) {
logger.debug("previous state info : " + stateInfo);
}
}
SipApplicationRoutingRegion routingRegion = null;
if(sipSessionImpl != null) {
routingRegion = sipSessionImpl.getRegion();
}
// 15.11.3 Target Session : Encode URI mechanism
// Upon receiving an initial request for processing, a container MUST check the topmost Route header and
// Request-URI (in that order) to see if it contains an encoded URI.
// If it does, the container MUST use the encoded URI to locate the targeted SipApplicationSession object
String targetedApplicationKey = ((Parameters)request.getRequestURI()).getParameter(MobicentsSipApplicationSession.SIP_APPLICATION_KEY_PARAM_NAME);
if(targetedApplicationKey != null) {
targetedApplicationKey = RFC2396UrlDecoder.decode(targetedApplicationKey);
}
SipTargetedRequestInfo targetedRequestInfo = retrieveTargetedApplication(targetedApplicationKey);
if(targetedRequestInfo == null && poppedRoute != null) {
Parameters poppedAddress = (Parameters)poppedRoute.getAddress().getURI();
targetedApplicationKey = poppedAddress.getParameter(MobicentsSipApplicationSession.SIP_APPLICATION_KEY_PARAM_NAME);
targetedRequestInfo = retrieveTargetedApplication(targetedApplicationKey);
}
// 15.11.4 Join and Replaces Targeting Mechanism
SipTargetedRequestInfo joinReplacesTargetedRequestInfo = null;
MobicentsSipSession joinReplacesCorrespondingSession = null;
JoinHeader joinHeader = (JoinHeader)request.getHeader(JoinHeader.NAME);
ReplacesHeader replacesHeader = (ReplacesHeader)request.getHeader(ReplacesHeader.NAME);
if(joinHeader != null || replacesHeader != null) {
if(logger.isDebugEnabled()) {
logger.debug("One Join or Replaces Header has been found : JoinHeader = " + joinHeader + ", ReplacesHeader = " + replacesHeader);
}
// 15.11.4.1 If a container supporting [RFC 3911] or [RFC 3891] receives an initial INVITE request with Join or Replaces header,
// the container must first ensure that the request passes the RFC-defined validation rules.
if(!Request.INVITE.equals(request.getMethod())) {
throw new DispatcherException(Response.BAD_REQUEST, "A Join or Replaces Header cannot be present in a request other than INVITE as per RFC 3911, Section 4 or RFC 3891, Section 3. Check your request " + request);
}
if(joinHeader != null && replacesHeader != null) {
throw new DispatcherException(Response.BAD_REQUEST, "A Join Header and a Replaces Header cannot be present as per RFC 3911, Section 4 in the same request " + request);
}
Dialog joinReplacesDialog = null;
if(joinHeader != null) {
joinReplacesDialog = ((SipStackExt)sipProvider.getSipStack()).getJoinDialog(joinHeader);
}
if(replacesHeader != null) {
joinReplacesDialog = ((SipStackExt)sipProvider.getSipStack()).getReplacesDialog(replacesHeader);
}
if(targetedRequestInfo != null) {
// If no match is found, but the Request-URI in the INVITE corresponds to a conference URI, the UAS MUST ignore the Join header and continue
// processing the INVITE as if the Join header did not exist
} else
//Otherwise if no match is found, the UAS rejects the INVITE and returns a 481 Call/Transaction Does Not Exist response.
if(joinReplacesDialog == null && targetedRequestInfo == null) {
throw new DispatcherException(Response.CALL_OR_TRANSACTION_DOES_NOT_EXIST, "Join/Replaces Header : no match is found as per RFC 3911, Section 4 or RFC 3891, Section 3 in the request " + request);
} else
//Likewise, if the Join/Replaces header field matches a dialog which was not created with an INVITE, the UAS MUST reject the request with a 481 response.
if(((TransactionApplicationData)joinReplacesDialog.getApplicationData()) != null && !Request.INVITE.equals(((TransactionApplicationData)joinReplacesDialog.getApplicationData()).getSipServletMessage().getMethod())) {
throw new DispatcherException(Response.CALL_OR_TRANSACTION_DOES_NOT_EXIST, "Join/Replaces header field matches a dialog which was not created with an INVITE as per RFC 3911, Section 4 or RFC 3891, Section 3 in the request " + request);
} else
// If the Join/Replaces header field matches a dialog which has already terminated, the UA SHOULD decline the request with a 603 Declined response.
if(DialogState.TERMINATED.equals(joinReplacesDialog.getState())) {
throw new DispatcherException(Response.DECLINE, "Join/Replaces header field matches a dialog which has already terminated as per RFC 3911, Section 4 or RFC 3891, Section 3 in the request " + request);
}
//TODO If the initiator of the new INVITE has authenticated successfully as equivalent to the user who is being joined, then the join is authorized
//TODO Alternatively, the Referred-By mechanism [9] defines a mechanism that the UAS can use to verify that a join request was sent on behalf of the other participant in the matched dialog
// 15.11.4.2. For a request that passes the validation step, the container MUST attempt to locate the SipSession object matching
// the tuple from the Join or Replaces header. In locating this SipSession, the container MUST not match a SipSession
// owned by an application that has acted as a proxy with respect to the candidate SipSession.
// It must only match a SipSession for an application that has acted as a UA.
// If the matching SipSession is found, the container Session Targeting must locate the corresponding SipApplicationSession
// object along with the application name of the application that owns the SipApplicationSession.
joinReplacesCorrespondingSession = retrieveSipSession(joinReplacesDialog);
if(joinReplacesCorrespondingSession != null) {
if(logger.isDebugEnabled()) {
logger.debug("the following matching sip session has been found for the Join or Replaces Header " + joinReplacesCorrespondingSession.getKey());
}
// 15.11.4.3. The container MUST populate a SipTargetedRequestInfo object with the type corresponding to whichever header
// appears in the request (e.g. JOIN or REPLACES) and with the application name that owns the identified SipApplicationSession
// and SipSession.
// The container MUST then pass the request to the Application Router’s getNextApplication() method as a targeted request i.e.,
// along with the populated SipTargetedRequestInfo object.
SipTargetedRequestType sipTargetedRequestType = null;
if(joinHeader != null) {
sipTargetedRequestType = SipTargetedRequestType.JOIN;
}
if(replacesHeader != null) {
sipTargetedRequestType = SipTargetedRequestType.REPLACES;
}
joinReplacesTargetedRequestInfo = new SipTargetedRequestInfo(sipTargetedRequestType, joinReplacesCorrespondingSession.getKey().getApplicationName());
} else {
// 15.11.4.4.If no SipSession matching the tuple is found, the container MUST pass the request to the Application Router as an untargeted request, i.e., where the SipTargetedRequestInfo argument to getNextApplication() is null.
if(logger.isDebugEnabled()) {
logger.debug("no matching sip session found for the Join or Replaces Header");
}
}
}
if(joinReplacesTargetedRequestInfo != null) {
targetedRequestInfo = joinReplacesTargetedRequestInfo;
}
// 15.4.1 Routing an Initial request Algorithm
// 15.4.1 Procedure : point 1
//the application router shouldn't modify the request
sipServletRequest.setReadOnly(true);
// if the router info is null, it means that the request comes from the external
if(applicationRouterInfo == null) {
applicationRouterInfo =
sipApplicationRouter.getNextApplication(
sipServletRequest,
routingRegion,
sipApplicationRoutingDirective,
targetedRequestInfo,
stateInfo);
} else if (logger.isDebugEnabled()){
logger.debug("application name selected by the AR before re routing this request back to the container " + applicationRouterInfo.getNextApplicationName());
}
sipServletRequest.setReadOnly(false);
// 15.4.1 Procedure : point 2
if(checkRouteModifier(applicationRouterInfo, sipServletRequest)) {
return;
}
// 15.4.1 Procedure : point 3
if(applicationRouterInfo.getNextApplicationName() == null) {
dispatchOutsideContainer(sipServletRequest);
} else {
dispatchInsideContainer(sipProvider, applicationRouterInfo, sipServletRequest, sipFactoryImpl, joinReplacesCorrespondingSession);
}
}
/**
* Dispatch a request inside the container based on the information returned by the AR
* @param sipProvider the sip Provider on which the request has been received
* @param applicationRouterInfo information returned by the AR
* @param sipServletRequest the request to dispatch
* @param sipFactoryImpl the sip factory
* @param joinReplacesSipSession the potential corresponding Join/Replaces SipSession previously found, can be null
* @throws DispatcherException a problem occured while dispatching the request
*/
private void dispatchInsideContainer(final SipProvider sipProvider, final SipApplicationRouterInfo applicationRouterInfo, final SipServletRequestImpl sipServletRequest, final SipFactoryImpl sipFactoryImpl, MobicentsSipSession joinReplacesSipSession) throws DispatcherException {
final String nextApplicationName = applicationRouterInfo.getNextApplicationName();
if(logger.isInfoEnabled()) {
logger.info("Dispatching the request event to " + nextApplicationName);
}
final Request request = (Request) sipServletRequest.getMessage();
sipServletRequest.setCurrentApplicationName(nextApplicationName);
final SipContext sipContext = sipApplicationDispatcher.findSipApplication(nextApplicationName);
// follow the procedures of Chapter 16 to select a servlet from the application.
//no matching deployed apps
if(sipContext == null) {
// the app returned by the Application Router returned an app
// that is not currently deployed, sends a 500 error response as was discussed
// in the JSR 289 EG, (for reference thread "Questions regarding AR" initiated by Uri Segev)
// and stops processing.
throw new DispatcherException(Response.SERVER_INTERNAL_ERROR, "No matching deployed application has been found !");
}
final SipManager sipManager = (SipManager)sipContext.getManager();
// subscriber URI should be set before calling makeAppSessionKey method, see Issue 750
// http://code.google.com/p/mobicents/issues/detail?id=750
try {
URI subscriberUri = SipFactories.addressFactory.createURI(applicationRouterInfo.getSubscriberURI());
javax.servlet.sip.URI jainSipSubscriberUri = null;
if(subscriberUri instanceof javax.sip.address.SipURI) {
jainSipSubscriberUri= new SipURIImpl((javax.sip.address.SipURI)subscriberUri);
} else if (subscriberUri instanceof javax.sip.address.TelURL) {
jainSipSubscriberUri = new TelURLImpl((javax.sip.address.TelURL)subscriberUri);
} else {
jainSipSubscriberUri = new GenericURIImpl(subscriberUri);
}
sipServletRequest.setSubscriberURI(jainSipSubscriberUri);
} catch (ParseException pe) {
throw new DispatcherException(Response.SERVER_INTERNAL_ERROR, "Impossible to parse the subscriber URI returned by the Application Router "
+ applicationRouterInfo.getSubscriberURI() +
", please put one of DAR:<HeaderName> with Header containing a valid URI or an exlicit valid URI ", pe);
}
MobicentsSipApplicationSession sipApplicationSession = null;
if(joinReplacesSipSession != null && nextApplicationName.equals(joinReplacesSipSession.getKey().getApplicationName())) {
// 15.11.4.5. If the Application Router returns an application name that matches the application name found in step 15.11.4.2,
// then the container must create a SipSession object and associate it with the SipApplicationSession identified in step 2.
final JoinHeader joinHeader = (JoinHeader)request.getHeader(JoinHeader.NAME);
final ReplacesHeader replacesHeader = (ReplacesHeader)request.getHeader(ReplacesHeader.NAME);
String headerName = null;
if(joinHeader != null) {
headerName = JoinHeader.NAME;
} else if (replacesHeader != null) {
headerName = ReplacesHeader.NAME;
}
sipApplicationSession = joinReplacesSipSession.getSipApplicationSession();
if(logger.isDebugEnabled()) {
logger.debug("Reusing the application session from the Join/Replaces " + sipApplicationSession.getId());
}
SipApplicationSessionKey sipApplicationSessionKey = makeAppSessionKey(
sipContext, sipServletRequest, nextApplicationName);
sipContext.getSipSessionsUtil().addCorrespondingSipApplicationSession(sipApplicationSessionKey, sipApplicationSession.getKey(), headerName);
} else {
// 15.11.4.6. If the Application Router returns an application name that does not match the application name found in step 15.11.4.2,
// then the container invokes the application using its normal procedure, creating a new SipSession and SipApplicationSession.
SipApplicationSessionKey sipApplicationSessionKey = makeAppSessionKey(
sipContext, sipServletRequest, nextApplicationName);
sipApplicationSession = sipManager.getSipApplicationSession(
sipApplicationSessionKey, true);
}
//sip application session association
final MobicentsSipApplicationSession appSession = sipApplicationSession;
//sip session association
final SipSessionKey sessionKey = SessionManagerUtil.getSipSessionKey(appSession.getKey().getId(), nextApplicationName, request, false);
final MobicentsSipSession sipSessionImpl = sipManager.getSipSession(sessionKey, true, sipFactoryImpl, appSession);
sipServletRequest.setSipSessionKey(sessionKey);
if(joinReplacesSipSession != null && nextApplicationName.equals(joinReplacesSipSession.getKey().getApplicationName())) {
final JoinHeader joinHeader = (JoinHeader)request.getHeader(JoinHeader.NAME);
final ReplacesHeader replacesHeader = (ReplacesHeader)request.getHeader(ReplacesHeader.NAME);
String headerName = null;
if(joinHeader != null) {
headerName = JoinHeader.NAME;
} else if (replacesHeader != null) {
headerName = ReplacesHeader.NAME;
}
// 15.11.4.5. If the Application Router returns an application name that matches the application name found in step 15.11.4.2,
// then the container must create a SipSession object and associate it with the SipApplicationSession identified in step 2.
// The association of this newly created SipSession with the one found in step 15.11.4.2 is made available to the application
// through the SipSessionsUtil.getCorrespondingSipSession(SipSession session, String headerName) method.
sipContext.getSipSessionsUtil().addCorrespondingSipSession(sipSessionImpl, joinReplacesSipSession, headerName);
}
// set the request's stateInfo to result.getStateInfo(), region to result.getRegion(), and URI to result.getSubscriberURI().
sipSessionImpl.setStateInfo(applicationRouterInfo.getStateInfo());
sipSessionImpl.setRoutingRegion(applicationRouterInfo.getRoutingRegion());
sipServletRequest.setRoutingRegion(applicationRouterInfo.getRoutingRegion());
sipSessionImpl.setSipSubscriberURI(sipServletRequest.getSubscriberURI());
final InitialDispatchTask dispatchTask = new InitialDispatchTask(sipServletRequest, sipProvider);
// if the flag is set we bypass the executor
if(sipApplicationDispatcher.isBypassRequestExecutor()) {
dispatchTask.dispatchAndHandleExceptions();
} else {
getConcurrencyModelExecutorService(sipContext, sipServletRequest).execute(dispatchTask);
}
}
/**
* Dispatch a request outside the container
* @param sipServletRequest request
* @throws DispatcherException problem occured when dispatching the request outside the container
*/
private void dispatchOutsideContainer(SipServletRequestImpl sipServletRequest) throws DispatcherException {
final Request request = (Request) sipServletRequest.getMessage();
if(logger.isInfoEnabled()) {
logger.info("Dispatching the request event outside the container");
}
//check if the request point to another domain
if(request.getRequestURI() instanceof TelURL) {
throw new DispatcherException(Response.SERVER_INTERNAL_ERROR, "cannot dispatch a request with a tel url request uri outside the container ");
}
javax.sip.address.SipURI sipRequestUri = (javax.sip.address.SipURI)request.getRequestURI();
String host = sipRequestUri.getHost();
int port = sipRequestUri.getPort();
String transport = JainSipUtils.findTransport(request);
boolean isAnotherDomain = sipApplicationDispatcher.isExternal(host, port, transport);
ListIterator<String> routeHeaders = sipServletRequest.getHeaders(RouteHeader.NAME);
if(isAnotherDomain || routeHeaders.hasNext()) {
try {
forwardRequestStatefully(sipServletRequest, SipSessionRoutingType.PREVIOUS_SESSION, SipRouteModifier.NO_ROUTE);
} catch (Exception e) {
throw new DispatcherException(Response.SERVER_INTERNAL_ERROR, "Unexpected Exception while trying to forward statefully the following subsequent request " + request, e);
}
} else {
// the Request-URI does not point to another domain, and there is no Route header,
// the container should not send the request as it will cause a loop.
// Instead, the container must reject the request with 404 Not Found final response with no Retry-After header.
throw new DispatcherException(Response.NOT_FOUND, "the Request-URI does not point to another domain, and there is no Route header," +
"the container should not send the request as it will cause a loop. " +
"Instead, the container must reject the request with 404 Not Found final response with no Retry-After header. You may want to check your dar configuration file to see if the request can be handled or make sure you use the correct Application Router jar.");
}
}
/**
* JSR 289 Section 15.4.1 Procedure : point 2
* Check the result.getRouteModifier() :
* If result.getRouteModifier() is ROUTE, then get the routes using result.getRoutes()
* If the first returned route is external (does not belong to this container) then
* push all of the routes on the Route header stack of the request and send the request externally.
* Note that the first returned route becomes the top route header of the request.
*
* If the first returned route is internal then the container MUST make it available
* to the applications via the SipServletRequest.getPoppedRoute() method and ignore the remaining ones, if any.
* This allows the AR modify the popped route before passing it to the application.
*
* If result.getRouteModifier() is ROUTE_BACK then push a route back to the container followed
* by the external routes obtained from result.getRoutes() and send the request externally.
* Note that the container route SHOULD include AR state encoded as a route parameter in order for the AR to continue processing the application chain once the request returns back to the container.
*
* If result.getRouteModifier() is NO_ROUTE then disregard the result.getRoutes() and proceed.
*
* @param applicationRouterInfo the applicationRouterInfo returned by the AR
* @param sipServletRequest the request
* @return true if the request has been routed outside, false otherwise
* @throws DispatcherException a proble occured while checking the information returned by the AR
*/
private final boolean checkRouteModifier(SipApplicationRouterInfo applicationRouterInfo, SipServletRequestImpl sipServletRequest) throws DispatcherException {
final SipRouteModifier sipRouteModifier = applicationRouterInfo.getRouteModifier();
if(logger.isDebugEnabled()) {
logger.debug("the AR returned the following sip route modifier" + sipRouteModifier);
}
if(sipRouteModifier != null) {
final Request request = (Request) sipServletRequest.getMessage();
final String[] routes = applicationRouterInfo.getRoutes();
switch(sipRouteModifier) {
// ROUTE modifier indicates that SipApplicationRouterInfo.getRoute() returns a valid route,
// it is up to container to decide whether it is external or internal.
case ROUTE :
Address routeAddress = null;
RouteHeader applicationRouterInfoRouteHeader = null;
try {
routeAddress = SipFactories.addressFactory.createAddress(routes[0]);
applicationRouterInfoRouteHeader = SipFactories.headerFactory.createRouteHeader(routeAddress);
if(sipApplicationDispatcher.isRouteExternal(applicationRouterInfoRouteHeader)) {
// push all of the routes on the Route header stack of the request and
// send the request externally
for (int i = routes.length-1 ; i >= 0; i--) {
routeAddress = SipFactories.addressFactory.createAddress(routes[i]);
URI routeURI = routeAddress.getURI();
if(routeURI.isSipURI()) {
((javax.sip.address.SipURI)routeURI).setLrParam();
}
Header routeHeader = SipFactories.headerFactory.createRouteHeader(routeAddress);
// Fix for Issue 280 provided by eelcoc
try {
request.addFirst(routeHeader);
} catch (SipException e) {
throw new DispatcherException(Response.SERVER_INTERNAL_ERROR, "Unexpected Exception while trying to add a route header to route externally the following initial request " + request, e);
}
}
if(logger.isDebugEnabled()) {
logger.debug("Routing the request externally " + sipServletRequest );
}
request.setRequestURI(SipFactories.addressFactory.createURI(routes[0]));
try {
forwardRequestStatefully(sipServletRequest, null, sipRouteModifier);
return true;
} catch (Exception e) {
throw new DispatcherException(Response.SERVER_INTERNAL_ERROR, "Unexpected Exception while trying to forward statefully the following initial request " + request, e);
}
} else {
// the container MUST make the route available to the applications
// via the SipServletRequest.getPoppedRoute() method.
sipServletRequest.setPoppedRoute(applicationRouterInfoRouteHeader);
break;
}
} catch (ParseException e) {
// the AR returned an empty string route or a bad route
// this shouldn't happen if the route modifier is ROUTE, processing is stopped
throw new DispatcherException(Response.SERVER_INTERNAL_ERROR, "Impossible to parse the route returned by the application router " +
"into a compliant address" ,e);
}
// NO_ROUTE indicates that application router is not returning any route
// and the SipApplicationRouterInfo.getRoute() value if any should be disregarded.
case NO_ROUTE :
//nothing to do here
//disregard the result.getRoute() and proceed. Specifically the SipServletRequest.getPoppedRoute()
//returns the same value as before the invocation of application router.
break;
// ROUTE_BACK directs the container to push its own route before
// pushing the external routes obtained from SipApplicationRouterInfo.getRoutes().
case ROUTE_BACK :
try {
// Push container Route, pick up the first outbound interface
SipURI sipURI = sipApplicationDispatcher.getOutboundInterfaces().get(0);
sipURI.setParameter("modifier", "route_back");
Header routeHeader = SipFactories.headerFactory.createHeader(RouteHeader.NAME, sipURI.toString());
// Fix for Issue 280 provided by eelcoc
try {
request.addFirst(routeHeader);
// push all of the routes on the Route header stack of the request and
// send the request externally
for (int i = routes.length-1 ; i >= 0; i--) {
routeHeader = SipFactories.headerFactory.createHeader(RouteHeader.NAME, routes[i]);
request.addFirst(routeHeader);
}
} catch (SipException e) {
throw new DispatcherException(Response.SERVER_INTERNAL_ERROR, "Unexpected Exception while trying to add a route header to route externally the following initial request " + request, e);
}
if(logger.isDebugEnabled()) {
logger.debug("Routing the request externally " + sipServletRequest );
}
try {
forwardRequestStatefully(sipServletRequest, null, sipRouteModifier);
return true;
} catch (Exception e) {
throw new DispatcherException(Response.SERVER_INTERNAL_ERROR, "Unexpected Exception while trying to forward statefully the following subsequent request " + request, e);
}
} catch (ParseException e) {
// the AR returned an empty string route or a bad route
// this shouldn't happen if the route modifier is ROUTE, processing is stopped
throw new DispatcherException(Response.SERVER_INTERNAL_ERROR, "Impossible to parse the route returned by the application router " +
"into a compliant address" ,e);
}
}
}
return false;
}
/**
* Section 15.11.3 Encode URI Mechanism
* The container MUST use the encoded URI to locate the targeted SipApplicationSession object.
* If a valid SipApplicationSession is found, the container must determine
* the name of the application that owns the SipApplicationSession object.
*
* @param targetedApplicationKey the encoded URI parameter
* @return null if no corresponding SipApplicationSession could be found or
* if the param is null, the SipTargetedRequestInfo completed otherwise
*/
private final SipTargetedRequestInfo retrieveTargetedApplication(
String targetedApplicationKey) {
if( targetedApplicationKey != null &&
targetedApplicationKey.length() > 0) {
SipApplicationSessionKey targetedApplicationSessionKey;
try {
targetedApplicationSessionKey = SessionManagerUtil.parseSipApplicationSessionKey(targetedApplicationKey);
} catch (ParseException e) {
logger.error("Couldn't parse the targeted application key " + targetedApplicationKey, e);
return null;
}
SipContext sipContext = sipApplicationDispatcher.findSipApplication(targetedApplicationSessionKey.getApplicationName());
if(sipContext != null && ((SipManager)sipContext.getManager()).getSipApplicationSession(targetedApplicationSessionKey, false) != null) {
return new SipTargetedRequestInfo(SipTargetedRequestType.ENCODED_URI, targetedApplicationSessionKey.getApplicationName());
}
}
return null;
}
/**
* Try to find a matching Sip Session to a given dialog
* @param dialog the dialog to find the session
* @return the matching session, null if not session have been found
*/
private MobicentsSipSession retrieveSipSession(Dialog dialog) {
Iterator<SipContext> iterator = sipApplicationDispatcher.findSipApplications();
while(iterator.hasNext()) {
SipContext sipContext = iterator.next();
SipManager sipManager = sipContext.getSipManager();
Iterator<MobicentsSipSession> sipSessionsIt= sipManager.getAllSipSessions();
while (sipSessionsIt.hasNext()) {
MobicentsSipSession mobicentsSipSession = (MobicentsSipSession) sipSessionsIt
.next();
SipSessionKey sessionKey = mobicentsSipSession.getKey();
if(sessionKey.getCallId().trim().equals(dialog.getCallId().getCallId())) {
if(logger.isDebugEnabled()) {
logger.debug("found session with the same Call Id " + sessionKey + ", to Tag " + sessionKey.getToTag());
logger.debug("dialog localParty = " + dialog.getLocalParty().getURI() + ", localTag " + dialog.getLocalTag());
logger.debug("dialog remoteParty = " + dialog.getRemoteParty().getURI() + ", remoteTag " + dialog.getRemoteTag());
}
if(sessionKey.getFromAddress().equals(dialog.getLocalParty().getURI().toString()) && sessionKey.getToAddress().equals(dialog.getRemoteParty().getURI().toString()) &&
sessionKey.getFromTag().equals(dialog.getLocalTag()) && sessionKey.getToTag().equals(dialog.getRemoteTag())) {
if(mobicentsSipSession.getProxy() == null) {
return mobicentsSipSession;
}
} else if (sessionKey.getFromAddress().equals(dialog.getRemoteParty().getURI().toString()) && sessionKey.getToAddress().equals(dialog.getLocalParty().getURI().toString()) &&
sessionKey.getFromTag().equals(dialog.getRemoteTag()) && sessionKey.getToTag().equals(dialog.getLocalTag())){
if(mobicentsSipSession.getProxy() == null) {
return mobicentsSipSession;
}
}
}
}
}
return null;
}
public static class InitialDispatchTask extends DispatchTask {
InitialDispatchTask(SipServletRequestImpl sipServletRequest, SipProvider sipProvider) {
super(sipServletRequest, sipProvider);
}
public void dispatch() throws DispatcherException {
final SipServletRequestImpl sipServletRequest = (SipServletRequestImpl)sipServletMessage;
final MobicentsSipSession sipSessionImpl = sipServletRequest.getSipSession();
final MobicentsSipApplicationSession appSession = sipSessionImpl.getSipApplicationSession();
final SipContext sipContext = appSession.getSipContext();
final SipManager sipManager = (SipManager)sipContext.getManager();
final Request request = (Request) sipServletRequest.getMessage();
sipContext.enterSipApp(sipServletRequest, null, sipManager, true, true);
try {
sipSessionImpl.setSessionCreatingTransaction(sipServletRequest.getTransaction());
String sipSessionHandlerName = sipSessionImpl.getHandler();
if(sipSessionHandlerName == null || sipSessionHandlerName.length() < 1) {
String mainServlet = appSession.getCurrentRequestHandler();
if(mainServlet != null && mainServlet.length() > 0) {
sipSessionHandlerName = mainServlet;
} else {
SipServletMapping sipServletMapping = sipContext.findSipServletMappings(sipServletRequest);
if(sipServletMapping == null && sipContext.getSipRubyController() == null) {
logger.error("Sending 404 because no matching servlet found for this request ");
sendErrorResponse(Response.NOT_FOUND, (ServerTransaction) sipServletRequest.getTransaction(), request, sipProvider);
return;
} else if(sipServletMapping != null) {
sipSessionHandlerName = sipServletMapping.getServletName();
}
}
if(sipSessionHandlerName!= null) {
try {
sipSessionImpl.setHandler(sipSessionHandlerName);
} catch (ServletException e) {
// this should never happen
throw new DispatcherException(Response.SERVER_INTERNAL_ERROR, "An unexpected servlet exception occured while routing an initial request",e);
}
}
}
try {
callServlet(sipServletRequest);
if(logger.isInfoEnabled()) {
logger.info("Request event dispatched to " + sipContext.getApplicationName());
}
} catch (ServletException e) {
throw new DispatcherException(Response.SERVER_INTERNAL_ERROR, "An unexpected servlet exception occured while routing the following initial request " + request, e);
} catch (IOException e) {
throw new DispatcherException(Response.SERVER_INTERNAL_ERROR, "An unexpected IO exception occured while routing the following initial request " + request, e);
}
} finally {
sipContext.exitSipApp(sipServletRequest, null);
}
//nothing more needs to be done, either the app acted as UA, PROXY or B2BUA. in any case we stop routing
}
}
}