/*
* 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.LayerHandler;
import com.ericsson.ssa.config.ConvergedContext;
import com.ericsson.ssa.container.SipBindingResolver;
import com.ericsson.ssa.container.sim.ServletDispatcher;
import com.ericsson.ssa.sip.PathNode.Type;
import com.ericsson.ssa.sip.dialog.Cleanable;
import com.ericsson.ssa.sip.dialog.DialogCleaner;
import com.ericsson.ssa.utils.UUIDUtil;
import com.sun.enterprise.util.LocalStringManagerImpl;
import org.apache.catalina.session.SessionLock;
import org.jvnet.glassfish.comms.util.LogUtil;
import org.jvnet.glassfish.comms.deployment.backend.SipApplication;
import java.io.IOException;
import java.io.Externalizable;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import javax.servlet.ServletException;
import javax.servlet.ServletContext;
import javax.servlet.sip.Address;
import javax.servlet.sip.ProxyBranch;
import javax.servlet.sip.SipApplicationSession;
import javax.servlet.sip.SipServletMessage;
import javax.servlet.sip.SipServletRequest;
import javax.servlet.sip.SipServletResponse;
import javax.servlet.sip.SipSessionEvent;
import javax.servlet.sip.SipSessionListener;
import javax.servlet.sip.SipURI;
import javax.servlet.sip.UAMode;
import javax.servlet.sip.URI;
import javax.servlet.sip.ar.SipApplicationRoutingRegion;
/**
* The SipSession implementation represents the SSA point-to-point SIP
* relationship which is almost equal to a SIP dialog (it extends the dialog
* definition, see specification). Important that this class will use getters
* and setters internally since it should be possible to serialize the content
* to external media.
*/
public abstract class SipSessionImplBase implements SipSessionBase, Externalizable, Cleanable {
static SipBindingResolver sipBindingResolver = SipBindingResolver.instance();
/** The serialized format versioning. 2 = second version. */
private static final short serializedFormVersion = 2;
// --- Transient fields ---
private static final long serialVersionUID = 3762535598732096310L;
private static final Logger log = LogUtil.SIP_LOGGER.getLogger();
public final static String SIP_SESSIONID_DELIMITER = "##";
// this is an atomic boolean since we want to have visibility of this variable
// in different threads and we want to avoid that multiple threads start invalidating
// the SS without the need to obtain the sslock.
private AtomicBoolean isValid = new AtomicBoolean(true);
/**
* Flag indicating whether the application will be notified
* when this session transitions to ready-to-invalidate state or not.
*/
private volatile boolean invalidateWhenReady = true;
/**
* Flag indicating whether the session is in ready-to-invalidate
* state or not.
*/
private volatile boolean readyToInvalidate = false;
/**
* Flag indicating whether the session was invalidated due ready-to-invalidate mechanism
* (or explicitly invalidated by application or session-timeout)
*/
protected transient volatile boolean invalidatedDueIWR = false;
private transient SessionLock sessionLock = new SessionLock();
private transient Object ssLock = new Object();
private transient B2buaHelperPendingMessages pendingMessages = null;
private transient OutboundInterface oi = null;
private transient DialogSet dialogSet; // Final
private transient DialogFragment dialogFragment = null;
private transient TmpCallData tmpCallData = null;
protected static final LocalStringManagerImpl localStrings =
new LocalStringManagerImpl(SipSessionImplBase.class);
protected SipSessionImplBase(DialogSet set) {
dialogSet = set;
}
public boolean isValid() {
return isValid.get();
}
/**
* Creates a unique id for this session
*
* @return the unique id
*/
protected StringBuilder createID() {
StringBuilder id = new StringBuilder(UUIDUtil.randomUUID());
id.append(SIP_SESSIONID_DELIMITER);
id.append(sipBindingResolver.getCurrentPublicHost());
return id;
}
public long getCreationTime() {
return getPFieldCreationDate();
}
public String getId() {
return getPFieldId();
}
public long getLastAccessedTime() {
return getPFieldLastAccessedTime();
}
public long getExpirationTime() {
return getPFieldExpirationTime();
}
public void access() {
setPFieldLastAccessedTime(getPFieldCurrentAccessedTime());
setPFieldCurrentAccessedTime(System.currentTimeMillis());
}
public void invalidate() {
// if called from SipServlet code
invalidate(false);
}
public void invalidate(boolean hasTimedOut) {
if (isValid.getAndSet(false)) {
if(log.isLoggable(Level.FINE)) {
log.log(Level.FINE, "Invalidating " + this +
", readyToInvalidate = " + readyToInvalidate);
}
// fix for 1801 :: temporarily keep the data around, so that we can provide
// access to all those getters which are documented (in jsr289) not
// to throw IllegalStateException after the session is invalidated,
tmpCallData = new TmpCallData(getCallId(),
getFrom(), getTo(), getCSeq(), getPFieldDialogFragmentId());
synchronized (ssLock) {
DialogFragment df;
try {
df = getDF();
} catch (RemoteLockRuntimeException e) {
log.log(Level.SEVERE,
"sip.stack.sipsession.remote_locked_df_in_invalidate",
new Object[] { getId(), getPFieldDialogFragmentId() });
DialogCleaner.getInstance().registerForSupervision(this);
return;
}
if (df != null) {
df.invalidate(hasTimedOut); // TODO SR-FIX
setDF(null);
setPFieldDialogFragmentId(null);
} else {
// Non-dialog creational
DialogCleaner.getInstance()
.registerForSupervision(this, 5000);
}
// If the call from the SipApplicationSession then m_IsValid
// is
// false,
// But if the call made directly on the session then
// m_IsValid is
// true.
SipApplicationSessionImpl sas;
try {
sas = getApplicationSessionImpl();
} catch (RemoteLockRuntimeException e) {
log.log(Level.SEVERE,
"sip.stack.sipsession.remote_locked_sas_in_invalidate",
new Object[] { getId() });
DialogCleaner.getInstance().registerForSupervision(this);
return;
}
notifyAttributesUnbound();
notifySessionDestroyed();
if (sas != null) {
if (!sas.isValid()) {
setPFieldSipApplicationSession(null);
} else {
sas.removeSession(this);
}
}
}
} else {
throw new IllegalStateException(localStrings.getLocalString(
"ss_already_invalid",
"Should not call invalidate() when the sip session is not valid."));
}
}
private void validateSessionState() {
if(!isValid()) {
throw new IllegalStateException(localStrings.getLocalString(
"ss_invalid", "SipSession is not valid."));
}
}
private void validateSessionState(boolean checkReadyToInvalidate) {
validateSessionState();
if(checkReadyToInvalidate && readyToInvalidate) {
throw new IllegalStateException(localStrings.getLocalString(
"ss_readytoinvalidate",
"SipSession is in ready-to-invalidate state."));
}
}
protected void initInvalidateWhenReady() {
SipSessionManager manager = getSipSessionManager();
if (manager != null) {
ConvergedContext ctx = manager.getContext();
if (ctx != null) {
SipApplication descriptor = ctx.getSipApplication();
if (descriptor != null &&
SipApplication.ApplicationVersion.VERSION_1_0.equals(
descriptor.getApplicationVersion())) {
invalidateWhenReady = false;
return;
}
}
}
invalidateWhenReady = true;
}
public boolean isReadyToInvalidate() {
validateSessionState();
return readyToInvalidate;
}
public boolean invalidatedDueIWR() {
return invalidatedDueIWR;
}
public boolean getInvalidateWhenReady() {
validateSessionState();
return invalidateWhenReady;
}
public void setInvalidateWhenReady(boolean iwr) {
validateSessionState();
boolean wasInvalidateWhenReady;
synchronized (ssLock) {
if(iwr == invalidateWhenReady) {
return;
}
wasInvalidateWhenReady = invalidateWhenReady;
invalidateWhenReady = iwr;
}
/*
* If IWR was already true, then we should do nothing.
* Otherwise we will cause an infinite loop if setInvalidateWhenReady(true)
* is called from listener's sessionReadyToInvalidate callback.
*/
if (!wasInvalidateWhenReady) {
if (checkSessionReadyToInvalidate()) { // we should notify the listeners in this case.
sessionReadyToInvalidate(); // don't hold ssLock while notifying.
}
}
if (log.isLoggable(Level.FINE)) {
if (invalidateWhenReady) {
log.log(Level.FINE, "Invalidate-When-Ready machanism is enabled"
+ " for SipSession with ID " + getId() + ". wasEnabled = " + wasInvalidateWhenReady);
} else {
log.log(Level.FINE, "Invalidate-When-Ready machanism is disabled"
+ " for SipSession with ID" + getId() + ". wasEnabled = " + wasInvalidateWhenReady);
}
}
}
/**
* Transition this session to ready-to-invalidate state after the current
* transaction completes. If all the sessions in the dialog are not ready,
* then we can not transition this session to ready-to-invalidate.
*/
private void markReadyToInvalidate() {
// if the session was already marked while processing the request, then do nothing.
if(readyToInvalidate) {
return;
}
synchronized (ssLock) {
readyToInvalidate = true;
}
}
/**
* This method does not check for the validity of the session.
*/
boolean checkSessionReadyToInvalidate() {
if(invalidateWhenReady && readyToInvalidate) {
return true;
}
return false;
}
/**
/**
* This method is called under any of the following circumstances:
*
* (a) the session had transitioned to ready-to-invalidate state and
* servlet's service method has completed
* (b) the servlet has invoked setReadyToInvalidate(true) when IWR was false
* earlier but the session had already transitioned to ready-to-invalidate
* state. (eg., from doBye() method of the servlet).
*
* @see com.ericsson.ssa.sip.IWRHandler
*/
void sessionReadyToInvalidate() {
if (isValid()) {
notifySessionReadyToInvalidate(); // notify the application listeners.
if (invalidateWhenReady && isValid()) {
// application is OK to do invalidaion & has not invalidated explicitly.
if(log.isLoggable(Level.FINE)) {
log.log(Level.FINE, "Invalidating " + this + " which is in" +
" ready-to-invalidate state");
}
// first set the InvalidateDueIWR, so that during invalidation
// it is clear that this is an IWR-ed SS
invalidatedDueIWR = true;
try {
invalidate(false);
} catch (IllegalStateException e) {
log.log(Level.FINE, "race condition when trying to IWR invalidate " + this);
// OK, since the session will be invalidated anyway
}
}
}
}
private void notifySessionReadyToInvalidate() {
List<SipSessionListener> listeners = getSipSessionListeners();
if (listeners == null) {
return;
}
Iterator<SipSessionListener> iter = listeners.iterator();
SipSessionEvent sipSessionEvent = new SipSessionEvent(this);
/**
* Stop notifying the listeners if any of the following occur:
*
* (a) one of the listener invoked setInvalidateWhenReady(false)
* (b) this session is no longer valid, may be because the listener
* has invoked invalidate() explicitly.
*/
while (iter.hasNext() && invalidateWhenReady && isValid()) {
SipSessionListener listener = iter.next();
try {
listener.sessionReadyToInvalidate(sipSessionEvent);
} catch (Throwable t) {
log.log(Level.WARNING, t.getMessage(), t);
}
}
}
protected abstract void notifyAttributesUnbound();
private Address getFrom() {
if (dialogSet != null) {
return dialogSet.getFrom();
}
if (tmpCallData != null) {
return tmpCallData.getFrom();
}
return getDF().getFrom();
}
private void setDS(DialogSet ds) {
dialogSet = ds;
}
DialogSet getDS() {
if (dialogSet != null) {
return dialogSet;
}
return getDF().getDialogSet();
}
protected synchronized DialogFragment getDF() {
if (dialogFragment == null) {
if ((getPFieldDialogFragmentId() != null)) {
dialogFragment = getDF(getPFieldDialogFragmentId());
}
}
return dialogFragment; // May be null
}
private DialogFragment getDF(String dfId) {
try {
return DialogFragmentManager.getInstance()
.findDialogFragment(dfId);
} catch (RemoteLockException e) {
throw new RemoteLockRuntimeException(e);
}
}
protected void setDF(DialogFragment df) {
dialogFragment = df;
if (df != null) {
setPFieldDialogFragmentId(df.getDialogId());
dialogSet = null;
}
}
public SipApplicationSession getApplicationSession() {
return getPFieldSipApplicationSession();
}
public SipApplicationSessionImpl getApplicationSessionImpl() {
return getPFieldSipApplicationSession();
}
public String getCallId() {
if (dialogSet != null) {
return dialogSet.getCallId();
}
if (tmpCallData != null) {
return tmpCallData.getCallId();
}
return getDF().getCallId();
}
public Address getLocalParty() {
return getPFieldSwapLocalRemote() ? getTo() : getFrom();
}
public Address getRemoteParty() {
return getPFieldSwapLocalRemote() ? getFrom() : getTo();
}
/**
* Returns the from address for caller and to address for callee, otherwise
* null
*
* @return the from address for caller and to address for callee, otherwise
* null
*/
private Address getLocalSide() {
Address add = null;
if (getPFieldType().equals(Type.Caller)) {
add = getFrom();
} else if (getPFieldType().equals(Type.Callee)) {
add = getTo();
} else {
if (log.isLoggable(Level.FINE)) {
log.log(Level.FINE,
"Unable to decide if its caller or callee. Session id = " +
getId());
}
}
return add;
}
/**
* Returns the to address for caller and from address for callee, otherwise
* null
*
* @return the to address for caller and from address for callee, otherwise
* null
*/
private Address getOtherside() {
Address add = null;
if (getPFieldType().equals(Type.Caller)) {
add = getTo();
} else if (getPFieldType().equals(Type.Callee)) {
add = getFrom();
} else {
if (log.isLoggable(Level.FINE)) {
log.log(Level.FINE,
"Unable to decide if its caller or callee. Session id = " +
getId());
}
}
return add;
}
/**
* SSA 7.1.2 requestURI, Call-ID, From, To, CSeq and Route headers
*/
public SipServletRequest createRequest(String method) {
checkCreateRequestMethodValidity(method);
SipServletRequest result = (getState() == State.INITIAL) ? createRequestInitial(method)
: createRequestCommitted(method);
if (getDF() != null)
getDF().getDialogLifeCycle().initUnitOfWork();
return result;
}
/**
* Checks whether the proposed method name is allowed. To be allowed it must satisfy:
* <br>
* - being a proper rfc3261 "method" and it mus
* <br>
* - not being a CANCEL nor an ACK.
* <br>
*
* @param method
* @throws IllegalArgumentException if the method name is not valid
*/
private void checkCreateRequestMethodValidity(String method)
throws IllegalArgumentException {
// Check that the name is a valid method name according to rfc3261 ...
checkValidMethodName(method);
// And that it is neither CANCEL or ACK
if ("CANCEL".equals(method) || "ACK".equals(method)) {
throw new IllegalArgumentException(method + " is not allowed");
}
}
/**
* Checks that the method name is a valid method name rfc 3261
*
* @param method
* @throws IllegalArgumentException if the method name is not a rfc 3261 "Method"
*/
private void checkValidMethodName(String method)
throws IllegalArgumentException {
//
// INVITEm = %x49.4E.56.49.54.45 ; INVITE in caps
// ACKm = %x41.43.4B ; ACK in caps
// OPTIONSm = %x4F.50.54.49.4F.4E.53 ; OPTIONS in caps
// BYEm = %x42.59.45 ; BYE in caps
// CANCELm = %x43.41.4E.43.45.4C ; CANCEL in caps
// REGISTERm = %x52.45.47.49.53.54.45.52 ; REGISTER in caps
// Method = INVITEm / ACKm / OPTIONSm / BYEm
// / CANCELm / REGISTERm
// / extension-method
// extension-method = token
// token = 1*(alphanum / "-" / "." / "!" / "%" / "*"
// / "_" / "+" / "`" / "'" / "~" )
//
// We only need to check that method is a "token" according to RFC3261
// since all INVITEm , ACKm , OPTIONSm , BYEm, CANCELm, REGISTERm are tokens as well
int length = method.length();
for (int i = 0; i < length; i++) {
if (!isAllowedTokenCharacter(method.charAt(i))) {
throw new IllegalArgumentException(method +
" is not a valid SIP method name. Contains illegal characters. The character \'" +
method.charAt(i) + "\' at index " + i + " is not allowed");
}
}
}
/**
* Help method that checks that a character is allowed as part of a rfc 3261 "token".
*
* @param c
* @return Whether it is an allowed character for a token.
*/
private boolean isAllowedTokenCharacter(char c) {
// A valid method name is a "token" according to RFC3261
//
// token = 1*(alphanum / "-" / "." / "!" / "%" / "*"
// / "_" / "+" / "`" / "'" / "~" )
//
// alphanum = ALPHA / DIGIT
//
// ALPHA = %x41-5A / %x61-7A ; A-Z / a-z
//
// DIGIT = %x30-39 ; 0-9
//
// Some pdf versions of rfc3261 allow back quote instead of single quote.
// This is most probably a conversion issue but the specs are out there.
// Although we formally should not allow backquote we better do that anyway.
// We are using unicode literal \u00B4 for backquote (ACUTE ACCENT)
// since editors might mess things up otherwise.
return "ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvzyx0123456789-.!%*_+`'\u00B4~".indexOf(c) > -1;
}
/**
* SSA 7.1.2 requestURI, Call-ID, From, To, CSeq and Route headers Only used
* when
*/
private SipServletRequest createRequestCommitted(String method) {
validateSessionState(true);
if ((getDF() != null) && (getFromTag() != null) &&
(getToTag() != null) && hasPFieldCSeq()) {
Address from = (Address) ((AddressImpl) getLocalSide()).clone(true,
true);
Address to = (Address) ((AddressImpl) getOtherside()).clone(true,
true);
if ((from == null) || (to == null)) {
throw new IllegalStateException();
}
URI remoteTarget = getRemoteTarget();
if (remoteTarget != null) {
remoteTarget = (URI) remoteTarget.clone();
}
SipServletRequestImpl req = new SipServletRequestImpl(method,
remoteTarget, SipFactoryImpl.PROTOCOL_LINE);
req.setDirection(getPFieldType());
// set To
Header toHeader = new SingleLineHeader(Header.TO, true);
toHeader.setAddressValue(to, false);
req.setHeader(toHeader);
// set From
Header fromHeader = new SingleLineHeader(Header.FROM, true);
fromHeader.setAddressValue(from, false);
req.setHeader(fromHeader);
// set Max-Forwards
Header maxForwardsHeader = new SingleLineHeader(Header.MAX_FORWARDS,
false);
maxForwardsHeader.setValue("70", false);
req.setHeader(maxForwardsHeader);
// set CallID
Header callIDHeader = new SingleLineHeader(Header.CALL_ID, true);
callIDHeader.setValue(getCallId(), false);
req.setHeader(callIDHeader);
// set CSeq
Header cSeqHeader = new SingleLineHeader(Header.CSEQ, true);
cSeqHeader.setValue(Integer.toString(incrementAndGetPFieldCSeq()) +
" " + req.getMethod(), false);
req.setHeader(cSeqHeader);
// update new request with session
req.setSession(this);
// update it with dialog
req.setDialog(getDF());
// subsequent request
req.setInitial(false);
List<Layer> layers = LayerHandler.getInstance().getLayers();
req._applicationStack.addAll(layers);
req.setRole(getPFieldSipApplicationSession().getName());
DialogManager.getInstance().addContact(req);
return req;
} else {
throw new IllegalStateException("Not allowed to create a request.");
}
}
private SipServletRequest createRequestInitial(String method) {
validateSessionState(false);
if (tmpCallData != null) {
// OK, so the session is reused.
// the fact that we have tmpCallData is sufficient proof that this
// is allowed.
// To avoid the invalidation of the session by the removal of the
// dialogs (invalidate earlier in the reset() ) we decouple the
// caller pathnode from the path.
readyToInvalidate = false;
if (tmpCallData.getDialogFragmentId() != null) {
DialogFragment oldDF = getDF(tmpCallData.getDialogFragmentId());
if (oldDF != null) {
oldDF.removeCallerFromPath(getId());
}
}
Address from = tmpCallData.getFrom();
Address to = tmpCallData.getTo();
int CSeq = tmpCallData.getCSeq();
if (!hasPFieldCSeq()) {
setPFieldCSeq(++CSeq);
} else {
int tmp = incrementAndGetPFieldCSeq();
if (tmp <= CSeq) {
setPFieldCSeq(++CSeq);
} else {
CSeq = tmp;
}
}
String callId = tmpCallData.getCallId();
tmpCallData = null;
if ((from == null) || (to == null)) {
throw new IllegalStateException();
}
URI requestURI = (URI) to.getURI().clone();
if ("REGISTER".equals(method) && requestURI.isSipURI()) {
((SipURI) requestURI).setUser(null);
}
SipServletRequestImpl req = new SipServletRequestImpl(method,
requestURI, SipFactoryImpl.PROTOCOL_LINE);
req.setDirection(getPFieldType());
// set To
Header toHeader = new SingleLineHeader(Header.TO, true);
toHeader.setAddressValue(to, false);
req.setHeader(toHeader);
// set From
Header fromHeader = new SingleLineHeader(Header.FROM, true);
fromHeader.setAddressValue(from, false);
req.setHeader(fromHeader);
// set Max-Forwards
Header maxForwardsHeader = new SingleLineHeader(Header.MAX_FORWARDS,
false);
maxForwardsHeader.setValue("70", false);
req.setHeader(maxForwardsHeader);
// set CallID
Header callIDHeader = new SingleLineHeader(Header.CALL_ID, true);
callIDHeader.setValue(callId, false);
req.setHeader(callIDHeader);
// set CSeq
Header cSeqHeader = new SingleLineHeader(Header.CSEQ, true);
cSeqHeader.setValue(Integer.toString(CSeq) + " " + req.getMethod(),
false);
req.setHeader(cSeqHeader);
// update new request with session
req.setSession(this);
// update it with a new dialog fragment and dialog set
setDS(new DialogSet(req.getMethod(), req.getCallId(), req.getFromImpl(),
req.getBeKey(), // required just for ssr optimization to locate DF, refer issue 1586
SipFactoryImpl.isDialogCreational(req.getMethod())));
req.setDialog(getDS().getInitialDialogFragment());
// initial request
req.setInitial(true);
List<Layer> layers = LayerHandler.getInstance().getLayers();
req._applicationStack.addAll(layers);
req.setRole(getPFieldSipApplicationSession().getName());
DialogManager.getInstance().addContact(req);
return req;
} else {
throw new IllegalStateException("Not allowed to create a request.");
}
}
public void setHandler(String name) throws ServletException {
validateSessionState();
ServletDispatcher servletDispatcher;
servletDispatcher = getPFieldSipApplicationSession()
.getServletDispatcher();
if (servletDispatcher != null) {
if ((name == null) || !servletDispatcher.findServlet(name)) {
throw new ServletException(
"Could not found the servlet to set it as a handler.");
}
} else {
if (log.isLoggable(Level.FINE)) {
log.log(Level.FINE,
"Unexpected: Called setHandler, but the servlet dispatcher is null.");
}
}
setPFieldHandler(name);
}
public String getHandler() {
return getPFieldHandler();
}
public Object getAttribute(String name) {
if (name.equals(SipFactoryImpl.REMOTE_TARGET)) {
return getRemoteTarget();
}
if (!isValid()) {
throw new IllegalStateException("The session is not valid.");
}
return getFromPFieldSessionAttribute(name);
}
public Enumeration<String> getAttributeNames() {
if (!isValid()) {
throw new IllegalStateException("The session is not valid.");
}
// Since Map doesn't have Enumeration as interface need to convert via
// private class EnumerationConverter.
Collection<String> c = getFromPFieldSessionAttributeNames();
if (c.equals(Collections.EMPTY_SET)) {
c = new ArrayList<String>(1);
} else {
ArrayList<String> c1 = new ArrayList<String>(c.size() + 1);
c1.addAll(c);
c = c1;
}
c.add(SipFactoryImpl.REMOTE_TARGET);
return new EnumerationConverter<String>(c);
}
public void setAttribute(String name, Object attribute) {
if (name.equals(SipFactoryImpl.REMOTE_TARGET)) {
throw new IllegalStateException("reserved key.");
}
if (!isValid()) {
throw new IllegalStateException("The session is not valid.");
}
addToPFieldSessionAttribute(name, attribute);
}
public void removeAttribute(String name) {
if (!isValid()) {
throw new IllegalStateException("The session is not valid.");
}
removeFromPFieldSessionAttribute(name);
}
/**
* @return the to tag for this session.
*/
public String getToTag() {
return getTo().getParameter(AddressImpl.TAG_PARAM);
}
/**
* @return true if no to tag has been set in this dialog otherwise false.
*/
public boolean hasNoToTag() {
return getDF() == null;
}
/**
* @return the from tag of this session.
*/
public String getFromTag() {
return getFrom().getParameter(AddressImpl.TAG_PARAM);
}
public synchronized void setType(Type type) {
setPFieldType(type);
// if user agent then a cseq counter is needed, otherwise save memory...
if (!hasPFieldCSeq() &&
(getPFieldType().equals(Type.Caller) ||
getPFieldType().equals(Type.Callee))) {
createPFieldCSeq();
}
}
/**
* The session is associated with a dialog that must already be established
* with a proper toTag. If this is the first message retrieving the session
* the original session is returned otherwise a derived session is created
* and returned.
*
* @param d
* the established dialog to associate the session with
* @return the original or a derived session
*/
private SipSessionImplBase getOriginalOrDerivedSession(DialogFragment d,
String toTag) {
if ((d.getToTag() != null) && (toTag != null) &&
toTag.equals(d.getToTag())) {
SipSessionImplBase s = null;
synchronized (ssLock) {
if (deriveSessionNotApplicable(toTag, d)) {
// return original session
s = this;
registerSipSession(d, s, toTag);
} else {
DialogSet ds = getDF().getDialogSet();
// return derived session
s = getSipSessionManager()
.createSipSession(ds,
getTo(), getApplicationSessionImpl(),
getHandler(), getPFieldType());
// ...and set toTag of session
s.setPFieldDerived(true);
registerSipSession(d, s, toTag);
}
}
return s;
} else {
throw new IllegalStateException(
"DialogFragment must have same to-tag as message.");
}
}
/**
* Fix for issue 935.
*
* As per section 6.2.3 of the Sip Servlet API, SIP Session gets created only
* when a second 2xx respone or a second 1xx response (except 100) arrives.
* This happens in case of a forking proxy and the responses will have a different
* to tag than the one set in the original session.
*
* In case of spiralling, the fragment id will be different. So, in that case,
* even if totags are same, there should be a different session.
*
* Basically, if totags are different or when the fragment-ids are different
* create derived sipsessions. Otherwise use the orginal sipsession.
*/
private boolean deriveSessionNotApplicable(String toTag, DialogFragment df) {
return getDF() == null
|| getToTag() == null
|| (getFragmentId().equals(df.getFragmentId())
&& getToTag().equals(toTag));
}
private void registerSipSession(DialogFragment df, SipSessionImplBase s, String toTag) {
// set the dialog fragment in the session
s.setDF(df);
// register the session in the dialog fragment
s.getDF().registerSession(s);
AddressImpl toAddress = (AddressImpl) s.getTo();
toAddress.setReadOnly(false);
toAddress.setParameter(AddressImpl.TAG_PARAM, toTag);
toAddress.setReadOnly(true);
}
public SipSessionBase getOriginalOrDerivedSessionAndRegisterDialog(
SipServletRequestImpl req, DialogFragment d) {
// need to swap to and from for request
String toTag = req.getFromImpl().getParameter(AddressImpl.TAG_PARAM);
return getOriginalOrDerivedSessionAndRegisterDialog(req, d, toTag);
}
public SipSessionBase getOriginalOrDerivedSessionAndRegisterDialog(
SipServletResponseImpl resp, DialogFragment d) {
String toTag = resp.getToImpl().getParameter(AddressImpl.TAG_PARAM);
return getOriginalOrDerivedSessionAndRegisterDialog(resp, d, toTag);
}
/**
* Tries to register the DialogFragment with the toTag of the message. If
* successful a original or derived session is returned. If unsuccessful a
* confirmed DialogFragment with the toTag of the message is searched for.
* If found the message is updated with this DialogFragment and a original
* or derived session is returned. If no DialogFragment is found the
* original DialogFragment is cloned and it is recursively sent to this
* method.
*
* @param m
* the message with the toTag to be set in the DialogFragment
* @param d
* the DialogFragment to register the session in.
* @return an original or derived session
*/
private SipSessionBase getOriginalOrDerivedSessionAndRegisterDialog(
SipServletMessageImpl m, DialogFragment d, String toTag) {
if (toTag != null) {
SipSessionBase s = null;
boolean isDialogCreational = SipFactoryImpl.isDialogCreational(m.getMethod());
boolean success = d.tryToSetToTagAndRegisterDialog(toTag,
isDialogCreational);
if (success) {
s = getOriginalOrDerivedSession(d, toTag);
} else {
DialogFragment searchedDialog = getDS()
.searchForDialog(toTag,
m.getFragmentId());
if (searchedDialog != null) {
m.setDialog(searchedDialog);
s = getOriginalOrDerivedSession(searchedDialog, toTag);
} else {
// clone the dialog
DialogFragment clone = (DialogFragment) d.clone();
// FIXME its a recursive call and a break condition must be
// added
s = getOriginalOrDerivedSessionAndRegisterDialog(m, clone,
toTag);
}
}
return s;
} else {
throw new IllegalStateException(
"DialogFragment must have same to-tag as message.");
}
}
public void swapLocalRemote() {
setPFieldSwapLocalRemote(true);
}
public boolean setUpdateOngoing() {
if (getPFieldUpdateOngoing()) {
return false;
} else {
setPFieldUpdateOngoing(true);
return true;
}
}
public void resetUpdateOngoing() {
setPFieldUpdateOngoing(false);
}
// set to true the flag which specify that there is an 1xx Reliable
// response ongoing and if an sdp is included (CR38 PRACK)
//
// @param sdp=true if an sdp is included in the 1xx rel response
// @return true if there was no pending 1xx reliable response
// false if there was pending 1xx reliable response
public synchronized boolean set1xxReliableOngoing(boolean sdp) {
if (is1xxReliableOngoing()) {
return false;
}
setPField1xxReliableSDP(sdp);
setPField1xxReliableOngoing(true);
return true;
}
public void reset1xxReliable() {
setPField1xxReliableOngoing(false);
setPField1xxReliableSDP(false);
}
public String getFragmentId() {
return (getDF() != null) ? getDF().getFragmentId()
: DialogFragment.BAD_FRAGMENT_ID;
}
public State getState() {
return getState(true);
}
private State getState(boolean validateSession) {
if(validateSession) {
validateSessionState();
}
synchronized (ssLock) {
return getPFieldSessionState();
}
}
private void setState(State state) {
synchronized (ssLock) {
setPFieldSessionState(state);
}
}
/**
* JSR 289, 6.2.2.2 When in the INITIAL SipSession State.
*
* When a UAC transitions from the early state to the initial state, the
* dialog state maintained in the SipSession is updated as follows (see RFC
* 3261 for the definition of these dialog state components):
*
* the remote target is reset to the remote URI the remote tag component is
* cleared the remote sequence number is cleared (e.g. set to -1) the route
* set is cleared the �secure� flag is set to false
*
* As a consequence of these rules, requests generated in the same
* SipSession have the following characteristics: they all have the same
* Call-ID they all have the same From header field value including the same
* non-empty tag the CSeq of requests generated in the same SipSession will
* monotonically increase by one for each request created, regardless of the
* state of the SipSession all requests generated in the same SipSession in
* the initial state have the same To header field value which will not have
* a tag parameter SipSession objects in the initial state have no route
* set, the remote sequence number is undefined, the �secure� flag is false,
* and the remote target URI is the URI of the To header field value.
*/
private void reset(SipServletResponseImpl resp) {
// Eventually move all the reset() to DS.
// 1. update all FSMs to only add DF when CONFIRMED (not 3xx-6xx)
// DF should only call register for 2xx (DFM.registerDialogFragment()).
// 2. DS.invalidate() and DialogFragment.invalidate()
// since it's call setup, DS.invalidate is OK.
if (getOtherside() != null) {
// remove to-tag
Address to = (Address) ((AddressImpl) getOtherside()).clone(false, true);
setPFieldTo(to);
}
int cseq = resp.getCSeqNumber();
if (cseq < getCSeq()) {
cseq = getCSeq();
}
tmpCallData = new TmpCallData(getCallId(), getFrom(), getTo(), cseq, getPFieldDialogFragmentId());
for (Iterator<DialogFragment> it = getDS().getDialogs(); it.hasNext();) {
it.next().invalidate(false);
}
// reset dialog set and dialog fragment
setDF(null);
setPFieldDialogFragmentId(null);
// also clear the cached reference.
// XXX better to do this as a direct result of setting the fid to null
dialogFragment = null;
// remove remote target
setRemoteTarget(null);
}
/**
* Implements the logic for JSR289 <code>SipSession.State</code> This
* method is called on every response and may update the state of the
* SipSession.<br>
*
* JSR 289 6.2.1 Figure 6. The SipSession state machine.
*
* <pre>
* INITIAL ---> 1xx EARLY
* INITIAL ---> 2xx CONFIRMED
* EARLY ---> 3xx-6xx INITIAL
* EARLY ---> 2xx CONFIRMED
* </pre>
*
* Following section from RFC3265, 3.3.4. Dialog creation and termination is
* not supported: a dialog created with an INVITE does not necessarily
* terminate upon receipt of a BYE. Similarly, in the case that several
* subscriptions are associated with a single dialog, the dialog does not
* terminate until all the subscriptions in it are destroyed.
*
* @param resp
* Current response.
* @param type
* type of PathNode
*
*/
public void updateSipSessionState(SipServletResponse resp,
PathNode.Type type) {
String method = resp.getRequest().getMethod();
int status = resp.getStatus();
// From JSR 289 "6.2.1 Relationship to SIP Dialogs"
// 2. The exception to the general rule is that it does not apply to
// requests (e.g. BYE, CANCEL) that are dialog terminating
// NOTE: Ignore CANCEL because if it is canceled a 487 will eventually
// arrive and it will be handled correctly for UAC, UAS and Proxy.
if (method.equals("BYE")) {
if (status != 401 && status != 407) {
setState(State.TERMINATED);
markReadyToInvalidate();
if (log.isLoggable(Level.FINER)) {
log.log(Level.FINER, this + " transitioned to TERMINATED " +
"state while processing " + status + " for " + method);
}
}
} else if (method.equals("NOTIFY")) {
// From RFC 3265:
// A subscription is destroyed when a notifier sends a NOTIFY
// request
// with a "Subscription-State" of "terminated".
String subState = resp.getRequest().getHeader("Subscription-State");
if (subState != null) {
if (subState.toLowerCase().startsWith("terminated")) {
DialogFragment df = getDF();
if (df == null || !df.isInviteDialog()) { // check if we are in SUBSCRIBE/REFER/NOTIFY dialog.
setState(State.TERMINATED);
markReadyToInvalidate();
if (log.isLoggable(Level.FINER)) {
log.log(Level.FINER, this + " transitioned to " +
"TERMINATED state while processing " +
status + " for " + method);
}
}
}
}
} else {
switch (getState(false)) {
case INITIAL:
// 3. If the servlet acts as a UAC and sends a dialog creating
// request,
// then the SipSession state tracks directly the SIP dialog
// state.
// 4. If the servlet acts as a UAS and receives a dialog
// creating
// request then the SipSession state directly tracks the SIP
// dialog state.
if (((status >= 100) && (status <= 199)) &&
SipFactoryImpl.isDialogCreational(method)) {
setState(State.EARLY);
} else if (((status >= 200) && (status <= 299)) &&
SipFactoryImpl.isDialogCreational(method)) {
setState(State.CONFIRMED);
if (type == Type.Proxy) {
ProxyBranch pb = resp.getProxyBranch();
if (pb != null) {
if (!pb.getRecordRoute()) {
markReadyToInvalidate();
}
} else if (!resp.getProxy().getRecordRoute()) {
markReadyToInvalidate();
}
if (log.isLoggable(Level.FINER)) {
log.log(Level.FINER, this + " transitioned to " +
"CONFIRMED state while processing " +
status + " for " + method +
" in case of non-record-routing proxy");
}
}
// 4. Unlike a UAC, a non-2XX final response sent by the UAS in
// the EARLY or INITIAL states causes the SipSession state to go
// directly to the TERMINATED state.
} else if ((type == Type.Callee) &&
((status >= 300) && (status <= 699)) &&
SipFactoryImpl.isDialogCreational(method)) {
setState(State.TERMINATED);
markReadyToInvalidate();
if (log.isLoggable(Level.FINER)) {
log.log(Level.FINER, this + " transitioned to " +
"TERMINATED state while processing " +
status + " for " + method);
}
} else if((type == Type.Caller) &&
((status >= 300) && (status <= 699)) &&
SipFactoryImpl.isDialogCreational(method)) {
/**
* Need to mark for readyToInvalidate (refer issue 1814).
*
* Note that there is no state change, and this SipSession
* will be reset at the end of this method. If the application
* re-uses the reset SipSession to create new initial request, then
* this SipSession will regain life, so it will no longer be
* in readyToInvalidate state (see createRequest method).
*/
markReadyToInvalidate();
}
break;
case EARLY:
// 3. If the servlet acts as a UAC and sends a dialog creating
// request,
// then the SipSession state tracks directly the SIP dialog
// state
// except that non-2XX final responses received in the EARLY or
// INITIAL states cause the SipSession state to return to the
// INITIAL state rather than going to TERMINATED.
// 5. If the servlet acts as a proxy for a dialog creating
// request
// then the SipSession state tracks the SIP dialog state except
// that
// non-2XX final responses received from downstream in the EARLY
// or
// INITIAL states cause the SipSession state to return to
// INITIAL
// rather than going to TERMINATED.
if (((type == Type.Caller) || (type == Type.Proxy)) &&
((status >= 300) && (status <= 699)) &&
SipFactoryImpl.isDialogCreational(method)) {
setState(State.INITIAL);
//TODO. In case of proxy, this invalidation should happen
//only after dialogfragments for the branches are fully
//copied for sequential branches.
if (type != Type.Proxy) {
markReadyToInvalidate();
}
if (log.isLoggable(Level.FINER)) {
log.log(Level.FINER, this + " transitioned to INITIAL" +
" state while processing " + status +
" for " + method + " in case of UAC");
}
}
// 4. If the servlet acts as a UAS and receives a dialog
// creating
// request, then the SipSession state directly tracks the SIP
// dialog
// state. Unlike a UAC, a non-2XX final response sent by the UAS
// in
// the EARLY or INITIAL states causes the SipSession state to go
// directly to the TERMINATED state.
else if ((type == Type.Callee) &&
((status >= 300) && (status <= 699)) &&
SipFactoryImpl.isDialogCreational(method)) {
setState(State.TERMINATED);
markReadyToInvalidate();
if (log.isLoggable(Level.FINER)) {
log.log(Level.FINER, this + " transitioned to " +
"TERMINATED state while processing " +
status + " for " + method);
}
} else if (((status >= 200) && (status <= 299)) &&
SipFactoryImpl.isDialogCreational(method)) {
setState(State.CONFIRMED);
if ((type == Type.Proxy) && !resp.getProxy().getRecordRoute()) {
List<ProxyBranch> branches = resp.getProxy().getProxyBranches();
boolean isRecordRouted = false;
for (ProxyBranch branch : branches) {
if (branch.getRecordRoute()) {
isRecordRouted = true;
break;
}
}
if (!isRecordRouted) {
markReadyToInvalidate();
}
if (log.isLoggable(Level.FINER)) {
log.log(Level.FINER, this + " transitioned to " +
"CONFIRMED state while processing " +
status + " for " + method +
" in case of non-record-routing proxy");
}
}
}
break;
case CONFIRMED:
break;
case TERMINATED:
break;
}
if (type == Type.Caller && (getState() == State.INITIAL)){
reset((SipServletResponseImpl) resp);
}
}
/*
* Fix for sailfin issue 1615:
*
* The session associated with the non dialog creating && outside
* the dialog requests should be marked for invalidation. For example:
* MESSAGE, OPTIONS, PUBLISH, REGISTER requests fall into this catogory
* if they are outside the dialog.
*
* The Invalidate When Ready machanism for this type of requests
* is not defined in the JSR 289 specification. So, it is an
* additional feature.
*
* Also, with this change the session which is reset also gets marked
* for IWR. But if the application reuses the reset session to create
* new requests then the IWR is unmarked in the createRequestInitial().
*/
doNonDialogIWR(resp, type);
}
private void doNonDialogIWR(SipServletResponse resp,
PathNode.Type type) {
String method = resp.getRequest().getMethod();
if (!SipFactoryImpl.isDialogCreational(method) && getDF() == null) {
if (type == Type.Proxy) {
ProxyBranch pb = resp.getProxyBranch();
if (pb != null) {
ProxyImpl.ProxyFacade p = ((ProxyBranchImpl) pb).getProxy();
if (p != null && p.hasBestRepsonse()) {
markReadyToInvalidate();
}
} else {
markReadyToInvalidate();
}
} else {
markReadyToInvalidate();
}
}
}
/**
* Implements the logic for JSR289 <code>SipSession.State</code> This
* method is called on every request and may update the state of the
* SipSession.<br>
*
* @param req
*/
public void updateSipSessionState(SipServletRequest req, PathNode.Type type) {
String method = req.getMethod();
// From JSR 289 "6.2.1 Relationship to SIP Dialogs"
// 2. The exception to the general rule is that it does not apply to
// requests (e.g. BYE, CANCEL) that are dialog terminating
// 200 OK/BYE should transition the state to TERMINATED
if (method.equals("NOTIFY")) {
// From RFC 3265:
// A subscription is destroyed when a notifier sends a NOTIFY
// request
// with a "Subscription-State" of "terminated".
String subState = req.getHeader("Subscription-State");
if (subState != null) {
if (subState.toLowerCase().startsWith("terminated")) {
DialogFragment df = getDF();
if (df == null || !df.isInviteDialog()) { // check if we are in SUBSCRIBE/REFER/NOTIFY dialog.
setState(State.TERMINATED);
if (log.isLoggable(Level.FINER)) {
log.log(Level.FINER, this + " transitioned to " +
"TERMINATED state while processing " +
method + " request.");
}
}
}
}
}
}
/**
* The implementation is intentially made simple and does not correctly
* track every single transaction. The purpose is to make it clear when to
* allow invalidation of sessions.
*/
public boolean isOngoingTransaction() {
return ((getState() == State.EARLY) || (getState() == State.CONFIRMED))
? true : false;
}
/**
* Not doing anything inside this method
*/
public void setOutboundInterface(InetAddress address) {
validateSessionState();
if (address == null)
throw new NullPointerException("Argument address is null");
this.oi = new OutboundInterface(address);
}
/**
* Not doing anything inside this method
*/
public void setOutboundInterface(InetSocketAddress address) {
validateSessionState();
if (address == null)
throw new NullPointerException("Argument address is null");
this.oi = new OutboundInterface(address);
}
public OutboundInterface getOutboundInterface() {
return this.oi;
}
/**
* get this session locked for foreground if the session is found to be
* presently background locked; retry logic in a time-decay polling loop
* waits for background lock to clear after 6 attempts (12.6 seconds) it
* unlocks the session and acquires the foreground lock
*/
public boolean lockForegroundWithRetry() {
boolean result = false;
long pollTime = 200L;
int tryNumber = 0;
int maxNumberOfRetries = 7;
boolean keepTrying = true;
// try to lock up to numTries (i.e. 7) times
// poll and wait starting with 200 ms
while (keepTrying) {
boolean lockResult = lockForeground();
if (lockResult) {
keepTrying = false;
result = true;
break;
}
tryNumber++;
if (tryNumber < maxNumberOfRetries) {
pollTime = pollTime * 2L;
try {
Thread.sleep(pollTime);
} catch (InterruptedException e) {
;
}
} else {
// unlock the background so we can take over
// FIXME: need to log warning for this situation
unlockBackground();
}
}
return result;
}
/**
* return whether this session is currently foreground locked
*/
public synchronized boolean isForegroundLocked() {
return sessionLock.isForegroundLocked();
}
/**
* lock the session for foreground returns true if successful; false if
* unsuccessful
*/
public synchronized boolean lockBackground() {
return sessionLock.lockBackground();
}
/**
* lock the session for background returns true if successful; false if
* unsuccessful
*/
public synchronized boolean lockForeground() {
return sessionLock.lockForeground();
}
/**
* unlock the session completely irregardless of whether it was foreground
* or background locked
*/
public synchronized void unlockForegroundCompletely() {
sessionLock.unlockForegroundCompletely();
}
/**
* unlock the session from foreground
*/
public synchronized void unlockForeground() {
sessionLock.unlockForeground();
}
/**
* unlock the session from background
*/
public synchronized void unlockBackground() {
sessionLock.unlockBackground();
}
/**
* return the Session lock
*/
public SessionLock getSessionLock() {
return sessionLock;
}
/**
* return the Session lock
*/
public Object getSipSessionObjectLock() {
return ssLock;
}
/**
* Returns true if this SipSession is replicable, false otherwise.
*
* @return true if this SipSession is replicable, false otherwise
*/
public boolean isReplicable() {
return getPFieldSipApplicationSession().isReplicable();
}
public URI getRemoteTarget() {
return getPFieldRemoteTarget();
}
public void setRemoteTarget(URI contact) {
setPFieldRemoteTarget(contact);
}
// Used by ApplicationDispatcher to set this before invoking of servlets
public void setRegion(SipApplicationRoutingRegion region) {
setPFieldRoutingRegion(region);
}
public SipApplicationRoutingRegion getRegion() {
return getPFieldRoutingRegion();
}
// Used by ApplicationDispatcher to set this before invoking of servlets
public void setSubscriberURI(URI subscriberURI) {
setPFieldSubscriberURI(subscriberURI);
}
public URI getSubscriberURI() {
return getPFieldSubscriberURI();
}
public ServletContext getServletContext() {
SipSessionManager sessMgr =
getSipSessionManager();
ConvergedContext ctxt = getSipSessionManager().getContext();
return ctxt.getServletContext();
}
// JSR289
public String getLinkedSipSessionId() {
return getPFieldLinkedSipSessionId();
}
public void setLinkedSipSessionId(String id) {
setPFieldLinkedSipSessionId(id);
}
// JSR 289 15.11.4, point 5.
public String getLinkedCorrespondingSipSessionId(String header) {
return getPFieldCorrespondingSipSessionId(header);
}
public void setLinkedCorrespondingSipSessionId(String id, String header) {
setPFieldCorrespondingSipSessionId(id, header);
}
public SipSessionManager getSipSessionManager() {
return getPFieldSipSessionManagerField();
}
public Address getTo() {
if (tmpCallData != null) {
return tmpCallData.getTo();
}
return getPFieldTo();
}
public boolean isDerived() {
return getPFieldDerived();
}
public boolean is1xxReliableOngoing() {
return getPField1xxReliableOngoing();
}
public boolean is1xxReliableSDP() {
return getPField1xxReliableSDP();
}
public synchronized List<SipServletMessage> getPendingMessages(UAMode mode) {
return pendingMessages != null ? pendingMessages.get(mode) : new ArrayList<SipServletMessage>(2);
}
public synchronized void addPendingMessage(SipServletMessageImpl m, UAMode mode) {
if (pendingMessages == null) {
throw new IllegalStateException("B2buaHelper not initialized properly");
}
pendingMessages.addAndPurge(m, mode);
}
public synchronized boolean isB2buaHelper() {
return pendingMessages != null;
}
public synchronized void createPendingMessageHelper() {
if (pendingMessages == null) {
pendingMessages = new B2buaHelperPendingMessages();
}
}
protected abstract void notifySessionDestroyed();
/**
* @serialData first field is a short and represents the serializedFormVersion.<br><br>
* <h3>Data layout for serializedFormVersion = 1 follows</h3>
*
* <li>field is a <b>Boolean</b> and represents isValid field</li>
* <li>field is a <b>Boolean</b> and represents invalidateWhenReady field</li>
* <li>field is a <b>Boolean</b> and represents readyToInvalidate field</li>
*
* <h3>Data layout for serializedFormVersion = 2 follows</h3>
*
* <li>field is a <b>Boolean</b> and represents isValid field</li>
* <li>field is a <b>Boolean</b> and represents invalidateWhenReady field</li>
* <li>field is a <b>Boolean</b> and represents readyToInvalidate field</li>
* <li>field is a <b>Boolean</b> and represents whether the session is a b2buaHelper or not</li>
*
* @param in the stream to read the object members
*
* @throws IOException is thrown when unsupported version is detected
* @throws ClassNotFoundException
*/
public void readExternal(ObjectInput in)
throws IOException, ClassNotFoundException {
short readSerializedFormVersion = in.readShort();
switch (readSerializedFormVersion) {
case 1:
readExternalFormVersion1(in);
break;
case 2:
readExternalFormVersion2(in);
break;
default:
throw new IOException("Unable to deserialize into "
+ this.getClass().getName()
+ " with serialVersionUID = " + serialVersionUID
+ " due to unknown serializedFormVersion of "
+ readSerializedFormVersion);
}
}
private void readExternalFormVersion1(ObjectInput in)
throws IOException, ClassNotFoundException {
// NEVER change the body of this method. This method takes
// care of reading the serialization stream of a released product.
isValid = new AtomicBoolean(in.readBoolean());
invalidateWhenReady = in.readBoolean();
readyToInvalidate = in.readBoolean();
ssLock = new Object();
sessionLock = new SessionLock();
}
private void readExternalFormVersion2(ObjectInput in)
throws IOException, ClassNotFoundException {
// If serialization fields of serializedFormVersion1 are removed/shuffed
// in version2 then deserialization should be done independently without
// calling readExternalFormVersion1(in).
readExternalFormVersion1(in);
boolean isb2buaHelper = in.readBoolean();
if(isb2buaHelper) {
pendingMessages = new B2buaHelperPendingMessages();
}
}
/**
* @serialData See serialized form version
* #serializedFormVersion in #readExternal(ObjectInput in)
*
* @param oos the stream to write the object members
*
* @throws IOException
*/
public void writeExternal(ObjectOutput out)
throws IOException {
out.writeShort(serializedFormVersion);
out.writeBoolean(isValid.get());
out.writeBoolean(invalidateWhenReady);
out.writeBoolean(readyToInvalidate);
out.writeBoolean(isB2buaHelper());
}
// --- Persistable fields (PField) ---
protected abstract String getPFieldHandler();
protected abstract void setPFieldHandler(String name);
protected abstract void setPFieldLinkedSipSessionId(String id);
protected abstract String getPFieldLinkedSipSessionId();
protected abstract void
setPFieldCorrespondingSipSessionId(String id, String header);
protected abstract String getPFieldCorrespondingSipSessionId(String header);
protected abstract void setPFieldSubscriberURI(URI subscriberURI);
protected abstract URI getPFieldSubscriberURI();
protected abstract URI getPFieldRemoteTarget();
protected abstract void setPFieldRemoteTarget(URI contact);
protected abstract Object getFromPFieldSessionAttribute(String name);
protected abstract void addToPFieldSessionAttribute(String name,
Object value);
protected abstract Collection<String> getFromPFieldSessionAttributeNames();
protected abstract void removeFromPFieldSessionAttribute(String name);
protected abstract SipApplicationSessionImpl getPFieldSipApplicationSession();
protected abstract void setPFieldSipApplicationSession(
SipApplicationSessionImpl sipApplicationSession);
protected abstract long getPFieldCreationDate();
protected abstract long getPFieldLastAccessedTime();
protected abstract void setPFieldLastAccessedTime(long lastAccessedTime);
protected abstract long getPFieldCurrentAccessedTime();
protected abstract void setPFieldCurrentAccessedTime(
long currentAccessedTime);
protected abstract int incrementAndGetPFieldCSeq();
protected abstract boolean hasPFieldCSeq();
protected abstract void createPFieldCSeq();
protected abstract void setPFieldCSeq(int cseq);
protected abstract void setPField1xxReliableSDP(boolean is1xxReliableSDP);
protected abstract boolean getPField1xxReliableSDP();
protected abstract boolean getPField1xxReliableOngoing();
protected abstract void setPField1xxReliableOngoing(
boolean is1xxReliableOngoing);
protected abstract boolean getPFieldUpdateOngoing();
protected abstract void setPFieldUpdateOngoing(boolean updateOngoing);
protected abstract void setPFieldRoutingRegion(SipApplicationRoutingRegion routingRegion);
protected abstract SipApplicationRoutingRegion getPFieldRoutingRegion();
protected abstract boolean getPFieldSwapLocalRemote();
protected abstract void setPFieldSwapLocalRemote(boolean swapLocalRemote);
protected abstract Type getPFieldType();
protected abstract void setPFieldType(Type type);
protected abstract SipSessionManager getPFieldSipSessionManagerField();
protected abstract void setPFieldTo(Address to);
protected abstract Address getPFieldTo();
protected abstract void setPFieldDerived(boolean isDerived);
protected abstract boolean getPFieldDerived();
protected abstract String getPFieldId();
protected abstract String getPFieldDialogFragmentId();
protected abstract void setPFieldDialogFragmentId(String dialogFragmentId);
protected abstract State getPFieldSessionState();
protected abstract void setPFieldSessionState(State state);
protected abstract long getPFieldExpirationTime();
protected abstract void setPFieldExpirationTime(long expirationTime);
protected abstract List<SipSessionListener> getSipSessionListeners();
/**
* Temporary call data saved for SSA1.1, 6.2.1 Relationship to SIP Dialogs
*
* The INITIAL state is introduced to allow a UAC to generate multiple
* requests to be generated with the same Call-ID, From (including tag),
* and To (excluding tag), and within the same CSeq space.
*
* @author ehsroha
*/
private class TmpCallData {
private final String callID;
private final Address from;
private final Address to;
private final int CSeq;
private final String dfId;
public TmpCallData(String callID, Address from, Address to, int seq, String dfId) {
this.callID = callID;
this.from = from;
this.to = to;
CSeq = seq;
this.dfId = dfId;
}
public String getCallId() {
return callID;
}
public Address getFrom() {
return from;
}
public Address getTo() {
return to;
}
public int getCSeq() {
return CSeq;
}
public String getDialogFragmentId() {
return dfId;
}
}
}