/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 1997-2009 Sun Microsystems, Inc. All rights reserved.
* Copyright (c) Ericsson AB, 2004-2008. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can obtain
* a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
* or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
* Sun designates this particular file as subject to the "Classpath" exception
* as provided by Sun in the GPL Version 2 section of the License file that
* accompanied this code. If applicable, add the following below the License
* Header, with the fields enclosed by brackets [] replaced by your own
* identifying information: "Portions Copyrighted [year]
* [name of copyright owner]"
*
* Contributor(s):
*
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package com.ericsson.ssa.sip;
import com.ericsson.ssa.config.annotations.Configuration;
import com.ericsson.ssa.config.annotations.UsagePolicy;
import com.ericsson.ssa.container.SipBindingCtx;
import com.ericsson.ssa.container.SipBindingListener;
import com.ericsson.ssa.container.SipBindingResolver;
import com.ericsson.ssa.container.reporter.ReporterResolver;
import com.ericsson.ssa.container.reporter.Reporter;
import com.ericsson.ssa.container.startup.SipMonitoring;
import com.ericsson.ssa.sip.PathNode.Type;
import com.ericsson.ssa.sip.dialog.DialogCleaner;
import com.ericsson.ssa.sip.dialog.DialogLifeCycle;
import com.ericsson.ssa.sip.dns.SipTransports;
import com.ericsson.ssa.sip.dns.TargetTuple;
import com.ericsson.ssa.sip.persistence.IncompleteDialogException;
import com.ericsson.ssa.sip.transaction.TransactionManager;
import org.jvnet.glassfish.comms.util.LogUtil;
import java.util.Iterator;
import java.util.ListIterator;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.sip.Address;
import javax.servlet.sip.ServletParseException;
import javax.servlet.sip.SipURI;
import javax.servlet.sip.URI;
/**
* A Singleton that stores SipSessions and dialogs and fetches them upon
* request.
*
* @author ehsroha
* @reviewed ejoelbi 2007-jan-17
*/
public class DialogManager implements Layer {
private static final Logger m_Log = LogUtil.SIP_LOGGER.getLogger();
private static DialogManager m_Instance = null;
private static Class m_FactoryClass = null;
// needed for dialog creational NOTIFY request
private Layer m_NextLayer = null;
private SipURIImpl m_URI = null;
private SipURIImpl m_SURI = null;
private boolean defaultTCPTransport = false;
private AtomicLong m_EasFailedSipDialogs = new AtomicLong();
private AtomicLong m_EasSuccessfulSipDialogs = new AtomicLong();
private Reporter _reporter;
private volatile boolean strictFid = false;
/**
* private since its is a Singleton
*/
protected DialogManager() {
}
public void setReporters(String reporters) {
_reporter = ReporterResolver.getInstance().getReporter(reporters);
}
public Reporter getReporter() {
return _reporter;
}
/**
* Initializes this Singleton via the double checked locking pattern to
* minimize performance penelty since its accessed often.
*/
public void start() {
establishContactURIs();
SipBindingResolver.instance().registerSipBindingListener(new SipBindingListener() {
public void newSipBindingCtxAvaliable(String context) {
//ignore
}
public void sipBindingCtxUpdated(String context) {
//ignore
}
public void publicSipBindingCtxUpdated() {
establishContactURIs();
}
public void sipBindingCtxRemoved(String context) {
//ignore
}
});
if (m_Log.isLoggable(Level.FINE)) {
m_Log.log(Level.FINE, "Started DialogManager");
}
}
public SipURIImpl getVipSipUri() {
return (SipURIImpl) m_URI.clone();
}
public SipURIImpl getVipSipsUri() {
return (SipURIImpl) m_SURI.clone();
}
private void establishContactURIs() {
//TODO Deal with failures and lease expired.
// Build a Contact URI and a header
m_URI = null;
m_SURI = null;
SipURIImpl[] ifs = SipBindingResolver.getInterfaces();
//Find first UDP || TCP ||TLS
for (int i = 0; i < ifs.length; i++) {
if ((m_SURI == null) && ifs[i].isSecure()) {
m_SURI = ifs[i];
} else if ((m_URI == null) && defaultTCPTransport &&
ifs[i].getTransportParam().equalsIgnoreCase("TCP")) {
m_URI = ifs[i];
} else if ((m_URI == null) && !defaultTCPTransport &&
ifs[i].getTransportParam().equalsIgnoreCase("UDP")) {
m_URI = (SipURIImpl) ifs[i].clone();
m_URI.removeParameter("transport");
}
}
if (m_Log.isLoggable(Level.FINE)) {
m_Log.log(Level.FINE,
"DialogManager started or reinitialized: sip URI: " + m_URI +
", sips URI: " + m_SURI +
(defaultTCPTransport ? ", using TCP as default transport." : "."));
}
}
public void addContact(SipServletRequestImpl req) {
//12.1.2 If Req URI contains sips or top route then contact must be sips
//UAC behaviour
SipURIImpl uri = getSipOrSipsURI(req);
Header contactHeader = new MultiLineHeader(Header.CONTACT, true);
contactHeader.setValue("<" + uri.toString() + ">", false);
req.addHeader(contactHeader);
req.indicateContact();
}
private boolean shouldShowFragmentId(SipServletMessageImpl msg) {
return true;
}
public void addContact(SipServletResponseImpl resp) {
//12.1.1 Contact should be sips if:
//Req uri of the request was sips or
//Top most RR was sips and if none present the contact was sips
//UAS behaviour
DialogFragment df = resp.getDialog();
URI nextTarget = null;
if ((resp.getRequestImpl() != null) &&
(resp.getRequestImpl().getRequestURI() != null) &&
resp.getRequestImpl().getRequestURI().isSipURI()) {
nextTarget = resp.getRequestImpl().getRequestURI();
}
if ((nextTarget != null) && (!((SipURI) nextTarget).isSecure())) {
Header rr = resp.getRawHeader(Header.RECORD_ROUTE);
try {
if ((rr != null) && rr.getAddressValue().getURI().isSipURI()) {
nextTarget = rr.getAddressValue().getURI();
} else { //Get the other
Header c = resp.getRequestImpl().getRawHeader(Header.CONTACT);
if ((c != null) && (c.getAddressValue() != null)) {
nextTarget = c.getAddressValue().getURI();
}
}
} catch (ServletParseException spe) {
if (m_Log.isLoggable(Level.FINE)) {
m_Log.log(Level.FINE, "Failed eval 12.1.1 rules", spe);
}
}
}
SipURIImpl uri = null;
if (nextTarget != null && nextTarget.isSipURI())
{
SipURI targetUri = (SipURI) nextTarget;
boolean isTransportParamTls =
targetUri.getTransportParam() != null &&
targetUri.getTransportParam().equalsIgnoreCase("tls");
if (targetUri.isSecure() || isTransportParamTls)
{
uri = (SipURIImpl) m_SURI.clone();
// Do not use SIPS URI scheme if transport=TLS
if (isTransportParamTls)
{
uri.setSecure(false);
}
}
else
{
uri = (SipURIImpl) m_URI.clone();
}
} else {
uri = (SipURIImpl) m_URI.clone();
}
if ((df != null) && shouldShowFragmentId(resp)) {
uri.setParameter(SipURIImpl.FRAGID_PARAM, df.getFragmentId());
}
Header contactHeader = new MultiLineHeader(Header.CONTACT, true);
contactHeader.setValue("<" + uri.toString() + ">", false);
resp.addHeader(contactHeader);
resp.indicateContact();
}
/**
* Adds the default Path to the message
*
* @param req
* the message that will get the Path header set
* @throws ServletParseException thrown in case the Path URI is not valid
*/
public void addPath(SipServletRequestImpl req) {
Header pathHeader;
pathHeader = req.getRawHeader(Header.PATH);
if (pathHeader == null) {
pathHeader = new MultiLineHeader(Header.PATH, true);
req.addHeader(pathHeader);
}
SipURIImpl uri = getSipOrSipsURI(req);
uri.setLrParam(true);
setPathParams(req, uri);
pathHeader.setValue("<" + uri.toString() + ">", true);
// Add Requires header if needed
boolean pathRequired = false;
ListIterator<String> supportedList = req.getHeaders(Header.REQUIRE);
String supported = null;
while (supportedList.hasNext()) {
supported = (String) supportedList.next();
if (supported.equals("path")) {
pathRequired = true;
}
}
if (!pathRequired) {
req.addHeader(Header.REQUIRE, "path");
}
}
private void setPathParams(SipServletRequestImpl req, SipURIImpl uri) {
for (Iterator iter = req.getPathURIParamNames(); iter.hasNext();) {
String name = (String) iter.next();
uri.setParameter(name, req.getPathURIParam(name));
}
}
/**
* Adds the default record route to the message
*
* @param req
* the message that will get the record route header
* @throws ServletParseException thrown in case thye Record Route URI is not valid
*/
public void addRecordRoute(SipServletRequestImpl req) {
Header rrHeader;
rrHeader = req.getRawHeader(Header.RECORD_ROUTE);
if (rrHeader == null) {
rrHeader = new MultiLineHeader(Header.RECORD_ROUTE, true);
req.addHeader(rrHeader);
}
SipURIImpl uri = getSipOrSipsURI(req);
uri.setLrParam(true);
setRecordRouteParams(req, uri);
rrHeader.setValue("<" + uri.toString() + ">", true);
}
private void setRecordRouteParams(SipServletRequestImpl req, SipURIImpl uri) {
for (Iterator iter = req.getRecordRouteURIParamNames(); iter.hasNext();) {
String name = (String) iter.next();
uri.setParameter(name, req.getRecordRouteURIParam(name));
}
}
/**
* Checks if a sip or sips URI should be used.
* Returns a URI pointing to this container that can be used as Record-Route and Path header.
* The returned URI will contain DialogFragment information if needed.
* @param req the request to check
* @return a URI pointing to this container that can be used as a valid header
*/
private SipURIImpl getSipOrSipsURI(SipServletRequestImpl req) {
DialogFragment df = req.getDialog();
//Do the rfc 3261 sips check 16.6 (4,6,7)
URI nextTarget = null;
Header r = req.getRawHeader(Header.ROUTE);
if (r == null) { //No route headers, next hop ReqURI
nextTarget = req.getRequestURI();
} else {
//TODO maybe not a SipURI!
try {
if (r.getAddressValue() != null) {
nextTarget = r.getAddressValue().getURI();
}
} catch (ServletParseException ignore) {
}
}
SipURIImpl uri = null;
if (nextTarget != null && nextTarget.isSipURI())
{
SipURI targetUri = (SipURI) nextTarget;
boolean isTransportParamTls =
targetUri.getTransportParam() != null &&
targetUri.getTransportParam().equalsIgnoreCase("tls");
if (targetUri.isSecure() || isTransportParamTls)
{
uri = (SipURIImpl) m_SURI.clone();
// Do not use SIPS URI scheme if transport=TLS
if (isTransportParamTls)
{
uri.setSecure(false);
}
}
else
{
uri = (SipURIImpl) m_URI.clone();
}
} else {
uri = (SipURIImpl) m_URI.clone();
}
if ((df != null) && shouldShowFragmentId(req)) {
uri.setParameter(SipURIImpl.FRAGID_PARAM, df.getFragmentId());
}
return uri;
}
/**
* Attaches the session and dialog to the incoming request if found otherwise
* an error response is returned.
*
* @param req
* the incoming request
* @return null if session is recovered otherwise the error response Call
* Leg/Transaction does not exist (481).
*/
private SipServletResponseImpl setDialogContext(SipServletRequestImpl req)
throws IllegalStateException {
SipServletResponseImpl resp = null;
SipSessionBase s = null;
try {
s = getSession(req);
} catch (RemoteLockRuntimeException e) {
if (!req.getMethod().equals("ACK")) {
// Status code (481) indicating Call Leg/Transaction does not exist.
resp = req.createTerminatingResponse(500);
resp.setHeader(Header.RETRY_AFTER, "5");
return resp;
}
} catch (RemoteLockException e) {
if (!req.getMethod().equals("ACK")) {
// Status code (481) indicating Call Leg/Transaction does not exist.
resp = req.createTerminatingResponse(500);
resp.setHeader(Header.RETRY_AFTER, "5");
return resp;
}
} catch (IncompleteDialogException e) {
// Status code (481) indicating Call Leg/Transaction does not exist.
resp = req.createTerminatingResponse(481);
return resp;
}
if ((s != null) && s.isValid()) {
req.setSession(s);
} else {
if (!req.getMethod().equals("ACK")) {
// Status code (481) indicating Call Leg/Transaction does not exist.
resp = req.createTerminatingResponse(481);
} else {
throw new IllegalStateException(
"Call Leg/Transaction does not exist for ACK with callId = " +
req.getCallId() + ", from = " + req.getFromImpl() +
", to = " + req.getToImpl());
}
}
return resp;
}
/**
* Sets the request to initial or subsequent
*
* @param req
* the request to set
* @throws ServletParseException
*/
public void next(SipServletRequestImpl req) {
SipServletResponseImpl resp = null;
req.pushTransactionDispatcher(this);
req.pushApplicationDispatcher(this);
// Look at targetting session with Join/Replace. Even if the
// request is initial, we need to find the SAS.
try {
SessionTarget.setup(req);
} catch (SessionTargetException set) {
if (m_Log.isLoggable(Level.FINE)) {
m_Log.log(Level.FINE, set.getLocalizedMessage());
}
resp = req.createTerminatingResponse(set.getErrorCode());
if (resp != null) {
resp.popDispatcher().dispatch(resp);
return;
}
}
if (!req.isInitial()) {
try {
resp = setDialogContext(req);
} catch (IllegalStateException e) {
// Call Leg/Transaction does not exist for request
if (m_Log.isLoggable(Level.FINE)) {
m_Log.log(Level.FINE,
"Call Leg/Transaction does not exist for request " +
req.getMethod() + " with callId = " + req.getCallId() +
", from = " + req.getFromImpl() + ", to = " +
req.getToImpl());
}
return;
}
if (resp != null) {
resp.popDispatcher().dispatch(resp);
return;
}
} else {
if (SipFactoryImpl.isDialogCreational(req.getMethod())) {
if (req.getHeader(Header.CONTACT) == null) {
// Respond with error code 400 because of the contact header
// is missing for a dialog creational request.
resp = req.createTerminatingResponse(400,
"Missing Contact header field");
resp.popDispatcher().dispatch(resp);
return;
} else if (!isValidContact(req.getRawHeader(Header.CONTACT))) {
// Respond with error code 400 because of the contact header
// is invalid.
// TR HH52078
resp = req.createTerminatingResponse(400,
"Invalid Contact header field");
if (resp == null) {
return;
}
resp.popDispatcher().dispatch(resp);
return;
}
} else if (!SipFactoryImpl.initialRequestPossible(req.getMethod())) {
if (m_Log.isLoggable(Level.FINE)) {
m_Log.log(Level.FINE,
"Received an Initial Request, method is" +
req.getMethod());
}
if (!"ACK".equals(req.getMethod())) {
resp = req.createTerminatingResponse(481);
resp.popDispatcher().dispatch(resp);
}
return;
}
}
DialogFragment df = req.getDialog();
boolean isLocked = false;
try {
if (!req.isInitial() && df != null) {
df.obtainLockForIncomingMessage();
isLocked = true;
// Check if INVITE dialog is confirmed
if ("ACK".equals(req.getMethod()) && (df != null)) {
try {
df.setConfirmed();
} catch (RemoteLockRuntimeException e) {
if (m_Log.isLoggable(Level.FINE)) {
m_Log.log(Level.FINE,
"The dialog was remotely locked when the ACK arrived; the ACK is dropped.");
}
return;
}
}
pushApplicationDispatchers(req);
}
LayerHelper.next(req, this, m_NextLayer);
} finally {
if(isLocked) {
df.releaseLockForIncomingMessage();
}
}
}
private boolean isValidContact(Header header) {
// Verify the address spec is correct.
try {
Address adr = header.getAddressValue();
adr.toString();
} catch (ServletParseException e) {
return false;
}
return true;
}
/**
* Replaces PathNodes of the response transactionStack.
*
* @param resp
* @param d
*/
private void replaceTranscationPath(SipServletResponseImpl resp,
DialogFragment d) {
// need to update transaction path since there are new nodes.
for (int i = 0; i < d.size(); i++) {
// lets remove old nodes..
resp.popDispatcher();
}
// need to take it in reverse order since its a stack.
PathNode p = null;
Iterator<PathNode> i = d.getCallee2CallerPath();
while (i.hasNext()) {
// ...and replace them with new nodes
p = i.next();
resp.pushTransactionDispatcher(p);
}
}
public void next(SipServletResponseImpl resp) {
// associate response with session and dialog, clone dialog if
// necessary...
SipServletRequestImpl req = resp.getRequestImpl();
DialogFragment dialog = req.getDialog();
if (dialog != null && dialog.isValid()) {
String respToTag = resp.getToImpl()
.getParameter(AddressImpl.TAG_PARAM);
if ((respToTag == null) && !(resp.getStatus() == 100)) {
// Except for 100 Trying, all responses MUST have a to-tag
// to be complying to RFC3261 8.2.6.2
// Drop the response.
if (m_Log.isLoggable(Level.WARNING)) {
m_Log.log(Level.WARNING,
"sip.stack.dialog.response_without_totag");
}
return;
}
//
// A dialog-establishing NOTIFY request might
// already have created and stored a dialog
//
if (req.getMethod().equals("SUBSCRIBE") && req.isInitial()) {
DialogSet ds = dialog.getDialogSet();
DialogFragment clonedOrFetched = dialog;
if (clonedOrFetched.getToTag() == null) {
// The dialog was not confirmed by a speedy NOTIFY
boolean success = false;
// fetch stored dialog in set,
// lets use fragmentId since it might be a spiral
clonedOrFetched = ds.getDialog(req.getFragmentId());
// is it already occupied?
if (clonedOrFetched != null) {
// TODO qbinjoe: Change so that DS registers and clones
success = clonedOrFetched.tryToSetToTagAndRegisterDialog(respToTag,
true);
}
if (!success) {
// need to clone dialog
// TODO qbinjoe Shouldn't we try to set to-tag and register dialog here?
clonedOrFetched = ds.cloneDialog(req.getFragmentId());
// clonedOrFetched.tryToSetToTagAndRegisterDialog(respToTag,
// true);
}
}
resp.setDialog(clonedOrFetched);
resp.copyTransactionStack();
// it might be same dialog as from original request...
if (clonedOrFetched != dialog) {
if (clonedOrFetched != null) {
replaceTranscationPath(resp, clonedOrFetched);
} else {
if (m_Log.isLoggable(Level.FINE)) {
Iterator<DialogFragment> allDialogFragments = ds.getDialogs();
int counter = 0;
while (allDialogFragments.hasNext()) {
counter++;
}
if (m_Log.isLoggable(Level.FINE)) {
m_Log.log(Level.FINE,
"[clonedOrFetched=" + clonedOrFetched +
", FromTag=" + ds.getFromTag() +
", FragmentId=" + req.getFragmentId() +
", allDialogFragments size=" + counter +
" FragmentId=" + dialog.getFragmentId());
}
}
// No dialogFragment exist.
return;
}
}
} else if (req.isInitial() && (dialog.getToTag() != null) &&
!dialog.getToTag().equals(respToTag)) {
String id = DialogFragment.createKey(resp.getCallId(),
dialog.getDialogSet().getFromTag(), respToTag,
req.getFragmentId());
try {
DialogFragment clonedOrFetched = DialogFragmentManager.getInstance()
.findDialogFragment(id);
if (clonedOrFetched == null) {
// need to clone dialog
clonedOrFetched = (DialogFragment) dialog.clone();
}
resp.setDialog(clonedOrFetched);
resp.copyTransactionStack();
replaceTranscationPath(resp, clonedOrFetched);
} catch (RemoteLockException ex) {
// The dialog was locked (could only happen if response was too late).
// Drop the response.
if (m_Log.isLoggable(Level.FINE)) {
m_Log.log(Level.FINE,
"sip.stack.dialog.remotely_locked_dialog_at_response");
}
return;
}
} else {
resp.setDialog(dialog);
resp.copyTransactionStack();
}
SipServletRequestImpl transReq = req.getTransactionRequest();
if (transReq != null) {
resp.setRequest(transReq);
resp.setSession(transReq.getSessionImpl());
} else {
resp.setSession(req.getSessionImpl());
}
if (resp.getDialog() != null) {
resp.getDialog().getDialogLifeCycle().setThreadLocalUnitOfWork();
}
DialogFragment df = resp.getDialog();
try {
if (df != null) {
df.obtainLockForIncomingMessage();
}
LayerHelper.next(resp, this, m_NextLayer);
} finally {
if (df != null) {
df.releaseLockForIncomingMessage();
}
}
// Check if non-INVITE dialog is confirmed
// NOTE, this is done after the application has been invoked.
if (resp.getRequest().isInitial() &&
SipFactoryImpl.isDialogCreational(
resp.getRequest().getMethod()) &&
!"INVITE".equals(resp.getRequest().getMethod()) &&
(resp.getStatus() >= 200) && (resp.getStatus() < 300)) {
if (resp.getDialog() != null) {
try {
resp.getDialog().setConfirmed();
} catch (RemoteLockRuntimeException e) {
if (m_Log.isLoggable(Level.FINE)) {
m_Log.log(Level.FINE,
"The dialog was remotely locked when the response arrived; probably the response was late; the response is dropped.");
}
}
}
}
} else {
// no valid dialog exist, lets return and go back...
if (m_Log.isLoggable(Level.FINE) && dialog != null) {
m_Log.log(Level.FINE,
"The dialog is invalid. Response is not routed to servlet: "+ resp.toDebugString());
}
resp.copyTransactionStack();
dispatch(resp);
}
}
public void registerNext(Layer layer) {
m_NextLayer = layer;
}
public void dispatch(SipServletRequestImpl req) {
if (m_Log.isLoggable(Level.FINEST)) {
m_Log.log(Level.FINEST, req.toDebugString());
}
// add Record Route if its enabled and no contact exist
if (!req.isContactIndicated() && req.isRecordRouteIndicated()) {
addRecordRoute(req);
OutboundInterface.modifyRecordRoute(req);
}
// Make sure that the protocol in Contact is correct
try {
OutboundInterface.modifyContact(req);
req.validateContact();
} catch (ServletParseException e) {
if (m_Log.isLoggable(Level.WARNING)) {
m_Log.log(Level.WARNING, "Failed to adjust Contact ", e);
}
}
// add Path header if it is enabled
if (req.isPathIndicated()) {
addPath(req);
OutboundInterface.modifyPath(req);
}
//req.handleAssertedIdentity();
// lets register the early dialog...
DialogSet.registerEarlyDialog(req);
// Check if INVITE dialog has been confirmed
if ("ACK".equals(req.getMethod()) && (req.getDialog() != null)) {
try {
req.getDialog().setConfirmed();
} catch (RemoteLockRuntimeException e) {
if (m_Log.isLoggable(Level.FINE)) {
m_Log.log(Level.FINE,
"The dialog was remotely locked when the ACK was sent; the ACK is dropped.");
}
return;
}
}
// done...
req.popDispatcher().dispatch(req);
}
public void dispatch(SipServletResponseImpl resp) {
if (m_Log.isLoggable(Level.FINEST)) {
m_Log.log(Level.FINEST, resp.toDebugString());
}
if (resp.getRequest().isInitial() &&
SipFactoryImpl.isDialogCreational(resp.getRequest().getMethod())) {
if ((resp.getStatus() >= 200) && (resp.getStatus() < 300)) {
// OK
if (SipMonitoring.isEnabled(SipMonitoring.SESSION_MANAGER)) {
m_EasSuccessfulSipDialogs.incrementAndGet();
}
// Check if non-INVITE dialog is confirmed
if ((resp.getDialog() != null) &&
!"INVITE".equals(resp.getRequest().getMethod())) {
try {
resp.getDialog().setConfirmed();
} catch (RemoteLockRuntimeException e) {
if (m_Log.isLoggable(Level.FINE)) {
m_Log.log(Level.FINE,
"The dialog was remotely locked when the response was sent; the response is dropped.");
}
return;
}
}
} else if ((resp.getStatus() >= 400) && (resp.getStatus() < 700)) {
if (SipMonitoring.isEnabled(SipMonitoring.SESSION_MANAGER)) {
// FAILED
m_EasFailedSipDialogs.incrementAndGet();
}
}
}
OutboundInterface.modifyContact(resp);
resp.popDispatcher().dispatch(resp);
}
/**
* Returns the instance of the SessionManager
*
* @return the instance of the SessionManager
*/
public static DialogManager getInstance() {
if (m_Instance == null) {
synchronized (DialogManager.class) {
if (m_Instance == null) {
if (m_FactoryClass == null) {
m_Instance = new DialogManager();
} else {
try {
m_Instance = (DialogManager) m_FactoryClass.newInstance();
} catch (Exception e) {
m_Log.log(Level.SEVERE,
"Failed to instantiate factory class : ", e);
}
}
}
}
}
return m_Instance;
}
/**
* Returns the instance of the SessionManager
*
* @return the instance of the SessionManager
*/
public static void initInstance(Class instance) {
if (m_Log.isLoggable(Level.FINE)) {
m_Log.log(Level.FINE,
"instantiates this layer with class = " +
instance.getCanonicalName());
}
if (m_Instance == null) {
m_FactoryClass = instance;
} else {
throw new IllegalStateException(
"Can not init class after the factory has given a reference away!");
}
}
/**
* Fetches the early dialog and clones it.
*
* @param m
* the NOTIFY request
* @return the clone of the early dialog if found otherwise null
*/
private DialogFragment createDialogByCloningEarlyDialog(
SipServletMessageImpl m) {
DialogFragment d = null;
if (m.getMethod().equals("NOTIFY") && DialogSet.earlyDialogsExists()) {
// According to RFC3265, 3.3.4 "to header of NOTIFY
// should match from header of SUBSCRIBE"
String toTag = m.getToImpl().getParameter(AddressImpl.TAG_PARAM);
String id = DialogSet.createKey(m.getCallId(), toTag);
DialogSet ds = DialogSet.getEarlyDialog(id);
if (ds != null) {
boolean success = false;
// fetch stored dialog in set,
// lets use fragmentId since it might be a spiral
d = ds.getDialog(m.getFragmentId());
// is it already occupied?
if (d != null) {
// TODO qbinjoe: Change so that DS registers and clones
success = d.tryToSetToTagAndRegisterDialog(m.getFromImpl()
.getParameter(AddressImpl.TAG_PARAM),
true);
}
if (!success) {
d = ds.cloneDialog(m.getFragmentId());
}
m.setDialog(d);
}
}
return d;
}
/**
* According to RFC3265, 3.3.4 Dialog creation and termination: a matching
* response or NOTIFY will establish a dialog. This method supports the
* creation of dialog using a NOTIFY request and returns a established
* session if found otherwise null.
*
* @param m
* the NOTIFY request
* @return the session which now is established or null if not found
*/
private SipSessionBase getEarlySession(SipServletRequestImpl req) {
SipSessionBase s = null;
DialogFragment d = createDialogByCloningEarlyDialog(req);
if (d != null) {
Iterator<PathNode> iter = null;
PathNode p = null;
req.setDirection(Type.Callee);
req.setDialog(d);
iter = d.getCaller2CalleePath();
// add the dispatchers
while (iter.hasNext()) {
p = iter.next();
req.pushApplicationDispatcher(p);
}
s = p.getSipSession();
}
return s;
}
private void pushApplicationDispatchers(SipServletRequestImpl req) {
String fromTag = req.getFromImpl().getParameter(AddressImpl.TAG_PARAM);
Iterator<PathNode> iter = null;
DialogFragment d = req.getDialog();
if (d != null) {
if (fromTag.equals(d.getFromTag())) {
iter = d.getCallee2CallerPath();
} else {
iter = d.getCaller2CalleePath();
}
// push the dispatchers
while (iter.hasNext()) {
PathNode p = iter.next();
SipSessionBase sTmp = p.getSipSession();
if (sTmp != null && sTmp.isValid()) {
req.pushApplicationDispatcher(p);
}
}
}
}
/**
* Fetches a established session for incoming subsequent requests.
*
* @param req
* the subsequent request
* @return the established session or null if not found
* @throws RemoteLockException, RemoteLockRuntimeException
*/
public SipSessionBase getSession(SipServletRequestImpl req)
throws RemoteLockException, RemoteLockRuntimeException,
IncompleteDialogException {
// 1. fetch matching dialog [callId, to-tag, from-tag]
// 2. fetch the first path node from the application path of the dialog
// depending on the direction (caller to callee or vice versa)
// 3. Return the SipSession of the path node.
SipSessionBase s = null;
String toTag = null;
String fromTag = null;
DialogFragment d = null;
Iterator<PathNode> iter = null;
PathNode p = null;
// we need the fragmentId here
String fragmentId = req.getFragmentId();
if (fragmentId.equals(DialogFragment.DEFAULT_FRAGMENT_ID) &&
!getStrictFid()) {
fragmentId = DialogSet.hardCodedDefaultFragmentId();
if (m_Log.isLoggable(Level.FINE)) {
m_Log.log(Level.FINE,
"fid undefined for message " + req.getMethod() +
". Attempt using fid = " + fragmentId);
}
}
// fetch to-tag & from-tag
toTag = req.getToImpl().getParameter(AddressImpl.TAG_PARAM);
fromTag = req.getFromImpl().getParameter(AddressImpl.TAG_PARAM);
if ("NOTIFY".equals(req.getMethod())) {
String id = DialogSet.createKey(req.getCallId(), toTag);
DialogSet ds = DialogSet.getEarlyDialog(id);
if (ds != null) {
DialogFragment df = ds.getDialog(fragmentId);
if (df.getToTag() == null) {
// this is a speedy NOTIFY
s = getEarlySession(req);
}
}
}
if (s == null) {
if (!req.isInitial()) {
String id = DialogFragment.createKey(req.getCallId(), toTag,
fromTag, fragmentId);
d = DialogFragmentManager.getInstance().findDialogFragment(id);
if ((d != null) &&
((d.isValid() == false) ||
(d.isValidForIncomingTraffic() == false))) {
d = null;
}
}
if (d != null) {
if (fromTag.equals(d.getFromTag())) {
req.setDirection(Type.Caller);
iter = d.getCallee2CallerPath();
} else {
req.setDirection(Type.Callee);
iter = d.getCaller2CalleePath();
}
req.setDialog(d);
boolean foundValidSS = false;
while (iter.hasNext()) {
p = iter.next();
SipSessionBase sTmp = p.getSipSession();
if (sTmp != null && sTmp.isValid()) {
// even if a valid session was found, iterate fully
// to see if the DS is remotely locked.
if (!foundValidSS) {
s = sTmp;
foundValidSS = true;
}
}
}
}
}
DialogFragment dialog = req.getDialog();
if ((dialog != null) && dialog.isValid()) {
DialogLifeCycle dialogLifeCycle = dialog.getDialogLifeCycle();
dialogLifeCycle.associateTransaction(req.getTransactionId());
dialogLifeCycle.initUnitOfWork();
dialogLifeCycle.setThreadLocalUnitOfWork();
}
return s;
}
/**
* Sets the value for this parameter, this is done via reflection when
* starting Layer is started.
*
* @param defaultTCPTransport
* true if TCP should be used false if UDP should be used.
*/
@Configuration(key = "DefaultTcpTransport", node = "/SipService/SipProtocol")
public void setDefaultTCPTransport(Boolean aDefaultTCPTransport) {
defaultTCPTransport = aDefaultTCPTransport;
}
@Configuration(key = "strictFid", node = "/SipContainer", usage = UsagePolicy.IGNORE)
public void setStrictFid(boolean value) {
strictFid = value;
}
private boolean getStrictFid() {
return strictFid;
}
/*
public Header getContactHeader()
{
return m_ContactHeader;
}
*/
public long getEasFailedSipDialogs() {
return m_EasFailedSipDialogs.longValue();
}
public long getEasExpiredSipDialogs() {
return DialogFragmentManager.getInstance().getEasExpiredSipDialogs();
}
public long getEasSuccessfulSipDialogs() {
return m_EasSuccessfulSipDialogs.longValue();
}
public long getEasTotalSipDialogCount() {
return DialogFragmentManager.getInstance().getEasTotalSipDialogCount();
}
public long getEasTotalSipDialogLifeTime() {
return DialogFragmentManager.getInstance().getEasTotalSipDialogLifeTime();
}
public long getEasConcurrentSipDialogs() {
return DialogFragmentManager.getInstance().getEasConcurrentSipDialogs();
}
public long getEasHeapMemoryUsage() {
return Runtime.getRuntime().totalMemory() -
Runtime.getRuntime().freeMemory();
}
}