/*
* 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 java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.concurrent.ExecutorService;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.sip.ServerTransaction;
import javax.sip.SipProvider;
import javax.sip.message.Request;
import javax.sip.message.Response;
import org.apache.catalina.Wrapper;
import org.apache.log4j.Logger;
import org.mobicents.servlet.sip.SipFactories;
import org.mobicents.servlet.sip.core.SipApplicationDispatcher;
import org.mobicents.servlet.sip.core.SipApplicationDispatcherImpl;
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.message.SipServletMessageImpl;
import org.mobicents.servlet.sip.message.SipServletRequestImpl;
import org.mobicents.servlet.sip.message.SipServletResponseImpl;
import org.mobicents.servlet.sip.security.SipSecurityUtils;
import org.mobicents.servlet.sip.startup.SipContext;
/**
* @author <A HREF="mailto:jean.deruelle@gmail.com">Jean Deruelle</A>
*
*/
public abstract class MessageDispatcher {
private static transient Logger logger = Logger.getLogger(MessageDispatcher.class);
public static final String ROUTE_PARAM_DIRECTIVE = "directive";
public static final String ROUTE_PARAM_PREV_APPLICATION_NAME = "previousappname";
public static final String ROUTE_PARAM_PREV_APP_ID = "previousappid";
/*
* This parameter is to know which app handled the request
*/
public static final String RR_PARAM_APPLICATION_NAME = "appname";
/*
* This parameter is to know if a record routing proxy app forwarded the request
* It will help us to determine if the next request coming from either side is a subsequent request
*/
public static final String RR_PARAM_PROXY_APP = "proxy";
/*
* This parameter is to know if a servlet application sent a final response
*/
// public static final String FINAL_RESPONSE = "final_response";
/*
* This parameter is to know if a servlet application has generated its own application key
*/
public static final String GENERATED_APP_KEY = "gen_app_key";
/*
* This parameter is to know the application id
*/
public static final String APP_ID = "app_id";
/*
* This parameter is to know when an app was not deployed and couldn't handle the request
* used so that the response doesn't try to call the app not deployed
*/
public static final String APP_NOT_DEPLOYED = "appnotdeployed";
/*
* This parameter is to know when no app was returned by the AR when doing the initial selection process
* used so that the response is forwarded externally directly
*/
public static final String NO_APP_RETURNED = "noappreturned";
/*
* This parameter is to know when the AR returned an external route instead of an app
*/
public static final String MODIFIER = "modifier";
/*
* Those parameters is to indicate to the SIP Load Balancer, from which node comes from the request
* so that it can stick the Call Id to this node and correctly route the subsequent requests.
*/
public static final String ROUTE_PARAM_NODE_HOST = "node_host";
public static final String ROUTE_PARAM_NODE_PORT = "node_port";
protected SipApplicationDispatcher sipApplicationDispatcher = null;
public MessageDispatcher() {}
// public MessageDispatcher(SipApplicationDispatcher sipApplicationDispatcher) {
// this.sipApplicationDispatcher = sipApplicationDispatcher;
// }
/**
*
* @param errorCode
* @param transaction
* @param request
* @param sipProvider
*/
public static void sendErrorResponse(int errorCode,
ServerTransaction transaction, Request request,
SipProvider sipProvider) {
try{
Response response=SipFactories.messageFactory.createResponse
(errorCode,request);
if (transaction!=null) {
transaction.sendResponse(response);
} else {
sipProvider.sendResponse(response);
}
} catch (Exception e) {
logger.error("Problem while sending the error response to the following request "
+ request.toString(), e);
}
}
protected static SipApplicationSessionKey makeAppSessionKey(SipContext sipContext, SipServletRequestImpl sipServletRequestImpl, String applicationName) throws DispatcherException {
String appGeneratedKey = null;
Method appKeyMethod = null;
// Session Key Based Targeted Mechanism is oly for Initial Requests
// see JSR 289 Section 15.11.2 Session Key Based Targeting Mechanism
if(sipServletRequestImpl.isInitial() && sipContext != null) {
appKeyMethod = sipContext.getSipApplicationKeyMethod();
}
if(appKeyMethod != null) {
if(logger.isDebugEnabled()) {
logger.debug("For request target to application " + sipContext.getApplicationName() +
", using the following annotated method to generate the application key " + appKeyMethod);
}
sipServletRequestImpl.setReadOnly(true);
Servlet servlet = null;
// Set the Class Loader to the same that loaded the servlet class to avoid http://code.google.com/p/mobicents/issues/detail?id=700
ClassLoader oldLoader = java.lang.Thread.currentThread().getContextClassLoader();
java.lang.Thread.currentThread().setContextClassLoader(sipContext.getLoader().getClassLoader());
Class methodDeclaringClass = appKeyMethod.getDeclaringClass();
Wrapper sipServletImpl = (Wrapper) sipContext.findChildrenByClassName(methodDeclaringClass.getCanonicalName());
if(sipServletImpl != null) {
try {
servlet = sipServletImpl.allocate();
// http://code.google.com/p/mobicents/issues/detail?id=700 :
// we get the method from servlet class anew because the original method might have been loaded by a different class loader
Method newMethod = servlet.getClass().getMethod(appKeyMethod.getName(), appKeyMethod.getParameterTypes());
appKeyMethod = newMethod;
if(logger.isDebugEnabled()) {
logger.debug("Invoking the application key method " + appKeyMethod.getName() +
", on the following servlet " + methodDeclaringClass.getCanonicalName());
}
} catch (ServletException e) {
throw new DispatcherException(Response.SERVER_INTERNAL_ERROR, "Couldn't allocate the sip servlet to invoke the key annotated method !" ,e);
} catch (SecurityException e) {
throw new DispatcherException(Response.SERVER_INTERNAL_ERROR, "Couldn't allocate the sip servlet to invoke the key annotated method !" ,e);
} catch (NoSuchMethodException e) {
throw new DispatcherException(Response.SERVER_INTERNAL_ERROR, "Couldn't allocate the sip servlet to invoke the key annotated method !" ,e);
} finally {
java.lang.Thread.currentThread().setContextClassLoader(oldLoader);
}
}
try {
appGeneratedKey = (String) appKeyMethod.invoke(servlet, new Object[] {sipServletRequestImpl});
} catch (IllegalArgumentException e) {
throw new DispatcherException(Response.SERVER_INTERNAL_ERROR, "Couldn't invoke the app session key annotated method !" ,e);
} catch (IllegalAccessException e) {
throw new DispatcherException(Response.SERVER_INTERNAL_ERROR, "Couldn't invoke the app session key annotated method !" ,e);
} catch (InvocationTargetException e) {
throw new DispatcherException(Response.SERVER_INTERNAL_ERROR, "A Problem occured while invoking the app session key annotated method !" ,e);
} finally {
sipServletRequestImpl.setReadOnly(false);
if(sipServletImpl != null) {
try {
sipServletImpl.deallocate(servlet);
} catch (ServletException e) {
throw new DispatcherException(Response.SERVER_INTERNAL_ERROR, "Couldn't deallocate the sip servlet to invoke the key annotated method !" ,e);
}
}
java.lang.Thread.currentThread().setContextClassLoader(oldLoader);
}
if(appGeneratedKey == null) {
//JSR 289 Section 18.2.5 @SipApplicationKey Annotation , The container should treat a "null" return
//or an invalid session id as a failure to obtain a key from the application.
//It is recommended that the container create a new SipApplicationSession for the incoming request in such a case.
// throw new IllegalStateException("SipApplicationKey annotated method shoud not return null");
}
if(logger.isDebugEnabled()) {
logger.debug("For request target to application " + sipContext.getApplicationName() +
", following annotated method " + appKeyMethod + " generated the application key : " + appGeneratedKey);
}
}
SipApplicationSessionKey sipApplicationSessionKey = SessionManagerUtil.getSipApplicationSessionKey(
applicationName,
null);
sipApplicationSessionKey.setAppGeneratedKey(appGeneratedKey);
return sipApplicationSessionKey;
}
public static void callServlet(SipServletRequestImpl request) throws ServletException, IOException {
final MobicentsSipSession session = request.getSipSession();
final String sessionHandler = session.getHandler();
final MobicentsSipApplicationSession sipApplicationSessionImpl = session.getSipApplicationSession();
final SipContext sipContext = sipApplicationSessionImpl.getSipContext();
final Wrapper sipServletImpl = (Wrapper) sipContext.findChild(sessionHandler);
if(sipServletImpl != null) {
if(logger.isInfoEnabled()) {
logger.info("Dispatching request " + request.toString() +
" to following App/servlet => " + session.getKey().getApplicationName()+
"/" + session.getHandler() + " on following sip session " + session.getId());
}
final Servlet servlet = sipServletImpl.allocate();
// JBoss-specific CL issue:
// This is needed because usually the classloader here is some UnifiedClassLoader3,
// which has no idea where the servlet ENC is. We will use the classloader of the
// servlet class, which is the WebAppClassLoader, and has ENC fully loaded with
// with java:comp/env/security (manager, context etc)
ClassLoader cl = servlet.getClass().getClassLoader();
Thread.currentThread().setContextClassLoader(cl);
if(!securityCheck(request)) return;
if(logger.isDebugEnabled()) {
logger.debug("Invoking instance " + servlet);
}
try {
servlet.service(request, null);
} finally {
sipServletImpl.deallocate(servlet);
}
} else if(sipContext.getSipRubyController() != null) {
//handling the ruby case
if(logger.isInfoEnabled()) {
logger.info("Dispatching request " + request.toString() +
" to following App/ruby controller => " + request.getSipSession().getKey().getApplicationName()+
"/" + sipContext.getSipRubyController().getName());
}
ClassLoader cl = sipContext.getLoader().getClassLoader();
Thread.currentThread().setContextClassLoader(cl);
sipContext.getSipRubyController().routeSipMessageToRubyApp(sipContext.getServletContext(), request);
} else {
logger.error("no handler found for sip session " + session.getKey() + " and request " + request);
}
}
public static void callServlet(SipServletResponseImpl response) throws ServletException, IOException {
final MobicentsSipSession session = response.getSipSession();
final String sessionHandler = session.getHandler();
final MobicentsSipApplicationSession sipApplicationSessionImpl = session.getSipApplicationSession();
final SipContext sipContext = sipApplicationSessionImpl.getSipContext();
final Wrapper sipServletImpl = (Wrapper) sipContext.findChild(sessionHandler);
if(sipServletImpl == null || sipServletImpl.isUnavailable()) {
if(sipContext.getSipRubyController() != null) {
//handling the ruby case
if(logger.isInfoEnabled()) {
logger.info("Dispatching response " + response.toString() +
" to following App/ruby controller => " + response.getSipSession().getKey().getApplicationName()+
"/" + sipContext.getSipRubyController().getName());
}
ClassLoader cl = sipContext.getLoader().getClassLoader();
Thread.currentThread().setContextClassLoader(cl);
sipContext.getSipRubyController().routeSipMessageToRubyApp(sipContext.getServletContext(), response);
} else {
logger.warn(sessionHandler + " is unavailable, dropping response " + response);
}
} else {
final Servlet servlet = sipServletImpl.allocate();
if(logger.isInfoEnabled()) {
logger.info("Dispatching response " + response.toString() +
" to following App/servlet => " + session.getKey().getApplicationName()+
"/" + session.getHandler() + " on following sip session " + session.getId());
}
try {
servlet.service(null, response);
} finally {
sipServletImpl.deallocate(servlet);
}
}
}
public static boolean securityCheck(SipServletRequestImpl request)
{
MobicentsSipApplicationSession appSession = (MobicentsSipApplicationSession) request.getApplicationSession();
SipContext sipStandardContext = appSession.getSipContext();
boolean authorized = SipSecurityUtils.authorize(sipStandardContext, request);
// This will propagate the identity for the thread and all called components
// TODO: FIXME: This would introduce a dependency on JBoss
// SecurityAssociation.setPrincipal(request.getUserPrincipal());
return authorized;
}
/**
* Responsible for routing and dispatching a SIP message to the correct application
*
* @param sipProvider use the sipProvider to route the message if needed, can be null
* @param sipServletMessage the SIP message to route and dispatch
* @return true if we should continue routing to other applications, false otherwise
* @throws Exception if anything wrong happens
*/
public abstract void dispatchMessage(SipProvider sipProvider, SipServletMessageImpl sipServletMessage) throws DispatcherException;
/**
* This method return an ExecutorService depending on the current concurrency strategy. It can return the
* executor of a sip session, app session or just threadpool executor which doesn't limit concurrent processing
* of requests per app or sip session.
* Since 0.8.1 it always return threadpool executor which doesn't limit concurrent processing since concurrency is achieved through semaphore
*
* @param sipServletMessage the request you put here must have app and sip session associated
* @return
*/
public final ExecutorService getConcurrencyModelExecutorService(
SipContext sipContext, SipServletMessageImpl sipServletMessage) {
return ((SipApplicationDispatcherImpl) this.sipApplicationDispatcher)
.getAsynchronousExecutor();
}
}