All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.mobicents.servlet.sip.message.SipServletRequestImpl Maven / Gradle / Ivy

There is a newer version: 4.0.128
Show newest version
/*
 * TeleStax, Open Source Cloud Communications
 * Copyright 2011-2014, Telestax Inc and individual contributors
 * by the @authors tag.
 *
 * This program is free software: you can redistribute it and/or modify
 * under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation; either version 3 of
 * the License, or (at your option) any later version.
 *
 * This program 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see 
 */

package org.mobicents.servlet.sip.message;

import gov.nist.javax.sip.DialogExt;
import gov.nist.javax.sip.SipStackImpl;
import gov.nist.javax.sip.TransactionExt;
import gov.nist.javax.sip.header.ims.PathHeader;
import gov.nist.javax.sip.message.MessageExt;
import gov.nist.javax.sip.message.SIPRequest;
import gov.nist.javax.sip.stack.IllegalTransactionStateException;
import gov.nist.javax.sip.stack.IllegalTransactionStateException.Reason;
import gov.nist.javax.sip.stack.SIPTransaction;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.UnsupportedEncodingException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Locale;
import java.util.Queue;
import java.util.Random;
import java.util.Set;
import java.util.Vector;

import javax.servlet.ServletInputStream;
import javax.servlet.sip.Address;
import javax.servlet.sip.AuthInfo;
import javax.servlet.sip.B2buaHelper;
import javax.servlet.sip.Parameterable;
import javax.servlet.sip.Proxy;
import javax.servlet.sip.SipServletRequest;
import javax.servlet.sip.SipServletResponse;
import javax.servlet.sip.SipSession.State;
import javax.servlet.sip.SipURI;
import javax.servlet.sip.TooManyHopsException;
import javax.servlet.sip.URI;
import javax.servlet.sip.ar.SipApplicationRouterInfo;
import javax.servlet.sip.ar.SipApplicationRoutingDirective;
import javax.servlet.sip.ar.SipApplicationRoutingRegion;
import javax.sip.ClientTransaction;
import javax.sip.Dialog;
import javax.sip.DialogState;
import javax.sip.InvalidArgumentException;
import javax.sip.ListeningPoint;
import javax.sip.ServerTransaction;
import javax.sip.SipException;
import javax.sip.SipProvider;
import javax.sip.Transaction;
import javax.sip.TransactionState;
import javax.sip.address.Hop;
import javax.sip.address.TelURL;
import javax.sip.header.AuthorizationHeader;
import javax.sip.header.CSeqHeader;
import javax.sip.header.ContactHeader;
import javax.sip.header.FromHeader;
import javax.sip.header.Header;
import javax.sip.header.MaxForwardsHeader;
import javax.sip.header.Parameters;
import javax.sip.header.ProxyAuthenticateHeader;
import javax.sip.header.ProxyAuthorizationHeader;
import javax.sip.header.RecordRouteHeader;
import javax.sip.header.RouteHeader;
import javax.sip.header.SubscriptionStateHeader;
import javax.sip.header.ToHeader;
import javax.sip.header.ViaHeader;
import javax.sip.header.WWWAuthenticateHeader;
import javax.sip.message.Message;
import javax.sip.message.Request;
import javax.sip.message.Response;

import org.apache.log4j.Logger;
import org.mobicents.ext.javax.sip.dns.DNSAwareRouter;
import org.mobicents.ext.javax.sip.dns.DNSServerLocator;
import org.mobicents.servlet.sip.JainSipUtils;
import org.mobicents.servlet.sip.SipConnector;
import org.mobicents.servlet.sip.address.AddressImpl;
import org.mobicents.servlet.sip.address.AddressImpl.ModifiableRule;
import org.mobicents.servlet.sip.address.GenericURIImpl;
import org.mobicents.servlet.sip.address.SipURIImpl;
import org.mobicents.servlet.sip.address.TelURLImpl;
import org.mobicents.servlet.sip.address.URIImpl;
import org.mobicents.servlet.sip.core.ApplicationRoutingHeaderComposer;
import org.mobicents.servlet.sip.core.MobicentsExtendedListeningPoint;
import org.mobicents.servlet.sip.core.MobicentsSipServlet;
import org.mobicents.servlet.sip.core.RoutingState;
import org.mobicents.servlet.sip.core.SipApplicationDispatcher;
import org.mobicents.servlet.sip.core.SipNetworkInterfaceManager;
import org.mobicents.servlet.sip.core.b2bua.MobicentsB2BUAHelper;
import org.mobicents.servlet.sip.core.dispatchers.MessageDispatcher;
import org.mobicents.servlet.sip.core.message.MobicentsSipServletRequest;
import org.mobicents.servlet.sip.core.proxy.MobicentsProxy;
import org.mobicents.servlet.sip.core.security.MobicentsAuthInfoEntry;
import org.mobicents.servlet.sip.core.session.MobicentsSipApplicationSession;
import org.mobicents.servlet.sip.core.session.MobicentsSipApplicationSessionKey;
import org.mobicents.servlet.sip.core.session.MobicentsSipSession;
import org.mobicents.servlet.sip.core.session.MobicentsSipSessionKey;
import org.mobicents.servlet.sip.core.session.SipRequestDispatcher;
import org.mobicents.servlet.sip.proxy.ProxyImpl;
import org.mobicents.servlet.sip.security.AuthInfoEntry;
import org.mobicents.servlet.sip.security.AuthInfoImpl;
import org.mobicents.servlet.sip.startup.StaticServiceHolder;

