/* * 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; import java.io.Serializable; import java.text.ParseException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import javax.imageio.spi.ServiceRegistry; import javax.management.MBeanRegistration; import javax.management.MBeanServer; import javax.management.ObjectName; import javax.servlet.sip.SipErrorEvent; import javax.servlet.sip.SipErrorListener; import javax.servlet.sip.SipServletRequest; import javax.servlet.sip.SipURI; import javax.servlet.sip.ar.SipApplicationRouter; import javax.servlet.sip.ar.SipApplicationRouterInfo; import javax.servlet.sip.ar.SipApplicationRoutingRegion; import javax.servlet.sip.ar.SipRouteModifier; import javax.servlet.sip.ar.spi.SipApplicationRouterProvider; import javax.sip.ClientTransaction; import javax.sip.Dialog; import javax.sip.DialogTerminatedEvent; import javax.sip.IOExceptionEvent; import javax.sip.ListeningPoint; import javax.sip.RequestEvent; import javax.sip.ResponseEvent; import javax.sip.ServerTransaction; import javax.sip.SipProvider; import javax.sip.TimeoutEvent; import javax.sip.Transaction; import javax.sip.TransactionAlreadyExistsException; import javax.sip.TransactionTerminatedEvent; import javax.sip.TransactionUnavailableException; import javax.sip.address.Address; import javax.sip.address.URI; import javax.sip.header.CSeqHeader; import javax.sip.header.Header; import javax.sip.header.MaxForwardsHeader; import javax.sip.header.Parameters; import javax.sip.header.RouteHeader; import javax.sip.header.ViaHeader; import javax.sip.message.Request; import javax.sip.message.Response; import org.apache.catalina.LifecycleException; import org.apache.log4j.Logger; import org.apache.tomcat.util.modeler.Registry; import org.mobicents.servlet.sip.GenericUtils; import org.mobicents.servlet.sip.JainSipUtils; import org.mobicents.servlet.sip.SipFactories; import org.mobicents.servlet.sip.address.AddressImpl; import org.mobicents.servlet.sip.annotation.ConcurrencyControlMode; import org.mobicents.servlet.sip.core.dispatchers.DispatcherException; import org.mobicents.servlet.sip.core.dispatchers.MessageDispatcher; import org.mobicents.servlet.sip.core.dispatchers.MessageDispatcherFactory; 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.SipServletResponseImpl; import org.mobicents.servlet.sip.message.TransactionApplicationData; import org.mobicents.servlet.sip.proxy.ProxyImpl; import org.mobicents.servlet.sip.router.ManageableApplicationRouter; import org.mobicents.servlet.sip.startup.SipContext; /** * Implementation of the SipApplicationDispatcher interface. * Central point getting the sip messages from the different stacks for a Tomcat Service(Engine), * translating jain sip SIP messages to sip servlets SIP messages, creating a MessageRouter responsible * for choosing which Dispatcher will be used for routing the message and * dispatches the messages. * @author Jean Deruelle */ public class SipApplicationDispatcherImpl implements SipApplicationDispatcher, MBeanRegistration { /** * Timer task that will gather information about congestion control * @author <A HREF="mailto:jean.deruelle@gmail.com">Jean Deruelle</A> */ public class CongestionControlTimerTask implements Runnable { public void run() { if(logger.isDebugEnabled()) { logger.debug("CongestionControlTimerTask now running "); } analyzeQueueCongestionState(); analyzeMemory(); //TODO wait for JDK 6 new OperatingSystemMXBean // analyzeCPU(); } } //the logger private static transient Logger logger = Logger.getLogger(SipApplicationDispatcherImpl.class); //the sip factory implementation, it is not clear if the sip factory should be the same instance //for all applications private SipFactoryImpl sipFactoryImpl = null; //the sip application router responsible for the routing logic of sip messages to //sip servlet applications private SipApplicationRouter sipApplicationRouter = null; //map of applications deployed private Map<String, SipContext> applicationDeployed = null; //map hashes to app names private Map<String, String> mdToApplicationName = null; //map app names to hashes private Map<String, String> applicationNameToMd = null; //List of host names managed by the container private Set<String> hostNames = null; //30 sec private long congestionControlCheckingInterval; protected transient CongestionControlTimerTask congestionControlTimerTask; protected transient ScheduledFuture congestionControlTimerFuture; private Boolean started = Boolean.FALSE; private Lock statusLock = new ReentrantLock(); private SipNetworkInterfaceManager sipNetworkInterfaceManager; private ConcurrencyControlMode concurrencyControlMode; private AtomicLong requestsProcessed = new AtomicLong(0); private AtomicLong responsesProcessed = new AtomicLong(0); private boolean rejectSipMessages = false; private boolean bypassResponseExecutor = false; private boolean bypassRequestExecutor = false; private boolean memoryToHigh = false; private double maxMemory; private int memoryThreshold; private CongestionControlPolicy congestionControlPolicy; int queueSize; private int numberOfMessagesInQueue; private double percentageOfMemoryUsed; // This executor is used for async things that don't need to wait on session executors, like CANCEL requests // or when the container is configured to execute every request ASAP without waiting on locks (no concurrency control) private ThreadPoolExecutor asynchronousExecutor = new ThreadPoolExecutor(4, 32, 90, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()); //used for the congestion control mechanism private ScheduledThreadPoolExecutor congestionControlThreadPool = null; // fatcory for dispatching SIP messages private MessageDispatcherFactory messageDispatcherFactory; /** * */ public SipApplicationDispatcherImpl() { applicationDeployed = new ConcurrentHashMap<String, SipContext>(); mdToApplicationName = new ConcurrentHashMap<String, String>(); applicationNameToMd = new ConcurrentHashMap<String, String>(); sipFactoryImpl = new SipFactoryImpl(this); hostNames = new CopyOnWriteArraySet<String>(); sipNetworkInterfaceManager = new SipNetworkInterfaceManager(this); maxMemory = Runtime.getRuntime().maxMemory() / (double)1024; congestionControlPolicy = CongestionControlPolicy.ErrorResponse; congestionControlThreadPool = new ScheduledThreadPoolExecutor(2, new ThreadPoolExecutor.CallerRunsPolicy()); congestionControlThreadPool.prestartAllCoreThreads(); } /** * {@inheritDoc} */ public void init() throws LifecycleException { //load the sip application router from the javax.servlet.sip.ar.spi.SipApplicationRouterProvider system property //and initializes it if present String sipApplicationRouterProviderClassName = System.getProperty("javax.servlet.sip.ar.spi.SipApplicationRouterProvider"); if(sipApplicationRouterProviderClassName != null && sipApplicationRouterProviderClassName.length() > 0) { if(logger.isInfoEnabled()) { logger.info("Using the javax.servlet.sip.ar.spi.SipApplicationRouterProvider system property to load the application router provider"); } try { sipApplicationRouter = ((SipApplicationRouterProvider) Class.forName(sipApplicationRouterProviderClassName).newInstance()).getSipApplicationRouter(); } catch (InstantiationException e) { throw new LifecycleException("Impossible to load the Sip Application Router",e); } catch (IllegalAccessException e) { throw new LifecycleException("Impossible to load the Sip Application Router",e); } catch (ClassNotFoundException e) { throw new LifecycleException("Impossible to load the Sip Application Router",e); } catch (ClassCastException e) { throw new LifecycleException("Sip Application Router defined does not implement " + SipApplicationRouterProvider.class.getName(),e); } } else { if(logger.isInfoEnabled()) { logger.info("Using the Service Provider Framework to load the application router provider"); } //TODO when moving to JDK 6, use the official http://java.sun.com/javase/6/docs/api/java/util/ServiceLoader.html instead //http://grep.codeconsult.ch/2007/10/31/the-java-service-provider-spec-and-sunmiscservice/ Iterator<SipApplicationRouterProvider> providers = ServiceRegistry.lookupProviders(SipApplicationRouterProvider.class); if(providers.hasNext()) { sipApplicationRouter = providers.next().getSipApplicationRouter(); } } if(sipApplicationRouter == null) { throw new LifecycleException("No Sip Application Router Provider could be loaded. " + "No jar compliant with JSR 289 Section Section 15.4.2 could be found on the classpath " + "and no javax.servlet.sip.ar.spi.SipApplicationRouterProvider system property set"); } if(logger.isInfoEnabled()) { logger.info(this + " Using the following Application Router instance: " + sipApplicationRouter); } sipApplicationRouter.init(); sipApplicationRouter.applicationDeployed(new ArrayList<String>(applicationDeployed.keySet())); if( oname == null ) { try { oname=new ObjectName(domain + ":type=SipApplicationDispatcher"); Registry.getRegistry(null, null) .registerComponent(this, oname, null); if(logger.isInfoEnabled()) { logger.info("Sip Application dispatcher registered under following name " + oname); } } catch (Exception e) { logger.error("Impossible to register the Sip Application dispatcher in domain" + domain, e); } } if(logger.isInfoEnabled()) { logger.info("bypassRequestExecutor ? " + bypassRequestExecutor); logger.info("bypassResponseExecutor ? " + bypassResponseExecutor); } messageDispatcherFactory = new MessageDispatcherFactory(this); } /** * {@inheritDoc} */ public void start() { statusLock.lock(); try { if(started) { return; } started = Boolean.TRUE; } finally { statusLock.unlock(); } congestionControlTimerTask = new CongestionControlTimerTask(); if(congestionControlTimerFuture == null && congestionControlCheckingInterval > 0) { congestionControlTimerFuture = congestionControlThreadPool.scheduleWithFixedDelay(congestionControlTimerTask, congestionControlCheckingInterval, congestionControlCheckingInterval, TimeUnit.MILLISECONDS); if(logger.isInfoEnabled()) { logger.info("Congestion control background task started and checking every " + congestionControlCheckingInterval + " milliseconds."); } } else { if(logger.isInfoEnabled()) { logger.info("No Congestion control background task started since the checking interval is equals to " + congestionControlCheckingInterval + " milliseconds."); } } if(logger.isDebugEnabled()) { logger.debug("Sip Application Dispatcher started"); } // outbound interfaces set here and not in sipstandardcontext because // depending on jboss or tomcat context can be started before or after // connectors resetOutboundInterfaces(); //JSR 289 Section 2.1.1 Step 4.If present invoke SipServletListener.servletInitialized() on each of initialized Servlet's listeners. for (SipContext sipContext : applicationDeployed.values()) { sipContext.notifySipServletsListeners(); } } /** * {@inheritDoc} */ public void stop() { statusLock.lock(); try { if(!started) { return; } started = Boolean.FALSE; } finally { statusLock.unlock(); } sipApplicationRouter.destroy(); if(oname != null) { Registry.getRegistry(null, null).unregisterComponent(oname); } } /** * {@inheritDoc} */ public void addSipApplication(String sipApplicationName, SipContext sipApplication) { if(logger.isDebugEnabled()) { logger.debug("Adding the following sip servlet application " + sipApplicationName + ", SipContext=" + sipApplication); } if(sipApplicationName == null) { throw new IllegalArgumentException("Something when wrong while initializing a sip servlets or converged application "); } if(sipApplication == null) { throw new IllegalArgumentException("Something when wrong while initializing the following application " + sipApplicationName); } //if the application has not set any concurrency control mode, we default to the container wide one if(sipApplication.getConcurrencyControlMode() == null) { sipApplication.setConcurrencyControlMode(concurrencyControlMode); if(logger.isInfoEnabled()) { logger.info("No concurrency control mode for application " + sipApplicationName + " , defaulting to the container wide one : " + concurrencyControlMode); } } else { if(logger.isInfoEnabled()) { logger.info("Concurrency control mode for application " + sipApplicationName + " is " + sipApplication.getConcurrencyControlMode()); } } sipApplication.getServletContext().setAttribute(ConcurrencyControlMode.class.getCanonicalName(), sipApplication.getConcurrencyControlMode()); applicationDeployed.put(sipApplicationName, sipApplication); String hash = GenericUtils.hashString(sipApplicationName); mdToApplicationName.put(hash, sipApplicationName); applicationNameToMd.put(sipApplicationName, hash); List<String> newlyApplicationsDeployed = new ArrayList<String>(); newlyApplicationsDeployed.add(sipApplicationName); sipApplicationRouter.applicationDeployed(newlyApplicationsDeployed); //if the ApplicationDispatcher is started, notification is sent that the servlets are ready for service //otherwise the notification will be delayed until the ApplicationDispatcher has started statusLock.lock(); try { if(started) { sipApplication.notifySipServletsListeners(); } } finally { statusLock.unlock(); } if(logger.isInfoEnabled()) { logger.info("the following sip servlet application has been added : " + sipApplicationName); } if(logger.isInfoEnabled()) { logger.info("It contains the following Sip Servlets : "); for(String servletName : sipApplication.getChildrenMap().keySet()) { logger.info("SipApplicationName : " + sipApplicationName + "/ServletName : " + servletName); } if(sipApplication.getSipRubyController() != null) { logger.info("It contains the following Sip Ruby Controller : " + sipApplication.getSipRubyController()); } } } /** * {@inheritDoc} */ public SipContext removeSipApplication(String sipApplicationName) { SipContext sipContext = applicationDeployed.remove(sipApplicationName); List<String> applicationsUndeployed = new ArrayList<String>(); applicationsUndeployed.add(sipApplicationName); sipApplicationRouter.applicationUndeployed(applicationsUndeployed); if(sipContext != null) { ((SipManager)sipContext.getManager()).removeAllSessions(); } String hash = GenericUtils.hashString(sipApplicationName); mdToApplicationName.remove(hash); applicationNameToMd.remove(sipApplicationName); if(logger.isInfoEnabled()) { logger.info("the following sip servlet application has been removed : " + sipApplicationName); } return sipContext; } /* * (non-Javadoc) * @see javax.sip.SipListener#processIOException(javax.sip.IOExceptionEvent) */ public void processIOException(IOExceptionEvent event) { logger.error("An IOException occured on " + event.getHost() + ":" + event.getPort() + "/" + event.getTransport() + " for provider " + event.getSource()); } /* * Gives the number of pending messages in all queues for all concurrency control modes. */ public int getNumberOfPendingMessages() { return this.asynchronousExecutor.getQueue().size(); // int size = 0; // Iterator<SipContext> applicationsIterator = this.applicationDeployed // .values().iterator(); // boolean noneModeAlreadyCounted = false; // while (applicationsIterator.hasNext()) { // SipContext context = applicationsIterator.next(); // SipManager manager = (SipManager) context // .getManager(); // if(context.getConcurrencyControlMode().equals( // ConcurrencyControlMode.None) && !noneModeAlreadyCounted) { // size = this.asynchronousExecutor.getQueue().size(); // noneModeAlreadyCounted = true; // } else if (context.getConcurrencyControlMode().equals( // ConcurrencyControlMode.SipApplicationSession)) { // Iterator<MobicentsSipApplicationSession> sessionIterator = manager // .getAllSipApplicationSessions(); // while (sessionIterator.hasNext()) { // size += sessionIterator.next().getExecutorService() // .getQueue().size(); // } // } else if (context.getConcurrencyControlMode().equals( // ConcurrencyControlMode.SipSession)) { // Iterator<MobicentsSipSession> sessionIterator = manager // .getAllSipSessions(); // while (sessionIterator.hasNext()) { // size += sessionIterator.next().getExecutorService() // .getQueue().size(); // } // } // } // // return size; } private void analyzeQueueCongestionState() { this.numberOfMessagesInQueue = getNumberOfPendingMessages(); if(rejectSipMessages) { if(numberOfMessagesInQueue <queueSize) { logger.warn("number of pending messages in the queues : " + numberOfMessagesInQueue + " < to the queue Size : " + queueSize + " => stopping to reject requests"); rejectSipMessages = false; } } else { if(numberOfMessagesInQueue > queueSize) { logger.warn("number of pending messages in the queues : " + numberOfMessagesInQueue + " > to the queue Size : " + queueSize + " => starting to reject requests"); rejectSipMessages = true; } } } private void analyzeMemory() { Runtime runtime = Runtime.getRuntime(); double allocatedMemory = runtime.totalMemory() / 1024; double freeMemory = runtime.freeMemory() / 1024; double totalFreeMemory = freeMemory + (maxMemory - allocatedMemory); this.percentageOfMemoryUsed= (((double)100) - ((totalFreeMemory / maxMemory) * ((double)100))); if(memoryToHigh) { if(percentageOfMemoryUsed < memoryThreshold) { logger.warn("Memory used: " + percentageOfMemoryUsed + "% < to the memory threshold : " + memoryThreshold + " => stopping to reject requests"); memoryToHigh = false; } } else { if(percentageOfMemoryUsed > memoryThreshold) { logger.warn("Memory used: " + percentageOfMemoryUsed + "% > to the memory threshold : " + memoryThreshold + " => starting to reject requests"); memoryToHigh = true; } } } /* * (non-Javadoc) * @see javax.sip.SipListener#processRequest(javax.sip.RequestEvent) */ public void processRequest(RequestEvent requestEvent) { if((rejectSipMessages || memoryToHigh) && CongestionControlPolicy.DropMessage.equals(congestionControlPolicy)) { logger.error("dropping request, memory is too high or too many messages present in queues"); return; } final SipProvider sipProvider = (SipProvider)requestEvent.getSource(); ServerTransaction requestTransaction = requestEvent.getServerTransaction(); final Dialog dialog = requestEvent.getDialog(); final Request request = requestEvent.getRequest(); final String requestMethod = request.getMethod(); try { if(logger.isDebugEnabled()) { logger.debug("sipApplicationDispatcher " + this + "Got a request event " + request.toString()); } if (!Request.ACK.equals(requestMethod) && requestTransaction == null ) { try { //folsson fix : Workaround broken Cisco 7940/7912 if(request.getHeader(MaxForwardsHeader.NAME) == null){ request.setHeader(SipFactories.headerFactory.createMaxForwardsHeader(70)); } requestTransaction = sipProvider.getNewServerTransaction(request); } catch ( TransactionUnavailableException tae) { logger.error("cannot get a new Server transaction for this request " + request, tae); // Sends a 500 Internal server error and stops processing. MessageDispatcher.sendErrorResponse(Response.SERVER_INTERNAL_ERROR, requestTransaction, request, sipProvider); return; } catch ( TransactionAlreadyExistsException taex ) { // Already processed this request so just return. return; } } final ServerTransaction transaction = requestTransaction; if(logger.isDebugEnabled()) { logger.debug("ServerTx ref " + transaction); logger.debug("Dialog ref " + dialog); } final SipServletRequestImpl sipServletRequest = new SipServletRequestImpl( request, sipFactoryImpl, null, transaction, dialog, JainSipUtils.dialogCreatingMethods.contains(requestMethod)); requestsProcessed.incrementAndGet(); // Check if the request is meant for me. If so, strip the topmost // Route header. final RouteHeader routeHeader = (RouteHeader) request .getHeader(RouteHeader.NAME); //Popping the router header if it's for the container as //specified in JSR 289 - Section 15.8 if(!isRouteExternal(routeHeader)) { request.removeFirst(RouteHeader.NAME); sipServletRequest.setPoppedRoute(routeHeader); final Parameters poppedAddress = (Parameters)routeHeader.getAddress().getURI(); if(poppedAddress.getParameter(MessageDispatcher.RR_PARAM_PROXY_APP) != null) { if(logger.isDebugEnabled()) { logger.debug("the request is for a proxy application, thus it is a subsequent request "); } sipServletRequest.setRoutingState(RoutingState.SUBSEQUENT); } if(transaction != null) { TransactionApplicationData transactionApplicationData = (TransactionApplicationData)transaction.getApplicationData(); if(transactionApplicationData != null && transactionApplicationData.getInitialPoppedRoute() == null) { transactionApplicationData.setInitialPoppedRoute(new AddressImpl(routeHeader.getAddress(), null, false)); } } } if(logger.isDebugEnabled()) { logger.debug("Routing State " + sipServletRequest.getRoutingState()); } try { if(rejectSipMessages || memoryToHigh) { if(!Request.ACK.equals(requestMethod) && !Request.PRACK.equals(requestMethod)) { MessageDispatcher.sendErrorResponse(Response.SERVICE_UNAVAILABLE, (ServerTransaction) sipServletRequest.getTransaction(), (Request) sipServletRequest.getMessage(), sipProvider); return; } } messageDispatcherFactory.getRequestDispatcher(sipServletRequest, this). dispatchMessage(sipProvider, sipServletRequest); } catch (DispatcherException e) { logger.error("Unexpected exception while processing request " + request,e); // Sends an error response if the subsequent request is not an ACK (otherwise it violates RF3261) and stops processing. if(!Request.ACK.equalsIgnoreCase(requestMethod)) { MessageDispatcher.sendErrorResponse(e.getErrorCode(), (ServerTransaction) sipServletRequest.getTransaction(), (Request) sipServletRequest.getMessage(), sipProvider); } return; } catch (Throwable e) { logger.error("Unexpected exception while processing request " + request,e); // Sends a 500 Internal server error if the subsequent request is not an ACK (otherwise it violates RF3261) and stops processing. if(!Request.ACK.equalsIgnoreCase(requestMethod)) { MessageDispatcher.sendErrorResponse(Response.SERVER_INTERNAL_ERROR, (ServerTransaction) sipServletRequest.getTransaction(), (Request) sipServletRequest.getMessage(), sipProvider); } return; } } catch (Throwable e) { logger.error("Unexpected exception while processing request " + request,e); // Sends a 500 Internal server error if the subsequent request is not an ACK (otherwise it violates RF3261) and stops processing. if(!Request.ACK.equalsIgnoreCase(request.getMethod())) { MessageDispatcher.sendErrorResponse(Response.SERVER_INTERNAL_ERROR, requestTransaction, request, sipProvider); } return; } } /* * (non-Javadoc) * @see javax.sip.SipListener#processResponse(javax.sip.ResponseEvent) */ public void processResponse(ResponseEvent responseEvent) { if((rejectSipMessages || memoryToHigh) && CongestionControlPolicy.DropMessage.equals(congestionControlPolicy)) { logger.error("dropping response, memory is too high or too many messages present in queues"); return; } if(logger.isInfoEnabled()) { logger.info("Response " + responseEvent.getResponse().toString()); } final Response response = responseEvent.getResponse(); final CSeqHeader cSeqHeader = (CSeqHeader)response.getHeader(CSeqHeader.NAME); //if this is a response to a cancel, the response is dropped if(Request.CANCEL.equalsIgnoreCase(cSeqHeader.getMethod())) { if(logger.isDebugEnabled()) { logger.debug("the response is dropped accordingly to JSR 289 " + "since this a response to a CANCEL"); } return; } responsesProcessed.incrementAndGet(); final ClientTransaction clientTransaction = responseEvent.getClientTransaction(); final Dialog dialog = responseEvent.getDialog(); // Transate the response to SipServletResponse final SipServletResponseImpl sipServletResponse = new SipServletResponseImpl( response, sipFactoryImpl, clientTransaction, null, dialog); try { messageDispatcherFactory.getResponseDispatcher(sipServletResponse, this). dispatchMessage(null, sipServletResponse); } catch (Throwable e) { logger.error("An unexpected exception happened while routing the response " + sipServletResponse, e); return; } } /* * (non-Javadoc) * @see javax.sip.SipListener#processDialogTerminated(javax.sip.DialogTerminatedEvent) */ public void processDialogTerminated(DialogTerminatedEvent dialogTerminatedEvent) { if(logger.isInfoEnabled()) { logger.info("Dialog Terminated => " + dialogTerminatedEvent.getDialog().getCallId().getCallId()); } Dialog dialog = dialogTerminatedEvent.getDialog(); TransactionApplicationData tad = (TransactionApplicationData) dialog.getApplicationData(); if(tad != null && tad.getSipServletMessage() != null) { SipServletMessageImpl sipServletMessageImpl = tad.getSipServletMessage(); SipSessionKey sipSessionKey = sipServletMessageImpl.getSipSessionKey(); tryToInvalidateSession(sipSessionKey); } else { logger.warn("no application data for this dialog " + dialogTerminatedEvent.getDialog().getDialogId()); } } /** * @param sipSessionImpl */ private void tryToInvalidateSession(SipSessionKey sipSessionKey) { //the key can be null if the application already invalidated the session if(sipSessionKey != null) { SipContext sipContext = findSipApplication(sipSessionKey.getApplicationName()); //the context can be null if the server is being shutdown if(sipContext != null) { sipContext.enterSipApp(null, null, null, true, false); try { MobicentsSipSession sipSessionImpl = sipContext.getSipManager().getSipSession(sipSessionKey, false, sipFactoryImpl, null); MobicentsSipApplicationSession sipApplicationSession = null; if(sipSessionImpl != null) { if(logger.isInfoEnabled()) { logger.info("sip session " + sipSessionKey + " is valid ? :" + sipSessionImpl.isValid()); if(sipSessionImpl.isValid()) { logger.info("Sip session " + sipSessionKey + " is ready to be invalidated ? :" + sipSessionImpl.isReadyToInvalidate()); } } sipApplicationSession = sipSessionImpl.getSipApplicationSession(); if(sipSessionImpl.isValid() && sipSessionImpl.isReadyToInvalidate()) { sipSessionImpl.onTerminatedState(); } } else { if(logger.isInfoEnabled()) { logger.info("sip session already invalidated" + sipSessionKey); } } if(sipApplicationSession == null) { final SipApplicationSessionKey sipApplicationSessionKey = SessionManagerUtil.getSipApplicationSessionKey( sipSessionKey.getApplicationName(), sipSessionKey.getApplicationSessionId()); sipApplicationSession = sipContext.getSipManager().getSipApplicationSession(sipApplicationSessionKey, false); if(sipApplicationSession != null) { if(logger.isInfoEnabled()) { logger.info("sip app session " + sipApplicationSessionKey + " is valid ? :" + sipApplicationSession.isValid()); if(sipApplicationSession.isValid()) { logger.info("Sip app session " + sipApplicationSessionKey + " is ready to be invalidated ? :" + sipApplicationSession.isReadyToInvalidate()); } } if(sipApplicationSession.isValid() && sipApplicationSession.isReadyToInvalidate()) { sipApplicationSession.tryToInvalidate(); } } } } finally { sipContext.exitSipApp(null, null); } } } } /* * (non-Javadoc) * @see javax.sip.SipListener#processTimeout(javax.sip.TimeoutEvent) */ public void processTimeout(TimeoutEvent timeoutEvent) { Transaction transaction = null; if(timeoutEvent.isServerTransaction()) { transaction = timeoutEvent.getServerTransaction(); } else { transaction = timeoutEvent.getClientTransaction(); } if(logger.isInfoEnabled()) { logger.info("transaction " + transaction + " timed out => " + transaction.getRequest().toString()); } TransactionApplicationData tad = (TransactionApplicationData) transaction.getApplicationData(); if(tad != null) { SipServletMessageImpl sipServletMessage = tad.getSipServletMessage(); SipSessionKey sipSessionKey = sipServletMessage.getSipSessionKey(); MobicentsSipSession sipSession = sipServletMessage.getSipSession(); if(sipServletMessage instanceof SipServletRequestImpl) { try { SipServletRequestImpl sipServletRequestImpl = (SipServletRequestImpl) sipServletMessage; sipServletMessage.setTransaction(transaction); SipServletResponseImpl response = (SipServletResponseImpl) sipServletRequestImpl.createResponse(408, null, false); MessageDispatcher.callServlet(response); } catch (Throwable t) { logger.error("Failed to deliver 408 respone on transaction timeout" + transaction, t); } } if(sipSession != null) { sipSession.removeOngoingTransaction(transaction); //notifying SipErrorListener that no ACK has been received for a UAS only SipServletResponseImpl lastFinalResponse = (SipServletResponseImpl) ((SipServletRequestImpl)sipServletMessage).getLastInformationalResponse(); if(logger.isInfoEnabled()) { logger.info("last Final Response" + lastFinalResponse); } ProxyImpl proxy = sipSession.getProxy(); if(sipServletMessage instanceof SipServletRequestImpl && proxy == null && lastFinalResponse != null) { List<SipErrorListener> sipErrorListeners = sipSession.getSipApplicationSession().getSipContext().getListeners().getSipErrorListeners(); SipErrorEvent sipErrorEvent = new SipErrorEvent( (SipServletRequest)sipServletMessage, lastFinalResponse); for (SipErrorListener sipErrorListener : sipErrorListeners) { try { sipErrorListener.noAckReceived(sipErrorEvent); } catch (Throwable t) { logger.error("SipErrorListener threw exception", t); } } } SipServletResponseImpl lastInfoResponse = (SipServletResponseImpl) ((SipServletRequestImpl)sipServletMessage).getLastInformationalResponse(); if(logger.isInfoEnabled()) { logger.info("last Informational Response" + lastInfoResponse); } if(sipServletMessage instanceof SipServletRequestImpl && proxy == null && lastInfoResponse != null) { List<SipErrorListener> sipErrorListeners = sipSession.getSipApplicationSession().getSipContext().getListeners().getSipErrorListeners(); SipErrorEvent sipErrorEvent = new SipErrorEvent( (SipServletRequest)sipServletMessage, lastInfoResponse); for (SipErrorListener sipErrorListener : sipErrorListeners) { try { sipErrorListener.noPrackReceived(sipErrorEvent); } catch (Throwable t) { logger.error("SipErrorListener threw exception", t); } } } tryToInvalidateSession(sipSessionKey); } } } /* * (non-Javadoc) * @see javax.sip.SipListener#processTransactionTerminated(javax.sip.TransactionTerminatedEvent) */ public void processTransactionTerminated(TransactionTerminatedEvent transactionTerminatedEvent) { Transaction transaction = null; if(transactionTerminatedEvent.isServerTransaction()) { transaction = transactionTerminatedEvent.getServerTransaction(); } else { transaction = transactionTerminatedEvent.getClientTransaction(); } if(logger.isInfoEnabled()) { logger.info("transaction " + transaction + " terminated => " + transaction.getRequest().toString()); } TransactionApplicationData tad = (TransactionApplicationData) transaction.getApplicationData(); if(tad != null) { SipServletMessageImpl sipServletMessageImpl = tad.getSipServletMessage(); SipSessionKey sipSessionKey = sipServletMessageImpl.getSipSessionKey(); if(sipSessionKey == null) { if(logger.isInfoEnabled()) { logger.info("no sip session were returned for this key " + sipServletMessageImpl.getSipSessionKey() + " and message " + sipServletMessageImpl); } } else { tryToInvalidateSession(sipSessionKey); // sipSessionImpl.removeOngoingTransaction(transaction); } } else { if(logger.isDebugEnabled()) { logger.debug("TransactionApplicationData not available on the following request " + transaction.getRequest().toString()); } } } public String getApplicationNameFromHash(String hash) { return mdToApplicationName.get(hash); } public String getHashFromApplicationName(String appName) { return applicationNameToMd.get(appName); } /** * Check if the route is external * @param routeHeader the route to check * @return true if the route is external, false otherwise */ public final boolean isRouteExternal(RouteHeader routeHeader) { if (routeHeader != null) { javax.sip.address.SipURI routeUri = (javax.sip.address.SipURI) routeHeader.getAddress().getURI(); String routeTransport = routeUri.getTransportParam(); if(routeTransport == null) { routeTransport = ListeningPoint.UDP; } return isExternal(routeUri.getHost(), routeUri.getPort(), routeTransport); } return true; } /** * Check if the via header is external * @param viaHeader the via header to check * @return true if the via header is external, false otherwise */ public final boolean isViaHeaderExternal(ViaHeader viaHeader) { if (viaHeader != null) { return isExternal(viaHeader.getHost(), viaHeader.getPort(), viaHeader.getTransport()); } return true; } /** * Check whether or not the triplet host, port and transport are corresponding to an interface * @param host can be hostname or ipaddress * @param port port number * @param transport transport used * @return true if the triplet host, port and transport are corresponding to an interface * false otherwise */ public final boolean isExternal(String host, int port, String transport) { boolean isExternal = true; ExtendedListeningPoint listeningPoint = sipNetworkInterfaceManager.findMatchingListeningPoint(host, port, transport); if((hostNames.contains(host) || listeningPoint != null)) { if(logger.isDebugEnabled()) { logger.debug("hostNames.contains(host)=" + hostNames.contains(host) + " | listeningPoint found = " + listeningPoint); } isExternal = false; } if(logger.isDebugEnabled()) { logger.debug("the triplet host/port/transport : " + host + "/" + port + "/" + transport + " is external : " + isExternal); } return isExternal; } /** * @return the sipApplicationRouter */ public SipApplicationRouter getSipApplicationRouter() { return sipApplicationRouter; } /** * @param sipApplicationRouter the sipApplicationRouter to set */ public void setSipApplicationRouter(SipApplicationRouter sipApplicationRouter) { this.sipApplicationRouter = sipApplicationRouter; } /* * (non-Javadoc) * @see org.mobicents.servlet.sip.core.SipApplicationDispatcher#getSipNetworkInterfaceManager() */ public SipNetworkInterfaceManager getSipNetworkInterfaceManager() { return this.sipNetworkInterfaceManager; } /* * (non-Javadoc) * @see org.mobicents.servlet.sip.core.SipApplicationDispatcher#getSipFactory() */ public SipFactoryImpl getSipFactory() { return sipFactoryImpl; } /* * (non-Javadoc) * @see org.mobicents.servlet.sip.core.SipApplicationDispatcher#getOutboundInterfaces() */ public List<SipURI> getOutboundInterfaces() { return sipNetworkInterfaceManager.getOutboundInterfaces(); } /** * set the outbound interfaces on all servlet context of applications deployed */ private void resetOutboundInterfaces() { List<SipURI> outboundInterfaces = sipNetworkInterfaceManager.getOutboundInterfaces(); for (SipContext sipContext : applicationDeployed.values()) { sipContext.getServletContext().setAttribute(javax.servlet.sip.SipServlet.OUTBOUND_INTERFACES, outboundInterfaces); } } /* * (non-Javadoc) * @see org.mobicents.servlet.sip.core.SipApplicationDispatcher#addHostName(java.lang.String) */ public void addHostName(String hostName) { if(logger.isDebugEnabled()) { logger.debug(this); logger.debug("Adding hostname "+ hostName); } hostNames.add(hostName); } /* * (non-Javadoc) * @see org.mobicents.servlet.sip.core.SipApplicationDispatcher#findHostNames() */ public Set<String> findHostNames() { return hostNames; } /* * (non-Javadoc) * @see org.mobicents.servlet.sip.core.SipApplicationDispatcher#removeHostName(java.lang.String) */ public void removeHostName(String hostName) { if(logger.isDebugEnabled()) { logger.debug("Removing hostname "+ hostName); } hostNames.remove(hostName); } /** * */ public SipApplicationRouterInfo getNextInterestedApplication( SipServletRequestImpl sipServletRequest) { SipApplicationRoutingRegion routingRegion = null; Serializable stateInfo = null; if(sipServletRequest.getSipSession() != null) { routingRegion = sipServletRequest.getSipSession().getRegionInternal(); stateInfo = sipServletRequest.getSipSession().getStateInfo(); } final Request request = (Request) sipServletRequest.getMessage(); sipServletRequest.setReadOnly(true); SipApplicationRouterInfo applicationRouterInfo = sipApplicationRouter.getNextApplication( sipServletRequest, routingRegion, sipServletRequest.getRoutingDirective(), null, stateInfo); sipServletRequest.setReadOnly(false); // 15.4.1 Procedure : point 2 final SipRouteModifier sipRouteModifier = applicationRouterInfo.getRouteModifier(); final String[] routes = applicationRouterInfo.getRoutes(); try { // ROUTE modifier indicates that SipApplicationRouterInfo.getRoute() returns a valid route, // it is up to container to decide whether it is external or internal. if(SipRouteModifier.ROUTE.equals(sipRouteModifier)) { final Address routeAddress = SipFactories.addressFactory.createAddress(routes[0]); final RouteHeader applicationRouterInfoRouteHeader = SipFactories.headerFactory.createRouteHeader(routeAddress); if(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--) { RouteHeader routeHeader = (RouteHeader) SipFactories.headerFactory.createHeader(RouteHeader.NAME, routes[i]); URI routeURI = routeHeader.getAddress().getURI(); if(routeURI.isSipURI()) { ((javax.sip.address.SipURI)routeURI).setLrParam(); } request.addHeader(routeHeader); } } } else if (SipRouteModifier.ROUTE_BACK.equals(sipRouteModifier)) { // Push container Route, pick up the first outbound interface final SipURI sipURI = getOutboundInterfaces().get(0); sipURI.setParameter("modifier", "route_back"); Header routeHeader = SipFactories.headerFactory.createHeader(RouteHeader.NAME, sipURI.toString()); request.addHeader(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.addHeader(routeHeader); } } } catch (ParseException e) { logger.error("Impossible to parse the route returned by the application router " + "into a compliant address",e); } return applicationRouterInfo; } public ThreadPoolExecutor getAsynchronousExecutor() { return asynchronousExecutor; } /** * Serialize the state info in memory and deserialize it and return the new object. * Since there is no clone method this is the only way to get the same object with a new reference * @param stateInfo the state info to serialize * @return the state info serialized and deserialized */ // private Serializable serializeStateInfo(Serializable stateInfo) { // ByteArrayOutputStream baos = null; // ObjectOutputStream out = null; // ByteArrayInputStream bais = null; // ObjectInputStream in = null; // // try{ // baos = new ByteArrayOutputStream(); // out = new ObjectOutputStream(baos); // out.writeObject(stateInfo); // bais = new ByteArrayInputStream(baos.toByteArray()); // in =new ObjectInputStream(bais); // return (Serializable)in.readObject(); // } catch (IOException e) { // logger.error("Impossible to serialize the state info", e); // return stateInfo; // } catch (ClassNotFoundException e) { // logger.error("Impossible to serialize the state info", e); // return stateInfo; // } finally { // try { // if(out != null) { // out.close(); // } // if(in != null) { // in.close(); // } // if(baos != null) { // baos.close(); // } // if(bais != null) { // bais.close(); // } // } catch (IOException e) { // logger.error("Impossible to close the streams after serializing state info", e); // } // } // } /* * (non-Javadoc) * @see org.mobicents.servlet.sip.core.SipApplicationDispatcher#findSipApplications() */ public Iterator<SipContext> findSipApplications() { return applicationDeployed.values().iterator(); } /* * (non-Javadoc) * @see org.mobicents.servlet.sip.core.SipApplicationDispatcher#findSipApplication(java.lang.String) */ public SipContext findSipApplication(String applicationName) { return applicationDeployed.get(applicationName); } // -------------------- JMX and Registration -------------------- protected String domain; protected ObjectName oname; protected MBeanServer mserver; public ObjectName getObjectName() { return oname; } public String getDomain() { return domain; } public void setDomain(String domain) { this.domain = domain; } /* * (non-Javadoc) * @see javax.management.MBeanRegistration#postDeregister() */ public void postDeregister() {} /* * (non-Javadoc) * @see javax.management.MBeanRegistration#postRegister(java.lang.Boolean) */ public void postRegister(Boolean registrationDone) {} /* * (non-Javadoc) * @see javax.management.MBeanRegistration#preDeregister() */ public void preDeregister() throws Exception {} /* * (non-Javadoc) * @see javax.management.MBeanRegistration#preRegister(javax.management.MBeanServer, javax.management.ObjectName) */ public ObjectName preRegister(MBeanServer server, ObjectName name) throws Exception { oname=name; mserver=server; domain=name.getDomain(); return name; } /* Exposed methods for the management console. Some of these duplicate existing methods, but * with JMX friendly types. */ public String[] findInstalledSipApplications() { Iterator<SipContext> apps = findSipApplications(); ArrayList<String> appList = new ArrayList<String>(); while(apps.hasNext()){ SipContext ctx = apps.next(); appList.add(ctx.getApplicationName()); } String[] ret = new String[appList.size()]; for(int q=0; q<appList.size(); q++) ret[q] = appList.get(q); return ret; } public Object retrieveApplicationRouterConfiguration() { if(this.sipApplicationRouter instanceof ManageableApplicationRouter) { ManageableApplicationRouter router = (ManageableApplicationRouter) this.sipApplicationRouter; return router.getCurrentConfiguration(); } else { throw new RuntimeException("This application router is not manageable"); } } public void updateApplicationRouterConfiguration(Object configuration) { if(this.sipApplicationRouter instanceof ManageableApplicationRouter) { ManageableApplicationRouter router = (ManageableApplicationRouter) this.sipApplicationRouter; router.configure(configuration); } else { throw new RuntimeException("This application router is not manageable"); } } public ConcurrencyControlMode getConcurrencyControlMode() { return concurrencyControlMode; } public void setConcurrencyControlMode(ConcurrencyControlMode concurrencyControlMode) { this.concurrencyControlMode = concurrencyControlMode; if(logger.isInfoEnabled()) { logger.info("Container wide Concurrency Control set to " + concurrencyControlMode.toString()); } } public int getQueueSize() { return queueSize; } public void setQueueSize(int queueSize) { this.queueSize = queueSize; } public void setConcurrencyControlModeByName(String concurrencyControlMode) { this.concurrencyControlMode = ConcurrencyControlMode.valueOf(concurrencyControlMode); } /** * @return the requestsProcessed */ public long getRequestsProcessed() { return requestsProcessed.get(); } /** * @return the requestsProcessed */ public long getResponsesProcessed() { return responsesProcessed.get(); } /** * @param congestionControlCheckingInterval the congestionControlCheckingInterval to set */ public void setCongestionControlCheckingInterval( long congestionControlCheckingInterval) { this.congestionControlCheckingInterval = congestionControlCheckingInterval; statusLock.lock(); try { if(started) { if(congestionControlTimerFuture != null) { congestionControlTimerFuture.cancel(false); } if(congestionControlCheckingInterval > 0) { congestionControlTimerFuture = congestionControlThreadPool.scheduleWithFixedDelay(congestionControlTimerTask, congestionControlCheckingInterval, congestionControlCheckingInterval, TimeUnit.MILLISECONDS); if(logger.isInfoEnabled()) { logger.info("Congestion control background task modified to check every " + congestionControlCheckingInterval + " milliseconds."); } } else { if(logger.isInfoEnabled()) { logger.info("No Congestion control background task started since the checking interval is equals to " + congestionControlCheckingInterval + " milliseconds."); } } } } finally { statusLock.unlock(); } } /** * @return the congestionControlCheckingInterval */ public long getCongestionControlCheckingInterval() { return congestionControlCheckingInterval; } /** * @param congestionControlPolicy the congestionControlPolicy to set */ public void setCongestionControlPolicy(CongestionControlPolicy congestionControlPolicy) { this.congestionControlPolicy = congestionControlPolicy; } public void setCongestionControlPolicyByName(String congestionControlPolicy) { this.congestionControlPolicy = CongestionControlPolicy.valueOf(congestionControlPolicy); } /** * @return the congestionControlPolicy */ public CongestionControlPolicy getCongestionControlPolicy() { return congestionControlPolicy; } /** * @param memoryThreshold the memoryThreshold to set */ public void setMemoryThreshold(int memoryThreshold) { this.memoryThreshold = memoryThreshold; } /** * @return the memoryThreshold */ public int getMemoryThreshold() { return memoryThreshold; } /** * @return the numberOfMessagesInQueue */ public int getNumberOfMessagesInQueue() { return numberOfMessagesInQueue; } /** * @return the percentageOfMemoryUsed */ public double getPercentageOfMemoryUsed() { return percentageOfMemoryUsed; } /** * @param bypassRequestExecutor the bypassRequestExecutor to set */ public void setBypassRequestExecutor(boolean bypassRequestExecutor) { this.bypassRequestExecutor = bypassRequestExecutor; } /** * @return the bypassRequestExecutor */ public boolean isBypassRequestExecutor() { return bypassRequestExecutor; } /** * @param bypassResponseExecutor the bypassResponseExecutor to set */ public void setBypassResponseExecutor(boolean bypassResponseExecutor) { this.bypassResponseExecutor = bypassResponseExecutor; } /** * @return the bypassResponseExecutor */ public boolean isBypassResponseExecutor() { return bypassResponseExecutor; } }