public abstract class SipServletRequestImpl extends SipServletMessageImpl implements
		MobicentsSipServletRequest {

	public static final String STALE = "stale";

	private static final long serialVersionUID = 1L;

	private static final Logger logger = Logger.getLogger(SipServletRequestImpl.class);
	
	private static final String EXCEPTION_MESSAGE = "The context does not allow you to modify this request !";
	
	public static final Set NON_INITIAL_SIP_REQUEST_METHODS = new HashSet();
	
	static {
		NON_INITIAL_SIP_REQUEST_METHODS.add("CANCEL");
		NON_INITIAL_SIP_REQUEST_METHODS.add("BYE");
		NON_INITIAL_SIP_REQUEST_METHODS.add("PRACK");
		NON_INITIAL_SIP_REQUEST_METHODS.add("ACK");
		NON_INITIAL_SIP_REQUEST_METHODS.add("UPDATE");
		NON_INITIAL_SIP_REQUEST_METHODS.add("INFO");		
	};
	
	/* Linked request (for b2bua) */
	private SipServletRequestImpl linkedRequest;
	
	private boolean createDialog;
	/*
	 * Popped route header - when we are the UAS we pop and keep the route
	 * header
	 */
	private AddressImpl poppedRoute;
	private RouteHeader poppedRouteHeader;

	/* Cache the application routing directive in the record route header */
	private SipApplicationRoutingDirective routingDirective = SipApplicationRoutingDirective.NEW;

	private RoutingState routingState;
	
	private transient SipServletResponse lastFinalResponse;
	
	private transient SipServletResponse lastInformationalResponse;
	
	/**
	 * Routing region.
	 */
	private SipApplicationRoutingRegion routingRegion;

	private transient URI subscriberURI;

	private boolean isInitial;
	
	private boolean isFinalResponseGenerated;
	
	private boolean is1xxResponseGenerated;	
	
	private transient boolean isReadOnly;		
	
	// This field is only used in CANCEL requests where we need the INVITe transaction
	private transient Transaction inviteTransactionToCancel;
	
	// needed for externalizable
	public SipServletRequestImpl () {}
	
	protected SipServletRequestImpl(Request request, SipFactoryImpl sipFactoryImpl,
			MobicentsSipSession sipSession, Transaction transaction, Dialog dialog,
			boolean createDialog) {
		super(request, sipFactoryImpl, transaction, sipSession, dialog);
		this.createDialog = createDialog;
		routingState = checkRoutingState(this, dialog);
		if(RoutingState.INITIAL.equals(routingState)) {
			isInitial = true;
		}
		isFinalResponseGenerated = false;
	}
        
	@Override
	public ModifiableRule getModifiableRule(String headerName) {
                ModifiableRule overriden = retrieveModifiableOverriden();
                if ( overriden != null ) {
                    return overriden;
                }
                
		String hName = getFullHeaderName(headerName);

		/*
		 * Contact is a system header field in messages other than REGISTER
		 * requests and responses, 3xx and 485 responses, and 200/OPTIONS
		 * responses so it is not contained in system headers and as such
		 * as a special treatment
		 */
		boolean isSystemHeader = JainSipUtils.SYSTEM_HEADERS.contains(hName);
				
		if (isSystemHeader) {
			return ModifiableRule.NotModifiable;
		}
		// Issue http://code.google.com/p/mobicents/issues/detail?id=2467
		// From and to Header are modifiable except the Tag parameter in a request that is not committed
		if(headerName.equalsIgnoreCase(FromHeader.NAME)) {
			if(!isCommitted()) {
				return ModifiableRule.From;
			} else {
				return ModifiableRule.NotModifiable;
			}
		}
		if(headerName.equalsIgnoreCase(ToHeader.NAME)) {
			if (!isCommitted()) {
				return ModifiableRule.To;
			} else {
				return ModifiableRule.NotModifiable;
			}
		}
		
		if(hName.equals(ContactHeader.NAME)) {
			Request request = (Request) this.message;
	
			String method = request.getMethod();
			if (method.equals(Request.REGISTER)) {
				return ModifiableRule.ContactNotSystem;
			} else {
				return ModifiableRule.ContactSystem;
			}				
		} else {
			return ModifiableRule.Modifiable;
		}
	}

	public SipServletRequest createCancel() {
		checkReadOnly();
		if (!((Request) message).getMethod().equals(Request.INVITE)) {
			throw new IllegalStateException(
					"Cannot create CANCEL for non invite " + message);
		}
		if (super.getTransaction() == null
				|| super.getTransaction() instanceof ServerTransaction)
			throw new IllegalStateException("No client transaction found! " + super.getTransaction());

		if(RoutingState.FINAL_RESPONSE_SENT.equals(routingState) || lastFinalResponse != null) {
			if(lastFinalResponse != null) {
				throw new IllegalStateException("final response already sent : " + lastFinalResponse);
			} else {
				throw new IllegalStateException("final response already sent!");
			}
		}
		
		try {			
			Request cancelRequest = ((ClientTransaction) getTransaction())
					.createCancel();
			SipServletRequestImpl newRequest = (SipServletRequestImpl) sipFactoryImpl.getMobicentsSipServletMessageFactory().createSipServletRequest(
					cancelRequest, getSipSession(),
					null, getTransaction().getDialog(), false);
			newRequest.inviteTransactionToCancel = super.getTransaction();
			return newRequest;
		} catch (SipException ex) {
			throw new IllegalStateException("Could not create cancel", ex);
		}

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.servlet.sip.SipServletRequest#createResponse(int)
	 */
	public SipServletResponse createResponse(int statusCode) {
		return createResponse(statusCode, null, true, false);
	}
	
	/*
	 * (non-Javadoc)
	 * @see javax.servlet.sip.SipServletRequest#createResponse(int, java.lang.String)
	 */
	public SipServletResponse createResponse(final int statusCode, final String reasonPhrase) {
		return createResponse(statusCode, reasonPhrase, true, false);
	}		
	
	public SipServletResponse createResponse(final int statusCode, final String reasonPhrase, boolean validate, boolean hasBeenReceived) {
		checkReadOnly();
		final Transaction transaction = getTransaction();
		if(RoutingState.CANCELLED.equals(routingState)) {
			throw new IllegalStateException("Cannot create a response for the invite, a CANCEL has been received and the INVITE was replied with a 487!");
		}
		if(validate) {
			if (transaction == null) {
				throw new IllegalStateException(
					"Cannot create a response for request " + message + " transaction is null, a final error response has probably already been sent");
			}
			if(transaction instanceof ClientTransaction) {
				throw new IllegalStateException(
					"Cannot create a response for request " + message + " not a server transaction " + transaction);
			}
		}
		try {
			final Request request = (Request) this.getMessage();
			final Response response = SipFactoryImpl.messageFactory.createResponse(
					statusCode, request);			
			if(reasonPhrase!=null) {
				response.setReasonPhrase(reasonPhrase);
			}
			final MobicentsSipSession session = getSipSession();
			final String requestMethod = getMethod();
			//add a To tag for all responses except Trying (or trying too if it's a subsequent request)
			if((statusCode > Response.TRYING || !isInitial()) && statusCode <= Response.SESSION_NOT_ACCEPTABLE) {
				final ToHeader toHeader = (ToHeader) response
					.getHeader(ToHeader.NAME);
				// If we already have a to tag, dont create new
				if (toHeader.getTag() == null) {					
					// if a dialog has already been created
					// reuse local tag
					final Dialog dialog = transaction.getDialog();
					if(session != null && dialog != null && dialog.getLocalTag() != null && dialog.getLocalTag().length() > 0
							&& session.getKey().getToTag() != null && session.getKey().getToTag().length() >0) {
						if(!dialog.getLocalTag().equals(session.getKey().getToTag())) {
							// Issue 2354 : if the dialog to tag is different than the  session to tag use the session to tag
							// so that we send the forked response out with the correct to tag
							if(logger.isDebugEnabled()) {
						    	logger.debug("setting session ToTag: " + session.getKey().getToTag());
						    }
							toHeader.setTag(session.getKey().getToTag());
						} else {
							if(logger.isDebugEnabled()) {
						    	logger.debug("setting dialog LocalTag: " + dialog.getLocalTag());
						    }
							toHeader.setTag(dialog.getLocalTag());
						}
					} else if(session != null && session.getSipApplicationSession() != null) {						
						final MobicentsSipApplicationSessionKey sipAppSessionKey = session.getSipApplicationSession().getKey();
						final MobicentsSipSessionKey sipSessionKey = session.getKey();
						// Fix for Issue 1044 : javax.sip.SipException: Tag mismatch dialogTag during process B2B response
						// make sure not to generate a new tag
						synchronized (this) {
							String toTag = sipSessionKey.getToTag();
							if(logger.isDebugEnabled()) {
						    	logger.debug("sipSessionKey ToTag : " + toTag);
						    }
							if(toTag == null) {
								toTag = ApplicationRoutingHeaderComposer.getHash(sipFactoryImpl.getSipApplicationDispatcher(),sipSessionKey.getApplicationName(), sipAppSessionKey.getId());
								//need to stay false for TCK Test com.bea.sipservlet.tck.agents.api.javax_servlet_sip.SipSessionListenerTest.testSessionDestroyed001
								session.getKey().setToTag(toTag, false);
							}
							if(logger.isDebugEnabled()) {
						    	logger.debug("setting ToTag: " + toTag);
						    }
							toHeader.setTag(toTag);	
						}						
					} else {							
						//if the sessions are null, it means it is a cancel response
						toHeader.setTag(Integer.toString(new Random().nextInt(10000000)));
					}
				}
				// Following restrictions in JSR 289 Section 4.1.3 Contact Header Field
				// + constraints from Issue 1687 : Contact Header is present in SIP Message where it shouldn't
				boolean setContactHeader = true;
				if ((statusCode >= 300 && statusCode < 400) || statusCode == 485 
						|| Request.REGISTER.equals(requestMethod) || Request.OPTIONS.equals(requestMethod)
						|| Request.BYE.equals(requestMethod) || Request.CANCEL.equals(requestMethod)
						|| Request.PRACK.equals(requestMethod) || Request.MESSAGE.equals(requestMethod)
						|| Request.PUBLISH.equals(requestMethod)) {
					// don't set the contact header in those case
					setContactHeader = false;					
				} 				
				if(setContactHeader) {	
					String outboundInterface = null;
					if(session != null) {
						outboundInterface = session.getOutboundInterface();
					}
				    // Add the contact header for the dialog.				    
				    ContactHeader contactHeader = JainSipUtils.createContactHeader(
			    			super.sipFactoryImpl.getSipNetworkInterfaceManager(), request, null, null, outboundInterface);
				    String transport = "udp";
				    if(session != null && session.getTransport() != null) transport = session.getTransport();
				    SipConnector sipConnector = StaticServiceHolder.sipStandardService.findSipConnector(transport);
					if(sipConnector != null && sipConnector.isUseStaticAddress()) {
						if(session != null && session.getProxy() == null) {
							boolean sipURI = contactHeader.getAddress().getURI().isSipURI();
							if(sipURI) {
								javax.sip.address.SipURI sipUri = (javax.sip.address.SipURI) contactHeader.getAddress().getURI();
								sipUri.setHost(sipConnector.getStaticServerAddress());
								sipUri.setPort(sipConnector.getStaticServerPort());
							}
						}
					}
				    if(logger.isDebugEnabled()) {
				    	logger.debug("We're adding this contact header to our new response: '" + contactHeader + ", transport=" + JainSipUtils.findTransport(request));
				    }
				    response.setHeader(contactHeader);
			    }
			}
			// Issue 1355 http://code.google.com/p/mobicents/issues/detail?id=1355 Not RFC compliant :
			// Application Routing : Adding the recorded route headers as route headers
//			final ListIterator recordRouteHeaders = request.getHeaders(RecordRouteHeader.NAME);
//			while (recordRouteHeaders.hasNext()) {
//				RecordRouteHeader recordRouteHeader = (RecordRouteHeader) recordRouteHeaders
//						.next();
//				RouteHeader routeHeader = SipFactoryImpl.headerFactory.createRouteHeader(recordRouteHeader.getAddress());
//				response.addHeader(routeHeader);
//			}
			
			if(session != null && session.getCopyRecordRouteHeadersOnSubsequentResponses() && !isInitial() && Request.INVITE.equals(requestMethod)) {
				// Miss Record-Route in Response for non compliant Server in reINVITE http://code.google.com/p/mobicents/issues/detail?id=2066
				final ListIterator recordRouteHeaders = request.getHeaders(RecordRouteHeader.NAME);
				while (recordRouteHeaders.hasNext()) {
					RecordRouteHeader recordRouteHeader = (RecordRouteHeader) recordRouteHeaders
							.next();
					response.addHeader(recordRouteHeader);
				}
			}
			
			final SipServletResponseImpl newSipServletResponse = (SipServletResponseImpl) sipFactoryImpl.getMobicentsSipServletMessageFactory().createSipServletResponse(
					response, 
					validate ? (ServerTransaction) transaction : transaction, session, getDialog(), hasBeenReceived, false);
			newSipServletResponse.setOriginalRequest(this);
			if(!Request.PRACK.equals(requestMethod) && statusCode >= Response.OK && 
					statusCode <= Response.SESSION_NOT_ACCEPTABLE) {	
				isFinalResponseGenerated = true;
			}
			if(statusCode >= Response.TRYING && 
					statusCode < Response.OK) {	
				is1xxResponseGenerated = true;
			}
			return newSipServletResponse;
		} catch (ParseException ex) {
			throw new IllegalArgumentException("Bad status code " + statusCode,
					ex);
		}		
	}

	public B2buaHelper getB2buaHelper() {
		checkReadOnly();
		final MobicentsSipSession session = getSipSession();
		if (session.getProxy() != null)
			throw new IllegalStateException("Proxy already present");
		MobicentsB2BUAHelper b2buaHelper = session.getB2buaHelper();
		if (b2buaHelper != null)
			return b2buaHelper;
		b2buaHelper = new B2buaHelperImpl();
		b2buaHelper.setMobicentsSipFactory(sipFactoryImpl);
		b2buaHelper.setSipManager(session.getSipApplicationSession().getSipContext().getSipManager());
		if(JainSipUtils.DIALOG_CREATING_METHODS.contains(getMethod())) {
			this.createDialog = true; // flag that we want to create a dialog for outgoing request.
		}
		session.setB2buaHelper(b2buaHelper);
		return b2buaHelper;
	}		

	public ServletInputStream getInputStream() throws IOException {
		return null;
	}

	public int getMaxForwards() {
		return ((MaxForwardsHeader) ((Request) message)
				.getHeader(MaxForwardsHeader.NAME)).getMaxForwards();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.servlet.sip.SipServletRequest#getPoppedRoute()
	 */
	public Address getPoppedRoute() {
		if((this.poppedRoute == null && poppedRouteHeader != null) ||
				(poppedRoute != null && poppedRouteHeader != null && !poppedRoute.getAddress().equals(poppedRouteHeader.getAddress()))) {
			this.poppedRoute = new AddressImpl(poppedRouteHeader.getAddress(), null, getTransaction() == null ? ModifiableRule.Modifiable : ModifiableRule.NotModifiable);
		}
		return poppedRoute;
	}
	
	public RouteHeader getPoppedRouteHeader() {
		return poppedRouteHeader;
	}

	/**
	 * Set the popped route
	 * 
	 * @param routeHeader
	 *            the popped route header to set
	 */
	public void setPoppedRoute(RouteHeader routeHeader) {
		this.poppedRouteHeader = routeHeader;
	}

	/**
	 * {@inheritDoc}
	 */
	public Proxy getProxy() throws TooManyHopsException {
		checkReadOnly();
		final MobicentsSipSession session = getSipSession();
		if (session.getB2buaHelper() != null ) throw new IllegalStateException("Cannot proxy request");
		return getProxy(true);
	}

	/**
	 * {@inheritDoc}
	 */
	public Proxy getProxy(boolean create) throws TooManyHopsException {
		checkReadOnly();
		final MobicentsSipSession session = getSipSession();
		if (session.getB2buaHelper() != null ) throw new IllegalStateException("Cannot proxy request");
		
		final MaxForwardsHeader mfHeader = (MaxForwardsHeader)this.message.getHeader(MaxForwardsHeader.NAME);
		if(mfHeader.getMaxForwards()<=0) {
			try {
				this.createResponse(Response.TOO_MANY_HOPS, "Too many hops").send();
			} catch (IOException e) {
				throw new RuntimeException("could not send the Too many hops response out !", e);
			}
			throw new TooManyHopsException();
		}
		
		// For requests like PUBLISH dialogs(sessions) do not exist, but some clients
		// attempt to send them in sequence as if they support dialogs and when such a subsequent request
		// comes in it gets assigned to the previous request session where the proxy is destroyed.
		// In this case we must create a new proxy. And we recoginze this case by additionally checking
		// if this request is initial. TODO: Consider deleting the session contents too? JSR 289 says
		// the session is keyed against the headers, not against initial/non-initial...
		if (create) { 
			MobicentsProxy proxy = session.getProxy();
			boolean createNewProxy = false;
			if(isInitial() && proxy != null && proxy.getOriginalRequest() == null)  {
				createNewProxy = true;
			}
			if(proxy == null || createNewProxy) {				
				session.setProxy(new ProxyImpl(this, super.sipFactoryImpl));
			}
		}
		return session.getProxy();
	}

	/**
	 * {@inheritDoc}
	 */
	public BufferedReader getReader() throws IOException {
		return null;
	}

	/**
	 * {@inheritDoc}
	 */
	public URI getRequestURI() {
		Request request = (Request) super.message;
		if (request.getRequestURI() instanceof javax.sip.address.SipURI)
			return new SipURIImpl((javax.sip.address.SipURI) request
					.getRequestURI(), ModifiableRule.Modifiable);
		else if (request.getRequestURI() instanceof javax.sip.address.TelURL)
			return new TelURLImpl((javax.sip.address.TelURL) request
					.getRequestURI());
		else 
			// From horacimacias : Fix for Issue 2115 MSS unable to handle GenericURI URIs
			return new GenericURIImpl(request.getRequestURI());
	}

	/**
	 * {@inheritDoc}
	 */
	public boolean isInitial() {
		return isInitial && !isOrphan(); 
	}
	
	/*
	 * (non-Javadoc)
	 * @see javax.servlet.sip.SipServletMessage#isCommitted()
	 */
	public boolean isCommitted() {		
		//the message is an incoming request for which a final response has been generated
		if(getTransaction() instanceof ServerTransaction && 
				(RoutingState.FINAL_RESPONSE_SENT.equals(routingState) || isFinalResponseGenerated)) {
			return true;
		}
		//the message is an outgoing request which has been sent
		if(getTransaction() instanceof ClientTransaction && this.isMessageSent) {
			return true;
		}
		/*
		if(Request.ACK.equals((((Request)message).getMethod()))) {
			return true;
		}*/
		return false;
	}
	
	protected void checkMessageState() {
		if(isMessageSent || getTransaction() instanceof ServerTransaction) {
			throw new IllegalStateException("Message already sent or incoming message");
		}
	}
	
	/**
	 * {@inheritDoc}
	 */
	public void pushPath(Address uri) {
		checkReadOnly();
		if(!Request.REGISTER.equalsIgnoreCase(((Request)message).getMethod())) {
			throw new IllegalStateException("Cannot push a Path on a non REGISTER request !");
		}
		if(uri.getURI() instanceof TelURL) {
			throw new IllegalArgumentException("Cannot push a TelUrl as a path !");
		}
		
		if (logger.isDebugEnabled())
			logger.debug("Pushing path into message of value [" + uri + "]");

		try {
			javax.sip.header.Header p = SipFactoryImpl.headerFactory
					.createHeader(PathHeader.NAME, uri.toString());
			this.message.addFirst(p);
		} catch (Exception e) {
			logger.error("Error while pushing path [" + uri + "]");
			throw new IllegalArgumentException("Error pushing path ", e);
		}

	}

	/**
	 * {@inheritDoc}
	 */
	public void pushRoute(Address address) {
		checkReadOnly();
		if(address.getURI() instanceof TelURL) {
			throw new IllegalArgumentException("Cannot push a TelUrl as a route !");
		}
		
		javax.sip.address.SipURI sipUri = (javax.sip.address.SipURI) ((AddressImpl) address)
			.getAddress().getURI();
		pushRoute(sipUri);
	}

	/*
	 * (non-Javadoc)
	 * @see javax.servlet.sip.SipServletRequest#pushRoute(javax.servlet.sip.SipURI)
	 */
	public void pushRoute(SipURI uri) {	
		checkReadOnly();
		javax.sip.address.SipURI sipUri = ((SipURIImpl) uri).getSipURI();
			sipUri.setLrParam();
		pushRoute(sipUri);
	}

	/**
	 * Pushes a route header on initial request based on the jain sip sipUri given on parameter
	 * @param sipUri the jain sip sip uri to use to construct the route header to push on the request
	 */
	private void pushRoute(javax.sip.address.SipURI sipUri) {
		if(!isInitial() && getSipSession().getProxy() == null) {
			//as per JSR 289 Section 11.1.3 Pushing Route Header Field Values
			// pushRoute can only be done on the initial requests. 
			// Subsequent requests within a dialog follow the route set. 
			// Any attempt to do a pushRoute on a subsequent request in a dialog 
			// MUST throw and IllegalStateException.
			throw new IllegalStateException("Cannot push route on subsequent requests, only intial ones");
		} else {
			if (logger.isDebugEnabled())
				logger.debug("Pushing route into message of value [" + sipUri + "]");
			
			sipUri.setLrParam();
			try {
				javax.sip.header.Header p = SipFactoryImpl.headerFactory
						.createRouteHeader(SipFactoryImpl.addressFactory
								.createAddress(sipUri));
				this.message.addFirst(p);
			} catch (SipException e) {
				logger.error("Error while pushing route [" + sipUri + "]");
				throw new IllegalArgumentException("Error pushing route ", e);
			}
		}
	}
	
	public void setMaxForwards(int n) {
		checkReadOnly();
		MaxForwardsHeader mfh = (MaxForwardsHeader) this.message
				.getHeader(MaxForwardsHeader.NAME);
		try {
			if (mfh != null) {
				mfh.setMaxForwards(n);
			}
		} catch (Exception ex) {
			String s = "Error while setting max forwards";
			logger.error(s, ex);
			throw new IllegalArgumentException(s, ex);
		}

	}
	/*
	 * (non-Javadoc)
	 * @see javax.servlet.sip.SipServletRequest#setRequestURI(javax.servlet.sip.URI)
	 */
	public void setRequestURI(URI uri) {
		checkReadOnly();
		Request request = (Request) message;
		URIImpl uriImpl = (URIImpl) uri;
		javax.sip.address.URI wrappedUri = uriImpl.getURI();
		request.setRequestURI(wrappedUri);
		//TODO look through all contacts of the user and change them depending of if STUN is enabled
		//and the request is aimed to the local network or outside
	}

	/*
	 * (non-Javadoc)
	 * @see javax.servlet.sip.SipServletRequest#setRoutingDirective(javax.servlet.sip.SipApplicationRoutingDirective, javax.servlet.sip.SipServletRequest)
	 */
	public void setRoutingDirective(SipApplicationRoutingDirective directive,
			SipServletRequest origRequest) throws IllegalStateException {
		checkReadOnly();
		SipServletRequestImpl origRequestImpl = (SipServletRequestImpl) origRequest;
		final MobicentsSipSession session = getSipSession();
		//@jean.deruelle Commenting this out, why origRequestImpl.isCommitted() is needed ?
//			if ((directive == SipApplicationRoutingDirective.REVERSE || directive == SipApplicationRoutingDirective.CONTINUE)
//					&& (!origRequestImpl.isInitial() || origRequestImpl.isCommitted())) {
		// If directive is CONTINUE or REVERSE, the parameter origRequest must be an 
		//initial request dispatched by the container to this application, 
		//i.e. origRequest.isInitial() must be true
		if ((directive == SipApplicationRoutingDirective.REVERSE || 
				directive == SipApplicationRoutingDirective.CONTINUE)){ 
			if(origRequestImpl == null ||
				!origRequestImpl.isInitial()) {
				if(logger.isDebugEnabled()) {
					logger.debug("directive to set : " + directive);
					logger.debug("Original Request Routing State : " + origRequestImpl.getRoutingState());
				}
				throw new IllegalStateException(
						"Bad state -- cannot set routing directive");
			} else {
				//set AR State Info from previous request
				session.setStateInfo(origRequestImpl.getSipSession().getStateInfo());
				//needed for application composition
				currentApplicationName = origRequestImpl.getCurrentApplicationName();
				//linking the requests
				//FIXME one request can be linked to  many more as for B2BUA
				origRequestImpl.setLinkedRequest(this);
				setLinkedRequest(origRequestImpl);
			}
		} else {				
			//This request must be a request created in a new SipSession 
			//or from an initial request, and must not have been sent. 
			//If any one of these preconditions are not met, the method throws an IllegalStateException.
			Set ongoingTransactions = session.getOngoingTransactions();
			if(!State.INITIAL.equals(session.getState()) && ongoingTransactions != null && ongoingTransactions.size() > 0) {
				if(logger.isDebugEnabled()) {
					logger.debug("session state : " + session.getState());
					logger.debug("numbers of ongoing transactions : " + ongoingTransactions.size());
				}
				throw new IllegalStateException(
					"Bad state -- cannot set routing directive");
			}
		}
		routingDirective = directive;
		linkedRequest = origRequestImpl;
		//@jean.deruelle Commenting this out, is this really needed ?
//		RecordRouteHeader rrh = (RecordRouteHeader) request
//				.getHeader(RecordRouteHeader.NAME);
//		javax.sip.address.SipURI sipUri = (javax.sip.address.SipURI) rrh
//				.getAddress().getURI();
//
//		try {
//			if (directive == SipApplicationRoutingDirective.NEW) {
//				sipUri.setParameter("rd", "NEW");
//			} else if (directive == SipApplicationRoutingDirective.REVERSE) {
//				sipUri.setParameter("rd", "REVERSE");
//			} else if (directive == SipApplicationRoutingDirective.CONTINUE) {
//				sipUri.setParameter("rd", "CONTINUE");
//			}
//			
//		} catch (Exception ex) {
//			String s = "error while setting routing directive";
//			logger.error(s, ex);
//			throw new IllegalArgumentException(s, ex);
//		}							
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.servlet.ServletRequest#getLocalName()
	 */
	public String getLocalName() {
		// TODO Auto-generated method stub
		return null;
	}

	public Locale getLocale() {
		// TODO Auto-generated method stub
		return null;
	}

	public Enumeration getLocales() {
		// TODO Auto-generated method stub
		return null;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.servlet.ServletRequest#getParameter(java.lang.String)
	 */
	public String getParameter(String name) {
		// JSR 289 Section 5.6.1 Parameters :
		// For initial requests where a preloaded Route header specified the application to be invoked, the parameters are those of the SIP or SIPS URI in that Route header.
		// For initial requests where the application is invoked the parameters are those present on the request URI, 
		// if this is a SIP or a SIPS URI. For other URI schemes, the parameter set is undefined.
		// For subsequent requests in a dialog, the parameters presented to the application  are those that the application itself 
		// set on the Record-Route header for the initial request or response (see 10.4 Record-Route Parameters). 
		// These will typically be the URI parameters of the top Route header field but if the upstream SIP element is a 
		// "strict router" they may be returned in the request URI (see RFC 3261). 
		// It is the containers responsibility to recognize whether the upstream element is a strict router and determine the right parameter set accordingly.
		if(this.getPoppedRoute() != null) {
			return this.getPoppedRoute().getURI().getParameter(name);
		} else {
			return this.getRequestURI().getParameter(name);
		}
	}

	
	/*
	 * (non-Javadoc)
	 * @see javax.servlet.ServletRequest#getParameterNames()
	 */
	public Enumeration getParameterNames() {
		// JSR 289 Section 5.6.1 Parameters :
		// For initial requests where a preloaded Route header specified the application to be invoked, the parameters are those of the SIP or SIPS URI in that Route header.
		// For initial requests where the application is invoked the parameters are those present on the request URI, 
		// if this is a SIP or a SIPS URI. For other URI schemes, the parameter set is undefined.
		// For subsequent requests in a dialog, the parameters presented to the application  are those that the application itself 
		// set on the Record-Route header for the initial request or response (see 10.4 Record-Route Parameters). 
		// These will typically be the URI parameters of the top Route header field but if the upstream SIP element is a 
		// "strict router" they may be returned in the request URI (see RFC 3261). 
		// It is the containers responsibility to recognize whether the upstream element is a strict router and determine the right parameter set accordingly.
		Vector retval = new Vector();
		if(this.getPoppedRoute() != null) {
			Iterator parameterNamesIt =  this.getPoppedRoute().getURI().getParameterNames();
			while (parameterNamesIt.hasNext()) {
				String parameterName = parameterNamesIt.next();
				retval.add(parameterName);		
			}
		} else {
			Iterator parameterNamesIt =  this.getRequestURI().getParameterNames();
			while (parameterNamesIt.hasNext()) {
				String parameterName = parameterNamesIt.next();
				retval.add(parameterName);
			}
		}		
		
		return retval.elements();				
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.servlet.ServletRequest#getParameterValues(java.lang.String)
	 */
	public String[] getParameterValues(String name) {
		// JSR 289 Section 5.6.1 Parameters :
		// The getParameterValues method returns an array of String objects containing all the parameter values associated with a parameter name. 
		// The value returned from the getParameter method must always equal the first value in the array of String objects returned by getParameterValues.
		// Note:Support for multi-valued parameters is defined mainly for HTTP because HTML forms may contain multi-valued parameters in form submissions.
		return new String[] {getParameter(name)};
	}

	public String getRealPath(String arg0) {
		// TODO Auto-generated method stub
		return null;
	}

	public String getRemoteHost() {
		if(getTransaction() != null) {
			if(((SIPTransaction)getTransaction()).getPeerPacketSourceAddress() != null &&
					((SIPTransaction)getTransaction()).getPeerPacketSourceAddress().getHostAddress() != null) {
				return ((SIPTransaction)getTransaction()).getPeerPacketSourceAddress().getHostAddress();
			} else {
				return ((SIPTransaction)getTransaction()).getPeerAddress();
			}
		} else {
			ViaHeader via = (ViaHeader) message.getHeader(ViaHeader.NAME);
			if(via == null) {
				return null;
			} else {
				return via.getHost();
			}
		}
	}

	/**
	 * {@inheritDoc}
	 */
	public javax.servlet.RequestDispatcher getRequestDispatcher(String handler) {
		MobicentsSipServlet sipServletImpl = (MobicentsSipServlet)
			getSipSession().getSipApplicationSession().getSipContext().findSipServletByName(handler);
		if(sipServletImpl == null) {
			throw new IllegalArgumentException(handler + " is not a valid servlet name");
		}
		return new SipRequestDispatcher(sipServletImpl);
	}

	public String getScheme() {
		return ((Request)message).getRequestURI().getScheme();
	}

	public String getServerName() {
		// TODO Auto-generated method stub
		return null;
	}

	public int getServerPort() {
		// TODO Auto-generated method stub
		return 0;
	}

	/**
	 * @return the routingDirective
	 */
	public SipApplicationRoutingDirective getRoutingDirective() {
		if(!isInitial()) {
			throw new IllegalStateException("the request is not initial");
		}
		return routingDirective;
	}

	/*
	 * (non-Javadoc)
	 * @see org.mobicents.servlet.sip.message.SipServletMessageImpl#send()
	 */
	@Override
	public void send() throws IOException {
		checkReadOnly();
		// Cope with com.bea.sipservlet.tck.agents.api.javax_servlet_sip.SipServletMessageTest.testSend101 
		// make sure a message received cannot be sent out
		checkMessageState();
		final Request request = (Request) super.message;
		final String requestMethod = getMethod();
		final SipApplicationDispatcher sipApplicationDispatcher = sipFactoryImpl.getSipApplicationDispatcher();
		final MobicentsSipSession session = getSipSession();
		final DNSServerLocator dnsServerLocator = sipApplicationDispatcher.getDNSServerLocator();
		Hop hop = null;
		// RFC 3263 support
		if(dnsServerLocator != null) {
			if(Request.CANCEL.equals(requestMethod)) {
				// RFC 3263 Section 4 : a CANCEL for a particular SIP request MUST be sent to the same SIP 
				// server that the SIP request was delivered to.
				TransactionApplicationData inviteTxAppData = ((TransactionApplicationData)inviteTransactionToCancel.getApplicationData());
				if(inviteTxAppData != null && inviteTxAppData.getHops() != null) {
					hop = inviteTxAppData.getHops().peek();
				}
			} else {
				javax.sip.address.URI uriToResolve =  request.getRequestURI();
				RouteHeader routeHeader = (RouteHeader) request.getHeader(RouteHeader.NAME);
				if(routeHeader != null) {					
					uriToResolve = routeHeader.getAddress().getURI();
				} else {
					// RFC5626 - see if we are to find a flow for this request.
					// Note: we should do this even if the "uriToResolve" is coming
					// from a route header but since we currently have not implemented
					// the correct things for a proxy scenario, only do it for UAS
					// scenarios. At least this will minimize the potential for messing
					// up right now...
					uriToResolve = resolveSipOutbound(uriToResolve);
				}
				String uriToResolveTransport = ((javax.sip.address.SipURI)uriToResolve).getTransportParam(); 
				boolean transportParamModified = false;
				if(session.getProxy() == null && session.getTransport() != null && uriToResolve.isSipURI() && uriToResolveTransport  == null &&
						// no need to modify the Request URI for UDP which is the default transport
						!session.getTransport().equalsIgnoreCase(ListeningPoint.UDP)) {					
					try {
						((javax.sip.address.SipURI)uriToResolve).setTransportParam(session.getTransport());
						transportParamModified = true;
					} catch (ParseException e) {
						// nothing to do here, will never happen
					}
				}
				Queue hops = dnsServerLocator.locateHops(uriToResolve);
				if(transportParamModified) {
					// Issue http://code.google.com/p/sipservlets/issues/detail?id=186
					// Resetting the transport to what is was before the modification to avoid modifying the route set
					if(uriToResolveTransport == null) {
						((javax.sip.address.SipURI)uriToResolve).removeParameter("transport");
					} else {
						try {
							((javax.sip.address.SipURI)uriToResolve).setTransportParam(uriToResolveTransport);
						} catch (ParseException e) {
							// nothing to do here, will never happen
						}
					}
				}
				if(hops != null && hops.size() > 0) {
					// RFC 3263 support don't remove the current hop, it will be the one to reuse for CANCEL and ACK to non 2xx transactions
					hop = hops.peek();
					transactionApplicationData.setHops(hops);				
				}
			}
		}
		send(hop);
	}
	
	
	/**
	 * Check to see if the uri to resolve contains a "ob" parameter and if so, try
	 * and locate a "flow" for this uri.
	 * 
	 * This is part of the RFC5626 implementation
	 * 
	 * @param uriToResolve
	 * @return the flow uri or if no flow was found, the same uri 
	 * that was passed into the method
	 */
	private javax.sip.address.URI resolveSipOutbound(final javax.sip.address.URI uriToResolve) {
		if (!uriToResolve.isSipURI()) {
			return uriToResolve;
		}

		final javax.sip.address.SipURI sipURI = (javax.sip.address.SipURI) uriToResolve;
		if (sipURI.getParameter(MessageDispatcher.SIP_OUTBOUND_PARAM_OB) == null) {
			// no ob parameter, return
			return uriToResolve;
		}
		final MobicentsSipSession session = getSipSession();
		final javax.sip.address.SipURI flow = session.getFlow();
		if (flow != null) {
			if (logger.isDebugEnabled()) {
				logger.debug("Found a flow \"" + flow + "\" for the original uri \"" + uriToResolve + "\"");
			}
			return flow;
		}

		return uriToResolve;
	}
	
	public void send(Hop hop) throws IOException {
		final Request request = (Request) super.message;
		final String requestMethod = getMethod();
		final MobicentsSipSession session = getSipSession();
		final String sessionTransport = session.getTransport();
		final SipNetworkInterfaceManager sipNetworkInterfaceManager = sipFactoryImpl.getSipNetworkInterfaceManager();
		final MobicentsSipApplicationSession sipApplicationSession = session.getSipApplicationSession();
		
		try {
			if(logger.isDebugEnabled()) {
		    	logger.debug("session transport is " + sessionTransport);
		    }
			// Because proxy decides transport in different way, it allows inbound and outbound transport to be different.
			if(session != null && session.getProxy() == null) {
				((MessageExt)message).setApplicationData(session.getTransport());			
			}		
			
			if(Request.CANCEL.equals(requestMethod)) {
				getSipSession().setRequestsPending(0);
	    		Transaction tx = inviteTransactionToCancel;
	    		if(logger.isDebugEnabled()) {
	    			logger.debug("INVITE transaction for CANCEL is " + tx);
	    		}
    			if(tx != null) {
    				if(logger.isDebugEnabled()) {
    					logger.debug("State of INVITE transaction " + tx + " for CANCEL, tx state " + tx.getState());
					}
    				// we rely on the transaction state to know if we send the cancel or not
    				if(tx.getState() == null || tx.getState().equals(TransactionState.CALLING) || tx.getState().equals(TransactionState.TRYING)) {
    					if(logger.isDebugEnabled()) {
    						logger.debug("Can not send CANCEL. Will try to STOP retransmissions " + tx + " tx state " + tx.getState());
    					}
	    				// We still haven't received any response on this call, so we can not send CANCEL,
	    				// we will just stop the retransmissions
	    				StaticServiceHolder.disableRetransmissionTimer.invoke(tx);
	    				if(tx.getApplicationData() instanceof TransactionApplicationData) {
	    					TransactionApplicationData tad = (TransactionApplicationData) tx.getApplicationData();
	    					tad.setCanceled(true);
	    				}
	    				return;
    				}
    			} else {
    				if(logger.isDebugEnabled()) {
    					logger.debug("Can not send CANCEL because no responses arrived. " +
    						"Can not stop retransmissions. The transaction is null");
    				}
    			}
	    	} 						
			
			if(logger.isDebugEnabled()) {
				logger.debug("hop " + hop );
			}
			//Issue http://code.google.com/p/mobicents/issues/detail?id=3144
			//Store the transport of the resolved hop to be used later
			if(hop != null) {
				((MessageExt)message).setApplicationData(hop.getTransport());
			}
			
			// adding via header and update via branch if null
			checkViaHeaderAddition(hop);
						
			final String transport = JainSipUtils.findTransport(request);
			if(sessionTransport == null) {
				session.setTransport(transport);
			}
			if(logger.isDebugEnabled()) {
				logger.debug("The found transport for sending request is '" + transport + "'");
			}

			MobicentsExtendedListeningPoint matchingListeningPoint = null;
			// Issue 159 : http://code.google.com/p/sipservlets/issues/detail?id=159 
		    // Bad choice of connectors when multiple of the same transport are available			
			String outboundInterface = session.getOutboundInterface();
			if(outboundInterface != null) {
				if(logger.isDebugEnabled()) {
					logger.debug("Trying to find listening point with outbound interface " + outboundInterface);
				}
				javax.sip.address.SipURI outboundInterfaceURI = null;			
				try {
					outboundInterfaceURI = (javax.sip.address.SipURI) SipFactoryImpl.addressFactory.createURI(outboundInterface);
				} catch (ParseException e) {
					throw new IllegalArgumentException("couldn't parse the outbound interface " + outboundInterface, e);
				}
				matchingListeningPoint = sipNetworkInterfaceManager.findMatchingListeningPoint(outboundInterfaceURI, false);
				if(logger.isDebugEnabled()) {
					logger.debug("Matching listening point " + matchingListeningPoint);
				}
			}
			if(matchingListeningPoint == null) {
				if(logger.isDebugEnabled()) {
					logger.debug("Trying to find listening point with transport " + transport);
				}
				matchingListeningPoint = sipNetworkInterfaceManager.findMatchingListeningPoint(
						transport, false);
				if(logger.isDebugEnabled()) {
					logger.debug("Matching listening point " + matchingListeningPoint);
				}
			}
			
			final SipProvider sipProvider = matchingListeningPoint.getSipProvider();
			SipConnector sipConnector = matchingListeningPoint.getSipConnector();

			
			//we update the via header after the sip connector has been found for the correct transport
			checkViaHeaderUpdateOrForStaticExternalAddressUsage(matchingListeningPoint, hop);
			
			if(Request.ACK.equals(requestMethod)) {
				// DNS Route need to be added for ACK
				// See RFC 3263 : "Because the ACK request for 2xx responses to INVITE constitutes a
				// different transaction, there is no requirement that it be delivered
				// to the same server that received the original request"
				addDnsRoute(hop, request);
				sendAck(transport, sipConnector, matchingListeningPoint);
				return;
			}						
			boolean addDNSRoute = true;
			//Added for initial requests only not for subsequent requests 
			if(isInitial()) {  
				// https://telestax.atlassian.net/browse/MSS-124 commented as this breaks AR for REGISTER 
				// && !Request.REGISTER.equalsIgnoreCase(requestMethod)) {
				final SipApplicationRouterInfo routerInfo = sipFactoryImpl.getNextInterestedApplication(this);
				if(routerInfo.getNextApplicationName() != null) {
					if(logger.isDebugEnabled()) {
						logger.debug("routing back to the container " +
								"since the following app is interested " + routerInfo.getNextApplicationName());
					}
					//add a route header to direct the request back to the container 
					//to check if there is any other apps interested in it
					addInfoForRoutingBackToContainer(routerInfo, session.getSipApplicationSession().getKey().getId(), session.getKey().getApplicationName());
					addDNSRoute = false;
				}
			}
			
			if(addDNSRoute) {
				if(logger.isDebugEnabled()) {
					logger.debug("routing outside the container " +
							"since no more apps are interested.");
				}
				// Adding Route Header for LB if the request is initial or
				// http://code.google.com/p/sipservlets/issues/detail?id=130
				// if we are in a HA configuration and the request is an out of dialog request
				if(isInitial() || dialog == null) {		
				    //Issue: https://code.google.com/p/sipservlets/issues/detail?id=284
				    //Issue: https://telestax.atlassian.net/browse/MSS-121
					if(logger.isDebugEnabled()) {
						logger.debug("bypassLoadBalancer: " + session.getBypassLoadBalancer() + ", sipFactoryImpl UseLoadBalancer: " + sipFactoryImpl.isUseLoadBalancer()
								+ ", matchingListeningPoint: " + matchingListeningPoint);
					}
					if(!session.getBypassLoadBalancer() && sipFactoryImpl.isUseLoadBalancer()) {
						if(matchingListeningPoint != null && matchingListeningPoint.isUseLoadBalancer()) {
							sipFactoryImpl.addLoadBalancerRouteHeader(request, matchingListeningPoint);
							addDNSRoute = false;
							if(logger.isDebugEnabled()) {
								logger.debug("adding route to Load Balancer since we are in a HA configuration " +
								" and no more apps are interested.");
							}
						} else {
							if(logger.isDebugEnabled()) {
								logger.debug("Not Using load balancer as it is not enabled for listeningPoint " + matchingListeningPoint);
							}
						}
					}
					//Issue: https://code.google.com/p/sipservlets/issues/detail?id=284
					else if(!session.getBypassProxy() && StaticServiceHolder.sipStandardService.getOutboundProxy() != null) {
						sipFactoryImpl.addLoadBalancerRouteHeader(request, null);
						addDNSRoute = false;
						if(logger.isDebugEnabled()) {
							logger.debug("adding route to outbound proxy (no load balancer set) since we have outboundProxy configured " +
							" and no more apps are interested.");
						}
					}
				}
			}
			
			if(addDNSRoute) {
				// we add the DNS Route only if we don't redirect back to the container or if there is no outgoing load balancer defined
				addDnsRoute(hop, request);
			}
			
			if(logger.isDebugEnabled()) {
				getSipSession().getSipApplicationSession().getSipContext().getSipManager().dumpSipSessions();
			}
			if (super.getTransaction() == null) {				
				setSystemContactHeader(sipConnector, matchingListeningPoint, transport); 
				/* 
				 * If we the next hop is in this container we optimize the traffic by directing it here instead of going through load balancers.
				 * This is must be done before creating the transaction, otherwise it will go to the host/port specified prior to the changes here.
				 */
				if(!isInitial() && sipConnector != null && // Initial requests already use local address in RouteHeader.
						sipConnector.isUseStaticAddress()) {
					JainSipUtils.optimizeRouteHeaderAddressForInternalRoutingrequest(sipConnector, request, session, sipFactoryImpl, transport);
				}								
				if(logger.isDebugEnabled()) {
					logger.debug("Getting new Client Tx for request " + request);
				}
				final ClientTransaction ctx = sipProvider.getNewClientTransaction(request);				
				JainSipUtils.setTransactionTimers((TransactionExt) ctx, sipFactoryImpl.getSipApplicationDispatcher());
				Dialog dialog = null;
				if(session.getProxy() != null) {
					// take care of the RRH
					if(isInitial()) {
						if(session.getProxy().getRecordRoute()) {
							ListIterator li = request.getHeaders(RecordRouteHeader.NAME);
							while(li.hasNext()) {
								RecordRouteHeader rrh = (RecordRouteHeader) li.next();
								if(rrh == null) {
									if(logger.isDebugEnabled()) {
										logger.debug("Unexpected RRH = null for this request" + request);
									}
								} else {
									javax.sip.address.URI uri = rrh.getAddress().getURI();
									if(uri.isSipURI()) {
										
										javax.sip.address.SipURI sipUri = (javax.sip.address.SipURI) uri;
										String serverId = sipUri.getParameter(MessageDispatcher.RR_PARAM_SERVER_NAME);
										String nextApp = sipUri.getParameter(MessageDispatcher.RR_PARAM_APPLICATION_NAME);
										final MobicentsSipApplicationSessionKey sipAppKey = getSipSession().getSipApplicationSession().getKey();
										final String thisApp = sipFactoryImpl.getSipApplicationDispatcher().getHashFromApplicationName(sipAppKey.getApplicationName());
										if(sipFactoryImpl.getSipApplicationDispatcher().getApplicationServerId().equalsIgnoreCase(serverId) && thisApp.equals(nextApp)) {
											if(sipConnector != null && sipConnector.isUseStaticAddress()) {
												sipUri.setHost(sipConnector.getStaticServerAddress());
												sipUri.setPort(sipConnector.getStaticServerPort());
											}
											//sipUri.setTransportParam(transport);
											if(logger.isDebugEnabled()) {
												logger.debug("Updated the RRH with static server address " + sipUri);
											}
										}
									}
								}
							}
						}
					}
				} else {
					// no dialogs in proxy
					dialog = ctx.getDialog();
				}
				
				if (dialog == null && this.createDialog && JainSipUtils.DIALOG_CREATING_METHODS.contains(getMethod())) {					
					dialog = sipProvider.getNewDialog(ctx);
					((DialogExt)dialog).disableSequenceNumberValidation();
					session.setSessionCreatingDialog(dialog);
					if(logger.isDebugEnabled()) {
						logger.debug("new Dialog for request " + request + ", ref = " + dialog);
					}
				}

				if(linkedRequest != null) {
					updateLinkedRequestAppDataMapping(ctx, dialog);
				}
				// Make the dialog point here so that when the dialog event
				// comes in we can find the session quickly.
				if (dialog != null) {
					dialog.setApplicationData(this.transactionApplicationData);
				}
				
				// SIP Request is ALWAYS pointed to by the client tx.
				// Notice that the tx appplication data is cached in the request
				// copied over to the tx so it can be quickly accessed when response
				// arrives.				
				ctx.setApplicationData(this.transactionApplicationData);								
				super.setTransaction(ctx);
				session.setSessionCreatingTransactionRequest(this);

			} else if (Request.PRACK.equals(request.getMethod())) {			
				final ClientTransaction ctx = sipProvider.getNewClientTransaction(request);
				JainSipUtils.setTransactionTimers((TransactionExt) ctx, sipFactoryImpl.getSipApplicationDispatcher());
				
				if(linkedRequest != null) {
					updateLinkedRequestAppDataMapping(ctx, dialog);
				}
				// SIP Request is ALWAYS pointed to by the client tx.
				// Notice that the tx appplication data is cached in the request
				// copied over to the tx so it can be quickly accessed when response
				// arrives.				
				ctx.setApplicationData(this.transactionApplicationData);
				setTransaction(ctx);
			} else {
				if(logger.isDebugEnabled()) {
					logger.debug("Transaction is not null, where was it created? " + getTransaction());
				}
			}

			//tells the application dispatcher to stop routing the linked request
			// (in this case it would be the original request) since it has been relayed
			if(linkedRequest != null && 
					!SipApplicationRoutingDirective.NEW.equals(routingDirective)) {
				if(!RoutingState.PROXIED.equals(linkedRequest.getRoutingState())) {
					linkedRequest.setRoutingState(RoutingState.RELAYED);
				}
			}		
			session.addOngoingTransaction(getTransaction());
			// Update Session state
			session.updateStateOnSubsequentRequest(this, false);
			// Issue 1481 http://code.google.com/p/mobicents/issues/detail?id=1481
			// proxy should not add or remove subscription since there is no dialog associated with it
			checkSubscriptionStateHeaders(session);								

			//updating the last accessed times 
			session.access();
			sipApplicationSession.access();
			Dialog dialog = getDialog();
			if(session.getProxy() != null) dialog = null;
			if(request.getMethod().equals(Request.CANCEL)) dialog = null;
						
			// Issue 1791 : using a different classloader created outside the application loader 
			// to avoid leaks on startup/shutdown
			final ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();
			try {
				final ClassLoader cl = sipApplicationSession.getSipContext().getClass().getClassLoader();
				Thread.currentThread().setContextClassLoader(cl);
				// If dialog does not exist or has no state.
				if (dialog == null || dialog.getState() == null
						// https://github.com/Mobicents/sip-servlets/issues/66 include UPDATE as well so it is sent indialog
						|| (dialog.getState() == DialogState.EARLY && !Request.PRACK.equals(requestMethod) && !Request.UPDATE.equals(requestMethod)) 
						|| Request.CANCEL.equals(requestMethod)) {
					if(logger.isDebugEnabled()) {
						logger.debug("Sending the request " + request);
					}
					((ClientTransaction) super.getTransaction()).sendRequest();
				} else {
					// This is a subsequent (an in-dialog) request. 
					// we don't redirect it to the container for now
					if(logger.isDebugEnabled()) {
						logger.debug("Sending the in dialog request " + request);
					}
					dialog.sendRequest((ClientTransaction) getTransaction());
				}	
				sipFactoryImpl.getSipApplicationDispatcher().updateRequestsStatistics(request, false);
				isMessageSent = true;
				
				if(method.equals(Request.INVITE)) {
					session.setRequestsPending(session.getRequestsPending()+1);
				}
			} finally {
				Thread.currentThread().setContextClassLoader(oldClassLoader);
			}
		} catch (Exception ex) {			
			// The second condition for SipExcpetion is to cover com.bea.sipservlet.tck.agents.spec.ProxyBranchTest.testCreatingBranchParallel() where they send a request twice, the second
			// time it does a "Request already sent" jsip exception but the tx is going on and must not be destroyed
			boolean skipTxTermination = false;
			Transaction tx = getTransaction();
			if((ex instanceof IllegalTransactionStateException && ((IllegalTransactionStateException)ex).getReason().equals(Reason.RequestAlreadySent)) || (tx != null && !(tx instanceof ClientTransaction))) {
				skipTxTermination = true;
			}
			if(!skipTxTermination) {
				JainSipUtils.terminateTransaction(tx);
				// cleaning up the request to make sure it can be resent with some modifications in case of exception
				if(transactionApplicationData.getHops() != null && transactionApplicationData.getHops().size() > 0) {
					request.removeFirst(RouteHeader.NAME);
					// https://code.google.com/p/sipservlets/issues/detail?id=250 retry directly on TCP
                    boolean nextHopVisited = visitNextHop();
                    if(nextHopVisited) {
                    	return;
                    }
				}

				message = (Request) request.clone();
                message.removeFirst(ViaHeader.NAME);
                message.removeFirst(ContactHeader.NAME);
				setTransaction(null);

				if(ex.getCause() != null && ex.getCause() instanceof IOException) {				
					throw (IOException) ex.getCause();
				}
			}
			throw new IllegalStateException("Error sending request " + request,ex);
		} 
	}

	/**
	 * 
	 * @param hop
	 * @param request	
	 */
	private void addDnsRoute(Hop hop, final Request request)
			throws ParseException, SipException {
		if(hop != null && sipFactoryImpl.getSipApplicationDispatcher().isExternal(hop.getHost(), hop.getPort(), hop.getTransport())) {	
			javax.sip.address.SipURI nextHopUri = SipFactoryImpl.addressFactory.createSipURI(null, hop.getHost());
			nextHopUri.setLrParam();
			nextHopUri.setPort(hop.getPort());
			if(hop.getTransport() != null) {
				nextHopUri.setTransportParam(hop.getTransport());
			}
			// Deal with http://code.google.com/p/mobicents/issues/detail?id=2346
			nextHopUri.setParameter(DNSAwareRouter.DNS_ROUTE, Boolean.TRUE.toString());
			final javax.sip.address.Address nextHopRouteAddress = 
				SipFactoryImpl.addressFactory.createAddress(nextHopUri);
			final RouteHeader nextHopRouteHeader = 
				SipFactoryImpl.headerFactory.createRouteHeader(nextHopRouteAddress);
			if(logger.isDebugEnabled()) {
				logger.debug("Adding next hop found by RFC 3263 lookups as route header" + nextHopRouteHeader);			    	
			}
	
			request.addFirst(nextHopRouteHeader);
		}
	}

	/**
	 * 
	 */
	private void checkViaHeaderAddition(Hop hop) throws ParseException {
		final SipNetworkInterfaceManager sipNetworkInterfaceManager = sipFactoryImpl.getSipNetworkInterfaceManager();		
		final Request request = (Request) super.message;
		final MobicentsSipSession session = getSipSession();
		final MobicentsSipApplicationSession sipApplicationSession = session.getSipApplicationSession();
		final MobicentsProxy proxy = session.getProxy();
		ViaHeader viaHeader = (ViaHeader) request.getHeader(ViaHeader.NAME);
		
		if(!request.getMethod().equals(Request.CANCEL) && viaHeader == null) {
			//Issue 112 fix by folsson
			boolean addViaHeader = false;
			if(proxy == null) {
				addViaHeader = true;
			} else if(isInitial) {
				if(proxy.getRecordRoute()) {
					addViaHeader = true;
				}
			} else if(proxy.getFinalBranchForSubsequentRequests() != null && proxy.getFinalBranchForSubsequentRequests().getRecordRoute()) {
				addViaHeader = true;
			}
			if(addViaHeader) {		    		
				// Issue 					
				viaHeader = JainSipUtils.createViaHeader(
						sipNetworkInterfaceManager, request, null, session.getOutboundInterface());
				message.addHeader(viaHeader);
				if(logger.isDebugEnabled()) {
			    	logger.debug("Added via Header" + viaHeader);
			    }
		    }
		}
		if(viaHeader.getBranch() == null) {
			final String branch = JainSipUtils.createBranch(sipApplicationSession.getKey().getId(),  sipFactoryImpl.getSipApplicationDispatcher().getHashFromApplicationName(session.getKey().getApplicationName()));			
			viaHeader.setBranch(branch);
		}
		// https://github.com/Mobicents/sip-servlets/issues/62 modify the Via transport to match either the hop, the route or the request URI transport
		String transportFromRouteOrRequestUri = JainSipUtils.findRouteOrRequestUriTransport((Request) message);
		String hopTransport = null;
		if(hop != null) {
			hopTransport = hop.getTransport();
		}
		String viaTransport = viaHeader.getTransport();
		if(logger.isDebugEnabled()) {
	    	logger.debug("viaHeader transport " + viaTransport + 
	    			", hopTransport " + hopTransport + ", transportFromRouteOrRequestUri " + transportFromRouteOrRequestUri);
	    }
		if(hopTransport != null && !viaTransport.equalsIgnoreCase(hopTransport)) {
			if(logger.isDebugEnabled()) {
		    	logger.debug("updating via transport to hopTransport " + hopTransport);
		    }
			viaHeader.setTransport(hopTransport);
		} else if (transportFromRouteOrRequestUri != null && !viaTransport.equalsIgnoreCase(transportFromRouteOrRequestUri)) {
			if(logger.isDebugEnabled()) {
		    	logger.debug("updating via transport to transportFromRouteOrRequestUri " + transportFromRouteOrRequestUri);
		    }
			viaHeader.setTransport(transportFromRouteOrRequestUri);
		}
		
	}

	/**
	 * 
	 * @param mobicentsExtendedListeningPoint
     * @param hop
	 * @throws ParseException
	 * @throws InvalidArgumentException
	 */
	private void checkViaHeaderUpdateOrForStaticExternalAddressUsage(final MobicentsExtendedListeningPoint mobicentsExtendedListeningPoint, final Hop hop)
			throws ParseException, InvalidArgumentException {
		
		final SipConnector sipConnector = mobicentsExtendedListeningPoint.getSipConnector();
		final Request request = (Request) super.message;	
		ViaHeader viaHeader = (ViaHeader) request.getHeader(ViaHeader.NAME);
				
		if(sipConnector != null) {
		    //Issue: https://code.google.com/p/sipservlets/issues/detail?id=284
			if(sipConnector.isUseStaticAddress() && !((MobicentsSipSession)getSession()).getBypassLoadBalancer() ) {
				javax.sip.address.URI uri = request.getRequestURI();
				RouteHeader route = (RouteHeader) request.getHeader(RouteHeader.NAME);
				if(route != null) {
					uri = route.getAddress().getURI();
				}
				if(uri.isSipURI()) {
					javax.sip.address.SipURI sipUri = (javax.sip.address.SipURI) uri;
					String host = sipUri.getHost();
					int port = sipUri.getPort();
					if(sipFactoryImpl.getSipApplicationDispatcher().isExternal(host, port, transport)) {
						viaHeader.setHost(sipConnector.getStaticServerAddress());
						viaHeader.setPort(sipConnector.getStaticServerPort());
					}
				}
			} else {
				// Cope with http://code.google.com/p/sipservlets/issues/detail?id=31 set rport
				// since we set the via header before request creation now, we make sure to update it based on the outbound destination before sending it out
				String ipAddressToCheckAgainst = sipConnector.getIpAddress();
				if(mobicentsExtendedListeningPoint.isAnyLocalAddress()) {
					for(String lpIpAddress : mobicentsExtendedListeningPoint.getIpAddresses()) {
						if(logger.isTraceEnabled()) {
							logger.trace("AnyLocalAddress so checking the list of ip addresses " + lpIpAddress + " to see if one matches the destination hop" + hop.getHost());
						}
						if(lpIpAddress.equalsIgnoreCase(hop.getHost())) {
							ipAddressToCheckAgainst = lpIpAddress;
							if(logger.isTraceEnabled()) {
								logger.trace("AnyLocalAddress using ip address " + lpIpAddress + " matching the destination hop" + hop.getHost());
							}
						}
					}
				}
				//Issue: https://code.google.com/p/sipservlets/issues/detail?id=210
				String outboundInterface = this.getSipSession().getOutboundInterface();
				if(outboundInterface != null){
					javax.sip.address.SipURI outboundInterfaceURI = (javax.sip.address.SipURI) SipFactoryImpl.addressFactory.createURI(outboundInterface);
					ipAddressToCheckAgainst = ((gov.nist.javax.sip.address.SipUri)outboundInterfaceURI).getHost();
				}
				
				
				if(!viaHeader.getHost().equalsIgnoreCase(ipAddressToCheckAgainst) || 
						!viaHeader.getTransport().equalsIgnoreCase(sipConnector.getTransport()) ||
						viaHeader.getPort() != sipConnector.getPort()) {
					if(logger.isTraceEnabled()) {
						logger.trace("Via " + viaHeader + " different than outbound SIP Connector picked by the container " + sipConnector + " , updating it");
					}
					viaHeader.setHost(ipAddressToCheckAgainst);
					viaHeader.setPort(sipConnector.getPort());
					viaHeader.setTransport(sipConnector.getTransport());
					if(logger.isTraceEnabled()) {
						logger.trace("Via updated to outbound SIP Connector picked by the container " + viaHeader);
					}
				}
			}
		}
		
	}
	
	/**
	 * Keeping the transactions mapping in application data for CANCEL handling
	 * 
	 * @param ctx
	 * @param dialog
	 */
	private void updateLinkedRequestAppDataMapping(final ClientTransaction ctx,
			Dialog dialog) {
		final Transaction linkedTransaction = linkedRequest.getTransaction();
		final Dialog linkedDialog = linkedRequest.getDialog();
		//keeping the client transaction in the server transaction's application data
		if(linkedTransaction != null && linkedTransaction.getApplicationData() != null) {
			((TransactionApplicationData)linkedTransaction.getApplicationData()).setTransaction(ctx);
		}
		if(linkedDialog != null && linkedDialog.getApplicationData() != null) {
			((TransactionApplicationData)linkedDialog.getApplicationData()).setTransaction(ctx);
		}
		//keeping the server transaction in the client transaction's application data
		this.transactionApplicationData.setTransaction(linkedTransaction);
		if(dialog!= null && dialog.getApplicationData() != null) {
			((TransactionApplicationData)dialog.getApplicationData()).setTransaction(linkedTransaction);
		}
	}

	/**
	 * @param session
	 * @throws SipException
	 */
	private void checkSubscriptionStateHeaders(final MobicentsSipSession session)
			throws SipException {
		if(Request.NOTIFY.equals(getMethod()) && session.getProxy() == null) {
			final SubscriptionStateHeader subscriptionStateHeader = (SubscriptionStateHeader) 
				getMessage().getHeader(SubscriptionStateHeader.NAME);		
		
			// RFC 3265 : If a matching NOTIFY request contains a "Subscription-State" of "active" or "pending", it creates
			// a new subscription and a new dialog (unless they have already been
			// created by a matching response, as described above).
			if (subscriptionStateHeader != null && 
								(SubscriptionStateHeader.ACTIVE.equalsIgnoreCase(subscriptionStateHeader.getState()) ||
								SubscriptionStateHeader.PENDING.equalsIgnoreCase(subscriptionStateHeader.getState()))) {					
				session.addSubscription(this);
			}
			// A subscription is destroyed when a notifier sends a NOTIFY request
			// with a "Subscription-State" of "terminated".
			if (subscriptionStateHeader != null && 
								SubscriptionStateHeader.TERMINATED.equalsIgnoreCase(subscriptionStateHeader.getState())) {
				session.removeSubscription(this);
			}
		}
	}

	/**
	 * @param sipConnector
	 * @param matchingListeningPoint
	 * @param transportForRequest
	 * @param sipConnector
	 * @param matchingListeningPoint
	 * @throws ParseException
	 */
	private void setSystemContactHeader(final SipConnector sipConnector,
			final MobicentsExtendedListeningPoint matchingListeningPoint, String transportForRequest)
			throws ParseException {
		
		final SipNetworkInterfaceManager sipNetworkInterfaceManager = sipFactoryImpl.getSipNetworkInterfaceManager();
		final Request request = (Request) super.message;
		final String requestMethod = getMethod();
		final MobicentsSipSession session = getSipSession();
		final MobicentsProxy proxy = session.getProxy();
		
		ContactHeader contactHeader = (ContactHeader)request.getHeader(ContactHeader.NAME);
		if(contactHeader != null && (((Parameters)contactHeader.getAddress().getURI()).getParameter("gruu") != null || 
				((Parameters)contactHeader.getAddress().getURI()).getParameter("gr") != null)) {
			 if(logger.isDebugEnabled()) {
				 logger.debug("not changing existing contact header " + contactHeader + " as it contains gruu");
			 }
			 return;
		}
		if(contactHeader == null && !Request.REGISTER.equalsIgnoreCase(requestMethod) && JainSipUtils.CONTACT_HEADER_METHODS.contains(requestMethod) && proxy == null) {
			final FromHeader fromHeader = (FromHeader) request.getHeader(FromHeader.NAME);
			final javax.sip.address.URI fromUri = fromHeader.getAddress().getURI();
			String fromName = null;
			String displayName = fromHeader.getAddress().getDisplayName();
			if(fromUri instanceof javax.sip.address.SipURI) {
				fromName = ((javax.sip.address.SipURI)fromUri).getUser();
			}
			// Create the contact name address.						
			contactHeader = 
				JainSipUtils.createContactHeader(sipNetworkInterfaceManager, request, displayName, fromName, session.getOutboundInterface());	
			request.addHeader(contactHeader);
		}

		if(proxy == null && contactHeader != null) {
			boolean sipURI = contactHeader.getAddress().getURI().isSipURI();
			if(sipURI) {
				javax.sip.address.SipURI contactSipUri = (javax.sip.address.SipURI) contactHeader.getAddress().getURI();
				//Issue: https://code.google.com/p/sipservlets/issues/detail?id=284
				if(sipConnector != null && sipConnector.isUseStaticAddress() && !session.getBypassLoadBalancer()) {
					contactSipUri.setHost(sipConnector.getStaticServerAddress());
					contactSipUri.setPort(sipConnector.getStaticServerPort());
					//https://telestax.atlassian.net/browse/MSS-103
					//contactSipUri.setUser(null);
				} 
				// http://code.google.com/p/sipservlets/issues/detail?id=156 
				// MSS overwrites host part of Contact Header in REGISTER requests
				else if(JainSipUtils.CONTACT_HEADER_METHODS.contains(requestMethod)) {
					//Issue: https://code.google.com/p/sipservlets/issues/detail?id=210
					String outboundInterface = this.getSipSession().getOutboundInterface();
					if(outboundInterface != null){
						javax.sip.address.SipURI outboundInterfaceURI = (javax.sip.address.SipURI) SipFactoryImpl.addressFactory.createURI(outboundInterface);
						String outboundHost = ((gov.nist.javax.sip.address.SipUri)outboundInterfaceURI).getHost();
						contactSipUri.setHost(outboundHost);
					} else {	
					boolean usePublicAddress = JainSipUtils.findUsePublicAddress(
							sipNetworkInterfaceManager, request, matchingListeningPoint);
					contactSipUri.setHost(matchingListeningPoint.getIpAddress(usePublicAddress));	
					}
					contactSipUri.setPort(matchingListeningPoint.getPort());
				}
				// http://code.google.com/p/mobicents/issues/detail?id=1150 only set transport if not udp
				if(transportForRequest != null) {
					if(!ListeningPoint.UDP.equalsIgnoreCase(transportForRequest)) {
						contactSipUri.setTransportParam(transportForRequest);
					}						
					
					// If the transport for the request was changed in the last moment we need to update the header
					String transportParam = contactSipUri.getTransportParam();
					if(transportParam != null && !transportParam.equalsIgnoreCase(transportForRequest)) {
						contactSipUri.setTransportParam(transportForRequest);
					}

					if(ListeningPoint.TLS.equalsIgnoreCase(transportForRequest)) {
						final javax.sip.address.URI requestURI = request.getRequestURI();
						// make the contact uri secure only if the request uri is secure to cope with issue http://code.google.com/p/mobicents/issues/detail?id=2269
						// Wrong Contact header scheme URI in case TLS call with 'sip:' scheme
						if(requestURI.isSipURI() && ((javax.sip.address.SipURI)requestURI).isSecure()) {
							contactSipUri.setSecure(true);
						} else if(requestURI.isSipURI() && !((javax.sip.address.SipURI)requestURI).isSecure() && contactSipUri.isSecure()) {
							// if the Request URI is non secure but the contact is make sure to move it back to non secure to handle Microsoft OCS interop 
							contactSipUri.setSecure(false);
						}
					}
				}
			} 
		}
	}
	
	/**
	 * 
	 * @return
	 */
	public boolean visitNextHop() {
	    if(logger.isDebugEnabled()) {
            logger.debug("visitNextHop txAppData " + transactionApplicationData);
        }
		if(transactionApplicationData != null) {
			Queue nextHops = transactionApplicationData.getHops();
			if(logger.isDebugEnabled()) {
	            logger.debug("visitNextHop nextHops " + nextHops);
	            if(nextHops != null) {
	                logger.debug("visitNextHop nextHops size " + nextHops.size());
	            }
	        }
			if(sipFactoryImpl.getSipApplicationDispatcher().getDNSServerLocator() != null && nextHops != null && nextHops.size() > 1) {
				nextHops.remove();
				Hop nextHop = nextHops.peek();
				if(logger.isDebugEnabled()) {
		            logger.debug("visitNextHop nextHop " + nextHop);
		        }
				if(nextHop != null) {
					// If a failure occurs, the client SHOULD create a new request, 
					// which is identical to the previous, but
					getMessage().removeFirst(RouteHeader.NAME);
					// has a different value of the Via branch ID than the previous (and
					// therefore constitutes a new SIP transaction).  
					ViaHeader viaHeader = (ViaHeader) getMessage().getHeader(ViaHeader.NAME);
					viaHeader.removeParameter("branch");
					message = (Message) message.clone();
					if(logger.isDebugEnabled()) {
						logger.debug("sending request " + getMessage() + " to next hop " + nextHop + " discovered through RFC3263 mechanisms.");
					}
					setTransaction(null);
					// That request is sent to the next element in the list as specified by RFC 2782.
					try {
						send(nextHop);
					} catch (IOException e) {
						logger.error("unexpected IOException on sending " + getMessage() + " to next hop " + nextHop + "discovered through RFC3263 mechanisms.");
						return false;
					}
					return true;
				}
			}
		}
		return false;
		
	}

	/**
	 * @param transport
	 * @param sipConnector
	 * @param matchingListeningPoint
	 * @throws ParseException
	 * @throws SipException
	 */
	private void sendAck(final String transport, final SipConnector sipConnector,			
			final MobicentsExtendedListeningPoint matchingListeningPoint)
			throws ParseException, SipException {
		
		final Request request = (Request) super.message;			
		final MobicentsSipSession session = getSipSession();
		final MobicentsSipApplicationSession sipApplicationSession = session.getSipApplicationSession();
		
		// Issue 1791 : using a different classloader created outside the application loader 
		// to avoid leaks on startup/shutdown
		final ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();
		try {
			final ClassLoader cl = sipApplicationSession.getSipContext().getClass().getClassLoader();
			Thread.currentThread().setContextClassLoader(cl);
			if(sipConnector != null && // Initial requests already use local address in RouteHeader.
					sipConnector.isUseStaticAddress()) {
				JainSipUtils.optimizeRouteHeaderAddressForInternalRoutingrequest(sipConnector, request, session, sipFactoryImpl, transport);
			}
			//Issue 2588 : updating the last accessed times for ACK as well 
			session.access();
			sipApplicationSession.access();
			if(logger.isDebugEnabled()) {
				logger.debug("Sending the ACK request " + request);
			}
			session.getSessionCreatingDialog().sendAck(request);
			session.setRequestsPending(session.getRequestsPending()-1);
			sipFactoryImpl.getSipApplicationDispatcher().updateRequestsStatistics(request, false);
			final Transaction transaction = getTransaction();
			// transaction can be null in case of forking
			if(transaction != null) {
				final TransactionApplicationData tad = (TransactionApplicationData) transaction.getApplicationData();
				final MobicentsB2BUAHelper b2buaHelperImpl = sipSession.getB2buaHelper();
//				if(b2buaHelperImpl != null && tad != null) {
//					// we unlink the originalRequest early to avoid keeping the messages in mem for too long
//					b2buaHelperImpl.unlinkOriginalRequestInternal((SipServletRequestImpl)tad.getSipServletMessage(), false);
//				}
				if(b2buaHelperImpl == null) {
					session.removeOngoingTransaction(transaction);
				}
				if(tad != null) {
					// Issue 1468 : to handle forking, we shouldn't cleanup the app data since it is needed for the forked responses
					boolean nullifyAppData = true;					
					if(((SipStackImpl)(sipFactoryImpl.getSipApplicationDispatcher().getSipStack())).getMaxForkTime() > 0) {
						nullifyAppData = false;
					}
					if(nullifyAppData) {
						tad.cleanUp();
						transaction.setApplicationData(null);
						if(b2buaHelperImpl == null && sipSession.getProxy() == null) {
							tad.cleanUpMessage();
						}
					}
				}
			}
			final SipProvider sipProvider = matchingListeningPoint.getSipProvider();	
			// Issue 1468 : to handle forking, we shouldn't cleanup the app data since it is needed for the forked responses
			if(((SipStackImpl)sipProvider.getSipStack()).getMaxForkTime() == 0 && transaction != null) {
				transaction.setApplicationData(null);
			}
			return;
		} finally {
			Thread.currentThread().setContextClassLoader(oldClassLoader);
		}
	}

	/**
	 * Add a route header to route back to the container
	 * @param applicationName the application name that was chosen by the AR to route the request
	 * @throws ParseException
	 * @throws SipException 
	 * @throws NullPointerException 
	 */
	private void addInfoForRoutingBackToContainer(SipApplicationRouterInfo routerInfo, String applicationSessionId, String applicationName) throws ParseException, SipException {		
		final Request request = (Request) super.message;
		final javax.sip.address.SipURI sipURI = JainSipUtils.createRecordRouteURI(
				sipFactoryImpl.getSipNetworkInterfaceManager(), 
				request);
		sipURI.setLrParam();
		sipURI.setParameter(MessageDispatcher.ROUTE_PARAM_DIRECTIVE, 
				routingDirective.toString());
		if(getSipSession().getRegionInternal() != null) {
			sipURI.setParameter(MessageDispatcher.ROUTE_PARAM_REGION_LABEL, 
					getSipSession().getRegionInternal().getLabel());
			sipURI.setParameter(MessageDispatcher.ROUTE_PARAM_REGION_TYPE, 
					getSipSession().getRegionInternal().getType().toString());
		}
		sipURI.setParameter(MessageDispatcher.ROUTE_PARAM_PREV_APPLICATION_NAME, 
				applicationName);
		sipURI.setParameter(MessageDispatcher.ROUTE_PARAM_PREV_APP_ID, 
				applicationSessionId);
		final javax.sip.address.Address routeAddress = 
			SipFactoryImpl.addressFactory.createAddress(sipURI);
		final RouteHeader routeHeader = 
			SipFactoryImpl.headerFactory.createRouteHeader(routeAddress);
		request.addFirst(routeHeader);		
		// adding the application router info to avoid calling the AppRouter twice
		// See Issue 791 : http://code.google.com/p/mobicents/issues/detail?id=791
		final MobicentsSipSession session = getSipSession();
		session.setNextSipApplicationRouterInfo(routerInfo);
	}

	public void setLinkedRequest(SipServletRequestImpl linkedRequest) {
		this.linkedRequest = linkedRequest;

	}

	public SipServletRequestImpl getLinkedRequest() {
		return this.linkedRequest;
	}	

	/**
	 * @return the routingState
	 */
	public RoutingState getRoutingState() {
		return routingState;
	}

	/**
	 * @param routingState the routingState to set
	 */
	public void setRoutingState(RoutingState routingState) throws IllegalStateException {
		//JSR 289 Section 11.2.3 && 10.2.6
		if(routingState.equals(RoutingState.CANCELLED) && 
				(this.routingState.equals(RoutingState.FINAL_RESPONSE_SENT) || 
						this.routingState.equals(RoutingState.PROXIED))) {
			throw new IllegalStateException("Cannot cancel final response already sent!");
		}
		if((routingState.equals(RoutingState.FINAL_RESPONSE_SENT)|| 
				routingState.equals(RoutingState.PROXIED)) && this.routingState.equals(RoutingState.CANCELLED)) {
			throw new IllegalStateException("Cancel received and already replied with a 487!");
		}
		if(routingState.equals(RoutingState.SUBSEQUENT)) {
			isInitial = false;
		}
		// http://code.google.com/p/sipservlets/issues/detail?id=19 
		// Retried Request are not considered as initial
		if(routingState.equals(RoutingState.INITIAL)) {
			isInitial = true;
		}
		if(logger.isDebugEnabled()) {
			logger.debug("setting routing state to " + routingState);
		}
		this.routingState = routingState;	
	}
	
	/*
	 * (non-Javadoc)
	 * @see javax.servlet.sip.SipServletRequest#addAuthHeader(javax.servlet.sip.SipServletResponse, javax.servlet.sip.AuthInfo)
	 */
	public void addAuthHeader(SipServletResponse challengeResponse,
			AuthInfo authInfo) {
		addAuthHeader(challengeResponse, authInfo, false);
	}

	/*
	 * (non-Javadoc)
	 * @see javax.servlet.sip.SipServletRequest#addAuthHeader(javax.servlet.sip.SipServletResponse, java.lang.String, java.lang.String)
	 */
	public void addAuthHeader(SipServletResponse challengeResponse,
			String username, String password) {
		addAuthHeader(challengeResponse, username, password, false);
	}
	
	/*
	 * (non-Javadoc)
	 * @see org.mobicents.javax.servlet.sip.SipServletRequestExt#addAuthHeader(javax.servlet.sip.SipServletResponse, javax.servlet.sip.AuthInfo, boolean)
	 */
	public void addAuthHeader(SipServletResponse challengeResponse,
			AuthInfo authInfo, boolean cacheCredentials) {
		checkReadOnly();
		AuthInfoImpl authInfoImpl = (AuthInfoImpl) authInfo;
		
		SipServletResponseImpl challengeResponseImpl = 
			(SipServletResponseImpl) challengeResponse;
		
		Response response = (Response) challengeResponseImpl.getMessage();
		// First check for WWWAuthentication headers
		ListIterator authHeaderIterator = 
			response.getHeaders(WWWAuthenticateHeader.NAME);
		while(authHeaderIterator.hasNext()) {
			WWWAuthenticateHeader wwwAuthHeader = 
				(WWWAuthenticateHeader) authHeaderIterator.next();
			// Fix for Issue 1832 : http://code.google.com/p/mobicents/issues/detail?id=1832 
			// Authorization header is growing when nonce become stale, don't take into account stale headers
			// in the challenge request
			removeStaleAuthHeaders(wwwAuthHeader);
//			String uri = wwwAuthHeader.getParameter("uri");
			AuthInfoEntry authInfoEntry = authInfoImpl.getAuthInfo(wwwAuthHeader.getRealm());
			
			if(authInfoEntry == null) throw new SecurityException(
					"Cannot add authorization header. No credentials for the following realm: " + wwwAuthHeader.getRealm());
			
			addChallengeResponse(wwwAuthHeader,
					authInfoEntry.getUserName(),
					authInfoEntry.getPassword(),
					this.getRequestURI().toString());
			
			if(cacheCredentials) {
				getSipSession().getSipSessionSecurity().addCachedAuthInfo(wwwAuthHeader.getRealm(), authInfoEntry);
			}
		}
		
		// Now check for Proxy-Authentication
		authHeaderIterator = 
			response.getHeaders(ProxyAuthenticateHeader.NAME);
		while(authHeaderIterator.hasNext()) {
			ProxyAuthenticateHeader proxyAuthHeader = 
				(ProxyAuthenticateHeader) authHeaderIterator.next();
			// Fix for Issue 1832 : http://code.google.com/p/mobicents/issues/detail?id=1832 
			// Authorization header is growing when nonce become stale, don't take into account stale headers
			// in the challenge request
			removeStaleAuthHeaders(proxyAuthHeader);
//			String uri = wwwAuthHeader.getParameter("uri");
			AuthInfoEntry authInfoEntry = authInfoImpl.getAuthInfo(proxyAuthHeader.getRealm());
			
			if(authInfoEntry == null) throw new SecurityException(
					"No credentials for the following realm: " + proxyAuthHeader.getRealm());
			
			addChallengeResponse(proxyAuthHeader,
					authInfoEntry.getUserName(),
					authInfoEntry.getPassword(),
					this.getRequestURI().toString());
			
			if(cacheCredentials) {
				getSipSession().getSipSessionSecurity().addCachedAuthInfo(proxyAuthHeader.getRealm(), authInfoEntry);
			}
		}
	}

	/*
	 * (non-Javadoc)
	 * @see org.mobicents.javax.servlet.sip.SipServletRequestExt#addAuthHeader(javax.servlet.sip.SipServletResponse, java.lang.String, java.lang.String, boolean)
	 */
	public void addAuthHeader(SipServletResponse challengeResponse,
			String username, String password, boolean cacheCredentials) {
		checkReadOnly();
		SipServletResponseImpl challengeResponseImpl = 
			(SipServletResponseImpl) challengeResponse;
		
		Response response = (Response) challengeResponseImpl.getMessage();
		ListIterator
authHeaderIterator = response.getHeaders(WWWAuthenticateHeader.NAME); // First while(authHeaderIterator.hasNext()) { WWWAuthenticateHeader wwwAuthHeader = (WWWAuthenticateHeader) authHeaderIterator.next(); // Fix for Issue 1832 : http://code.google.com/p/mobicents/issues/detail?id=1832 // Authorization header is growing when nonce become stale, don't take into account stale headers // in the challenge request removeStaleAuthHeaders(wwwAuthHeader); // String uri = wwwAuthHeader.getParameter("uri"); addChallengeResponse(wwwAuthHeader, username, password, this.getRequestURI().toString()); if(cacheCredentials) { getSipSession().getSipSessionSecurity().addCachedAuthInfo(wwwAuthHeader.getRealm(), new AuthInfoEntry(response.getStatusCode(), username, password)); } } authHeaderIterator = response.getHeaders(ProxyAuthenticateHeader.NAME); while(authHeaderIterator.hasNext()) { ProxyAuthenticateHeader proxyAuthHeader = (ProxyAuthenticateHeader) authHeaderIterator.next(); // Fix for Issue 1832 : http://code.google.com/p/mobicents/issues/detail?id=1832 // Authorization header is growing when nonce become stale, don't take into account stale headers // in the challenge request removeStaleAuthHeaders(proxyAuthHeader); String uri = proxyAuthHeader.getParameter("uri"); if(uri == null) uri = this.getRequestURI().toString(); addChallengeResponse(proxyAuthHeader, username, password, uri); if(cacheCredentials) { getSipSession().getSipSessionSecurity().addCachedAuthInfo(proxyAuthHeader.getRealm(), new AuthInfoEntry(response.getStatusCode(), username, password)); } } } /* * Fix for Issue 1832 : http://code.google.com/p/mobicents/issues/detail?id=1832 * Authorization header is growing when nonce become stale, don't take into account stale headers * in the subsequent request * * From RFC 2617 : stale * A flag, indicating that the previous request from the client was * rejected because the nonce value was stale. If stale is TRUE * (case-insensitive), the client may wish to simply retry the request * with a new encrypted response, without reprompting the user for a * new username and password. The server should only set stale to TRUE * if it receives a request for which the nonce is invalid but with a * valid digest for that nonce (indicating that the client knows the * correct username/password). If stale is FALSE, or anything other * than TRUE, or the stale directive is not present, the username * and/or password are invalid, and new values must be obtained. */ protected void removeStaleAuthHeaders(WWWAuthenticateHeader responseAuthHeader) { String realm = responseAuthHeader.getRealm(); ListIterator
authHeaderIterator = message.getHeaders(AuthorizationHeader.NAME); if(authHeaderIterator.hasNext()) { message.removeHeader(AuthorizationHeader.NAME); while(authHeaderIterator.hasNext()) { AuthorizationHeader wwwAuthHeader = (AuthorizationHeader) authHeaderIterator.next(); if(realm != null && !realm.equalsIgnoreCase(wwwAuthHeader.getRealm())) { message.addHeader(wwwAuthHeader); } } } authHeaderIterator = message.getHeaders(ProxyAuthorizationHeader.NAME); if(authHeaderIterator.hasNext()) { message.removeHeader(ProxyAuthorizationHeader.NAME); while(authHeaderIterator.hasNext()) { ProxyAuthorizationHeader proxyAuthHeader = (ProxyAuthorizationHeader) authHeaderIterator.next(); if(realm != null && !realm.equalsIgnoreCase(proxyAuthHeader.getRealm())) { message.addHeader(proxyAuthHeader); } } } } private void addChallengeResponse( WWWAuthenticateHeader wwwAuthHeader, String username, String password, String uri) { int nc = generateNcFromMessage(message); AuthorizationHeader authorization = getSipSession().getSipApplicationSession().getSipContext().getDigestAuthenticator().getAuthorizationHeader( getMethod(), uri, "", // TODO: What is this entity-body? wwwAuthHeader, username, password, wwwAuthHeader.getNonce(), nc); message.addHeader(authorization); } /** * @return the finalResponse */ public SipServletResponse getLastFinalResponse() { return lastFinalResponse; } /** * @param response the finalResponse to set */ public void setResponse(SipServletResponseImpl response) { if(response.getStatus() >= 200 && (lastFinalResponse == null || lastFinalResponse.getStatus() < response.getStatus())) { if(logger.isDebugEnabled()) { logger.debug("last final response " + response + " set on " + this); } this.lastFinalResponse = response; } // we keep the last informational response for noPrackReceived only if(containsRel100(response.getMessage()) && (response.getStatus() > 100 && response.getStatus() < 200) && (lastInformationalResponse == null || lastInformationalResponse.getStatus() < response.getStatus())) { if(logger.isDebugEnabled()) { logger.debug("last informational response " + lastInformationalResponse + " set on " + this); } this.lastInformationalResponse = response; } } /* * (non-Javadoc) * @see javax.servlet.sip.SipServletRequest#getInitialPoppedRoute() */ public Address getInitialPoppedRoute() { return transactionApplicationData.getInitialPoppedRoute(); } /* * (non-Javadoc) * @see javax.servlet.sip.SipServletRequest#getRegion() */ public SipApplicationRoutingRegion getRegion() { return routingRegion; } /** * This method allows the application to set the region that the application * is in with respect to this SipSession * @param routingRegion the region that the application is in */ public void setRoutingRegion(SipApplicationRoutingRegion routingRegion) { this.routingRegion = routingRegion; } /** * {@inheritDoc} */ public URI getSubscriberURI() { return subscriberURI; } public void setSubscriberURI(URI uri) { this.subscriberURI = uri; } /** * Method checking whether or not the sip servlet request in parameter is initial * according to algorithm defined in JSR289 Appendix B * @param sipServletRequest the sip servlet request to check * @param dialog the dialog associated with this request * @return true if the request is initial false otherwise */ private static RoutingState checkRoutingState(SipServletRequestImpl sipServletRequest, Dialog dialog) { // 2. Ongoing Transaction Detection - Employ methods of Section 17.2.3 in RFC 3261 //to see if the request matches an existing transaction. //If it does, stop. The request is not an initial request. if(dialog != null && DialogState.CONFIRMED.equals(dialog.getState())) { return RoutingState.SUBSEQUENT; } // 3. Examine Request Method. If it is CANCEL, BYE, PRACK or ACK, stop. //The request is not an initial request for which application selection occurs. if(NON_INITIAL_SIP_REQUEST_METHODS.contains(sipServletRequest.getMethod())) { return RoutingState.SUBSEQUENT; } // 4. Existing Dialog Detection - If the request has a tag in the To header field, // the container computes the dialog identifier (as specified in section 12 of RFC 3261) // corresponding to the request and compares it with existing dialogs. // If it matches an existing dialog, stop. The request is not an initial request. // The request is a subsequent request and must be routed to the application path // associated with the existing dialog. // If the request has a tag in the To header field, // but the dialog identifier does not match any existing dialogs, // the container must reject the request with a 481 (Call/Transaction Does Not Exist). // Note: When this occurs, RFC 3261 says either the UAS has crashed or the request was misrouted. // In the latter case, the misrouted request is best handled by rejecting the request. // For the Sip Servlet environment, a UAS crash may mean either an application crashed // or the container itself crashed. In either case, it is impossible to route the request // as a subsequent request and it is inappropriate to route it as an initial request. // Therefore, the only viable approach is to reject the request. if(dialog != null && !DialogState.EARLY.equals(dialog.getState())) { return RoutingState.SUBSEQUENT; } // 6. Detection of Requests Sent to Encoded URIs - // Requests may be sent to a container instance addressed to a URI obtained by calling // the encodeURI() method of a SipApplicationSession managed by this container instance. // When a container receives such a request, stop. This request is not an initial request. // Refer to section 15.11.1 Session Targeting and Application Selection //for more information on how a request sent to an encoded URI is handled by the container. //This part will be done in routeIntialRequest since this is where the Session Targeting retrieval is done return RoutingState.INITIAL; } /** * @return the is1xxResponseGenerated */ public boolean is1xxResponseGenerated() { return is1xxResponseGenerated; } /** * @return the isFinalResponseGenerated */ public boolean isFinalResponseGenerated() { return isFinalResponseGenerated; } /** * @return the lastInformationalResponse */ public SipServletResponse getLastInformationalResponse() { return lastInformationalResponse; } public void setReadOnly(boolean isReadOnly) { this.isReadOnly = isReadOnly; } protected void checkReadOnly() { if(isReadOnly) { throw new IllegalStateException(EXCEPTION_MESSAGE); } } @Override public void addAcceptLanguage(Locale locale) { checkReadOnly(); super.addAcceptLanguage(locale); } @Override public void addAddressHeader(String name, Address addr, boolean first) throws IllegalArgumentException { checkReadOnly(); super.addAddressHeader(name, addr, first); } @Override public void addHeader(String name, String value) { checkReadOnly(); super.addHeader(name, value); } @Override public void addParameterableHeader(String name, Parameterable param, boolean first) { checkReadOnly(); super.addParameterableHeader(name, param, first); } @Override public void removeAttribute(String name) { checkReadOnly(); super.removeAttribute(name); } @Override public void removeHeader(String name) { checkReadOnly(); super.removeHeader(name); } @Override public void setAcceptLanguage(Locale locale) { checkReadOnly(); super.setAcceptLanguage(locale); } @Override public void setAddressHeader(String name, Address addr) { checkReadOnly(); super.setAddressHeader(name, addr); } @Override public void setAttribute(String name, Object o) { checkReadOnly(); super.setAttribute(name, o); } @Override public void setCharacterEncoding(String enc) throws UnsupportedEncodingException { checkReadOnly(); super.setCharacterEncoding(enc); } @Override public void setContent(Object content, String contentType) throws UnsupportedEncodingException { checkReadOnly(); super.setContent(content, contentType); } @Override public void setContentLanguage(Locale locale) { checkReadOnly(); super.setContentLanguage(locale); } @Override public void setContentType(String type) { checkReadOnly(); super.setContentType(type); } @Override public void setExpires(int seconds) { checkReadOnly(); super.setExpires(seconds); } @Override public void setHeader(String name, String value) { checkReadOnly(); super.setHeader(name, value); } @Override public void setHeaderForm(HeaderForm form) { checkReadOnly(); super.setHeaderForm(form); } @Override public void setParameterableHeader(String name, Parameterable param) { checkReadOnly(); super.setParameterableHeader(name, param); } /** * {@inheritDoc} */ public String getInitialRemoteAddr() { // https://code.google.com/p/sipservlets/issues/detail?id=255 includes both ACK Support without tx and packet // source ip address if(getMethod().equalsIgnoreCase(Request.ACK)) { if(logger.isTraceEnabled()) { logger.trace("ACK request trying to return the Via address as we don't have a transaction"); } // replaced because wasn't giving correct info for ACK if(message == null || ((SIPRequest)message).getPeerPacketSourceAddress() == null) { return null; } return ((SIPRequest)message).getPeerPacketSourceAddress().getHostAddress(); } else if (message != null && message instanceof SIPRequest && ((SIPRequest)message).getPeerPacketSourceAddress() != null ) { //https://github.com/Mobicents/jain-sip/issues/42 //take advantage of new message methods to extract addr from msg return ((SIPRequest)message).getPeerPacketSourceAddress().getHostAddress(); } else if(getTransaction() != null) { if(logger.isTraceEnabled()) { logger.trace("transaction not null, returning packet source ip address"); } if(((SIPTransaction)getTransaction()).getPeerPacketSourceAddress() != null) { return ((SIPTransaction)getTransaction()).getPeerPacketSourceAddress().getHostAddress(); } else { return ((SIPTransaction)getTransaction()).getPeerAddress(); } } else { if(logger.isTraceEnabled()) { logger.trace("transaction null, returning top via ip address"); } ViaHeader via = (ViaHeader) message.getHeader(ViaHeader.NAME); if(via == null || // https://github.com/Mobicents/sip-servlets/issues/47 // check if the via is container generated, if it is then it means // this is an outgoing request or response and thus should return null !sipFactoryImpl.getSipApplicationDispatcher().isViaHeaderExternal(via) ) { return null; } else { return via.getHost(); } } } /** * {@inheritDoc} */ public int getInitialRemotePort() { // https://code.google.com/p/sipservlets/issues/detail?id=255 includes both ACK Support without tx and packet // source port if(getMethod().equalsIgnoreCase(Request.ACK)) { if(logger.isTraceEnabled()) { logger.trace("ACK request trying to return the Via port as we don't have a transaction"); } // replaced because wasn't giving correct info for ACK if(message == null ) { return -1; } return ((SIPRequest)message).getPeerPacketSourcePort(); } else if (message != null && message instanceof SIPRequest ) { //https://github.com/Mobicents/jain-sip/issues/42 //take advantage of new message methods to extract port from msg return ((SIPRequest)message).getPeerPacketSourcePort(); } else if(getTransaction() != null) { if(logger.isTraceEnabled()) { logger.trace("transaction not null, returning packet source port"); } if(((SIPTransaction)getTransaction()).getPeerPacketSourceAddress() != null) { return ((SIPTransaction)getTransaction()).getPeerPacketSourcePort(); } else { return ((SIPTransaction)getTransaction()).getPeerPort(); } }else { if(logger.isTraceEnabled()) { logger.trace("transaction null, returning top via port"); } ViaHeader via = (ViaHeader) message.getHeader(ViaHeader.NAME); if(via == null || // https://github.com/Mobicents/sip-servlets/issues/47 // check if the via is container generated, if it is then it means // this is an outgoing request or response and thus should return null !sipFactoryImpl.getSipApplicationDispatcher().isViaHeaderExternal(via) ) { return -1; } else { return via.getPort()<=0 ? 5060 : via.getPort(); } } } /** * {@inheritDoc} */ public String getInitialTransport() { if(getTransaction() != null) { return ((SIPTransaction)getTransaction()).getTransport(); } else { ViaHeader via = (ViaHeader) message.getHeader(ViaHeader.NAME); if(via == null || // https://github.com/Mobicents/sip-servlets/issues/47 // check if the via is container generated, if it is then it means // this is an outgoing request or response and thus should return null !sipFactoryImpl.getSipApplicationDispatcher().isViaHeaderExternal(via) ) { return null; } else { return via.getTransport(); } } } public void cleanUp() { // super.cleanUp(); if(transactionApplicationData != null) { transactionApplicationData.cleanUp(); transactionApplicationData = null; } setTransaction(null); poppedRoute =null; poppedRouteHeader = null; routingDirective =null; routingRegion = null; routingState = null; subscriberURI = null; // lastFinalResponse = null; // lastInformationalResponse = null; linkedRequest = null; } public void cleanUpLastResponses() { if(logger.isDebugEnabled()) { logger.debug("cleaning up last responses on " + this); } lastFinalResponse = null; lastInformationalResponse = null; } /* * (non-Javadoc) * @see java.io.Externalizable#readExternal(java.io.ObjectInput) */ public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { super.readExternal(in); String messageString = in.readUTF(); try { message = SipFactoryImpl.messageFactory.createRequest(messageString); } catch (ParseException e) { throw new IllegalArgumentException("Message " + messageString + " previously serialized could not be reparsed", e); } boolean isLinkedRequestSerialized = in.readBoolean(); if (isLinkedRequestSerialized) { linkedRequest = (SipServletRequestImpl) in.readObject(); } createDialog = in.readBoolean(); String routingDirectiveString = in.readUTF(); if(!routingDirectiveString.equals("")) { routingDirective = SipApplicationRoutingDirective.valueOf(routingDirectiveString); } String routingStateString = in.readUTF(); if(!routingStateString.equals("")) { routingState = RoutingState.valueOf(routingStateString); } boolean isRoutingRegionSet = in.readBoolean(); if(isRoutingRegionSet) { routingRegion = (SipApplicationRoutingRegion) in.readObject(); } isInitial = in.readBoolean(); isFinalResponseGenerated = in.readBoolean(); is1xxResponseGenerated = in.readBoolean(); } /* * (non-Javadoc) * @see java.io.Externalizable#writeExternal(java.io.ObjectOutput) */ public void writeExternal(ObjectOutput out) throws IOException { super.writeExternal(out); if(linkedRequest == null) { out.writeBoolean(false); } else { out.writeBoolean(true); out.writeObject(linkedRequest); } out.writeBoolean(createDialog); if(routingDirective != null) { out.writeUTF(routingDirective.toString()); } else { out.writeUTF(""); } if(routingState != null) { out.writeUTF(routingState.toString()); } else { out.writeUTF(""); } if(routingRegion != null) { out.writeBoolean(true); out.writeObject(routingRegion); } else { out.writeBoolean(false); } out.writeBoolean(isInitial); out.writeBoolean(isFinalResponseGenerated); out.writeBoolean(is1xxResponseGenerated); } private static int generateNcFromMessage(Message message) { int nc = 1; CSeqHeader cseq = (CSeqHeader) message.getHeader(CSeqHeader.NAME); if (cseq != null) { nc = (int) (cseq.getSeqNumber() % Integer.MAX_VALUE); } return nc; } /** * Added for Issue 2173 http://code.google.com/p/mobicents/issues/detail?id=2173 * Handle Header [Authentication-Info: nextnonce="xyz"] in sip authorization responses */ public void updateAuthorizationHeaders(boolean useNextNonce) { if(logger.isDebugEnabled()) { logger.debug("Updating authorization headers with nextnonce " + getSipSession().getSipSessionSecurity().getNextNonce()); } int nc = -1; List
authorizationHeaders = new ArrayList
(); // First check for WWWAuthentication headers ListIterator authHeaderIterator = message.getHeaders(AuthorizationHeader.NAME); while(authHeaderIterator.hasNext()) { AuthorizationHeader wwwAuthHeader = (AuthorizationHeader) authHeaderIterator.next(); MobicentsAuthInfoEntry authInfoEntry = getSipSession().getSipSessionSecurity().getCachedAuthInfos().get(wwwAuthHeader.getRealm()); if(authInfoEntry != null) { if(nc < 0) { nc = generateNcFromMessage(message); } String nextNonce = null; if(useNextNonce) { nextNonce = getSipSession().getSipSessionSecurity().getNextNonce(); } else { nextNonce = wwwAuthHeader.getNonce(); } AuthorizationHeader authorization = getSipSession().getSipApplicationSession().getSipContext().getDigestAuthenticator().getAuthorizationHeader( getMethod(), this.getRequestURI().toString(), "", // TODO: What is this entity-body? wwwAuthHeader, authInfoEntry.getUserName(), authInfoEntry.getPassword(), nextNonce, nc); authorizationHeaders.add(authorization); } else { authorizationHeaders.add(wwwAuthHeader); } } // Now check for Proxy-Authentication authHeaderIterator = message.getHeaders(ProxyAuthorizationHeader.NAME); while(authHeaderIterator.hasNext()) { ProxyAuthorizationHeader proxyAuthHeader = (ProxyAuthorizationHeader) authHeaderIterator.next(); // String uri = wwwAuthHeader.getParameter("uri"); MobicentsAuthInfoEntry authInfoEntry = getSipSession().getSipSessionSecurity().getCachedAuthInfos().get(proxyAuthHeader.getRealm()); if(authInfoEntry != null) { if(nc < 0) { nc = generateNcFromMessage(message); } String nextNonce = null; if(useNextNonce) { nextNonce = getSipSession().getSipSessionSecurity().getNextNonce(); } else { nextNonce = proxyAuthHeader.getNonce(); } AuthorizationHeader authorization = getSipSession().getSipApplicationSession().getSipContext().getDigestAuthenticator().getAuthorizationHeader( getMethod(), this.getRequestURI().toString(), "", // TODO: What is this entity-body? proxyAuthHeader, authInfoEntry.getUserName(), authInfoEntry.getPassword(), nextNonce, nc); authorizationHeaders.add(authorization); } else { authorizationHeaders.add(proxyAuthHeader); } } message.removeHeader(AuthorizationHeader.NAME); message.removeHeader(ProxyAuthorizationHeader.NAME); for(Header header : authorizationHeaders) { message.addHeader(header); } } // Issue 2354 : need to clone the original request to create the forked response public Object clone() { SipServletRequestImpl sipServletRequestImpl = (SipServletRequestImpl) sipFactoryImpl.getMobicentsSipServletMessageFactory().createSipServletRequest((Request)message, sipSession, getTransaction(), null, createDialog); sipServletRequestImpl.setLinkedRequest(linkedRequest); sipServletRequestImpl.setPoppedRoute(poppedRouteHeader); sipServletRequestImpl.setSubscriberURI(subscriberURI); sipServletRequestImpl.setAttributeMap(getAttributeMap()); return sipServletRequestImpl; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy