/*
* 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.sip.PathNode.Type;
import com.ericsson.ssa.sip.dialog.DialogLifeCycle;
import org.apache.catalina.session.SessionLock;
import org.jvnet.glassfish.comms.util.LogUtil;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.Externalizable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.sip.Address;
import javax.servlet.sip.SipSession;
/**
* The DialogFragment has information for subsequent requests of the internal
* route of a dialog. A dialog may consist of many fragments because of spirals.
*
* @author ehsroha
*/
public class DialogFragment implements Externalizable {
private static final long serialVersionUID = -7081403938667526477L;
/** The serialized format versioning. 2 = second version. */
private static final short serializedFormVersion = 2;
// should be lower case
public static final String DEFAULT_FRAGMENT_ID = "default_fragment_id";
public static final String BAD_FRAGMENT_ID = "BAD_FRAGMENT_ID";
private static final Logger m_Logger = LogUtil.SIP_LOGGER.getLogger();
private LinkedList<PathNode> m_Nodes = new LinkedList<PathNode>();
private Ascii7String m_ToTag = null;
private String m_FragmentId = null; //DEFAULT_FRAGMENT_ID;
private DialogSet m_DialogSet;
private boolean m_IsValid = true;
private AtomicInteger m_SessionCounter = new AtomicInteger(0);
private long m__DialogStartedTimestamp = 0;
protected Object validLock = new Object();
private long invalidationTimeStamp;
private String invalidationThreadInfo;
private boolean isReplicable = (DialogFragmentManager.getInstance() instanceof PersistentDialogFragmentManager);
private Ascii7String m_callId = null;
private Address m_from = null;
private String m_beKey = null;
private transient boolean isMixedPersistence = false;
private transient SessionLock sessionLock = new SessionLock();
private boolean isConfirmed;
private transient DialogLifeCycle dialogLifeCycle;
private boolean isInitial = false;
private boolean registered = false;
private ReentrantLock messageLock = new ReentrantLock();
private boolean isInviteDialog = false;
public void obtainLockForIncomingMessage() {
messageLock.lock();
}
public void releaseLockForIncomingMessage() {
try {
messageLock.unlock();
} catch(IllegalMonitorStateException ex) {
// ignore.
}
}
/**
* Use DialogFragmentManager as a factory to create DialogFragments.
* However a dialogFragment can be cloned directly.
*
*/
protected DialogFragment(DialogSet set) {
m_DialogSet = set;
m_callId = set.getCallIdAscii7String(); // shared reference with ds
m_from = set.getFrom(); // shared reference with ds
m_beKey = set.getBeKey();
m_FragmentId = set.nextFragmentId();
//m_DialogSet.addDialog(this);
m__DialogStartedTimestamp = System.currentTimeMillis();
isInitial = true;
}
/* If DialogLifeCycle is initialized in the constructor, other threads
* are seeing partially constructed dialog fragment
*/
protected void initDialogLifeCycle() {
dialogLifeCycle = new DialogLifeCycle(this);
}
// Support For externalization
public DialogFragment() {
}
public boolean isInitialDialog() {
return isInitial;
}
public static String createKey(String callId, String fromTag, String toTag) {
return createKey(callId, fromTag, toTag, DEFAULT_FRAGMENT_ID);
}
/**
* Create a key from the given arguments. A null value for toTag causes the
* corresponding part of the key to be suppressed. Likewise, a zero value for
* fragmentId causes that part of the ket to be suppressed.
*
* @param callId
* @param fromTag
* @param toTag
* @param fragmentId
* @return
*/
public static String createKey(String callId, String fromTag, String toTag,
String fragmentId) {
StringBuilder sb = new StringBuilder(callId);
// Ensure that the order of from-tag and to-tag is determined
// by the lexical order of the two strings, to make the order constant.
if ((fromTag == null) && (toTag == null)) {
// Should never happen, but just in case there will be nothing added
} else if ((fromTag != null) && (toTag == null)) {
sb.append('&');
sb.append(fromTag);
} else if ((fromTag == null) && (toTag != null)) {
sb.append('&');
sb.append(toTag);
} else if (fromTag.compareTo(toTag) <= 0) {
sb.append('&');
sb.append(fromTag);
sb.append('&');
sb.append(toTag);
} else {
sb.append('&');
sb.append(toTag);
sb.append('&');
sb.append(fromTag);
}
if (fragmentId != null) {
sb.append(':');
sb.append(fragmentId.toLowerCase());
}
return sb.toString();
}
/**
* @serialData See serialized form version
* #serializedFormVersion in #readExternal(ObjectInput in)
*
* @param out the stream to write the object members
*
* @throws IOException
*/
public void writeExternal(ObjectOutput out) throws IOException {
out.writeShort(serializedFormVersion);
out.writeInt(m_Nodes.size());
Iterator<PathNode> pni = m_Nodes.iterator();
while (pni.hasNext()) {
out.writeObject(pni.next());
}
if (m_ToTag == null) {
out.writeUTF("");
} else {
out.writeUTF(m_ToTag.toString());
}
out.writeObject(m_FragmentId);
// m_DialogSet should not be serialized because it is confirmed
out.writeUTF(m_callId.toString());
out.writeObject(m_from);
out.writeBoolean(m_IsValid);
out.writeBoolean(isInitial);
out.writeInt(m_SessionCounter.get());
out.writeObject(m_beKey);
out.writeBoolean(isInviteDialog);
}
/**
* @serialData first field is an short and represents the serializedFormVersion.<br>
* <h3>Data layout for serializedFormVersion = 1 follows</h3>
*
* <li>field is a <b>Integer</b> and represents the number of written pathnodes</li>
* <li>0..n fields of type <b>PathNode</b> representing all pathnodes in this dialogfragment</li>
* <li>field is a <b>UTF String</b> and represents the m_toTag field</li>
* <li>field is a <b>UTF String</b> and represents the fragmentId</li>
* <li>field is a <b>UTF String</b> and represents the callId</li>
* <li>field is a <b>javax.servlet.sip.Address</b> and represents the from field</li>
* <li>field is a <b>Boolean</b> and represents the isValid field</li>
* <li>field is a <b>Boolean</b> and represents the isInitial field</li>
* <li>field is a <b>Integer</b> and represents the session counter</li>
*
* <h3>Data layout for serializedFormVersion = 2 follows</h3>
*
* <li>field is a <b>Integer</b> and represents the number of written pathnodes</li>
* <li>0..n fields of type <b>PathNode</b> representing all pathnodes in this dialogfragment</li>
* <li>field is a <b>UTF String</b> and represents the m_toTag field</li>
* <li>field is a <b>UTF String</b> and represents the fragmentId</li>
* <li>field is a <b>UTF String</b> and represents the callId</li>
* <li>field is a <b>javax.servlet.sip.Address</b> and represents the from field</li>
* <li>field is a <b>Boolean</b> and represents the isValid field</li>
* <li>field is a <b>Boolean</b> and represents the isInitial field</li>
* <li>field is a <b>Integer</b> and represents the session counter</li>
* <li>field is a <b>String</b> and represents the beKey field</li>
* <li>field is a <b>Boolean</b> and represents the isInviteDialog field</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.
dialogLifeCycle = new DialogLifeCycle(this);
m_Nodes = new LinkedList<PathNode>();
int i = in.readInt();
for (int c = 0; c < i; c++) {
m_Nodes.add((PathNode) in.readObject());
}
m_ToTag = new Ascii7String(in.readUTF());
if (m_ToTag.length() == 0) {
// map empty string back to null value
m_ToTag = null;
} else {
registered = true;
}
m_FragmentId = (String)in.readObject();
m_callId = new Ascii7String(in.readUTF());
m_from = (Address) in.readObject();
// m_DialogSet is null not valid for confirmed dialogs
m_IsValid = in.readBoolean();
isInitial = in.readBoolean();
m_SessionCounter = new AtomicInteger(in.readInt());
validLock = new Object();
sessionLock = new SessionLock();
isConfirmed = true;
}
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);
m_beKey = (String)in.readObject();
isInviteDialog = in.readBoolean();
}
public DialogSet getDialogSet() {
if (m_DialogSet == null) {
throw new IllegalStateException(
"This DialogFragment is in confirmed state and has no longer a DialogSet reference");
}
return m_DialogSet;
}
public Object clone() {
//DialogFragment d = new DialogFragment(m_DialogSet);
DialogFragment d = m_DialogSet.createAdditionalDialogFragment();
LinkedList<PathNode> nodes = new LinkedList<PathNode>();
PathNode p = null;
for (int i = 0; i < m_Nodes.size(); ++i) {
p = (PathNode) m_Nodes.get(i).clone();
nodes.add(p);
// if (!p.isReplicable()) {
// d.isReplicable = false;
// }
}
d.m_Nodes = nodes;
d.m_FragmentId = m_FragmentId;
d.isReplicable = isReplicable;
return d;
}
/**
* Will clone the dialog fragment starting from caller to callee until the
* pathNode is found or all if pathNode is not found.
*
* @param pn
* the stop indication for cloning
* @return cloned dialog fragment
*/
public DialogFragment cloneFromCallerToCalleeUntil(PathNode pn,
boolean excludePn) {
//DialogFragment d = new DialogFragment(m_DialogSet, true);
DialogFragment d = m_DialogSet.createAdditionalDialogFragment();
LinkedList<PathNode> nodes = new LinkedList<PathNode>();
PathNode p = null;
PathNode clone = null;
Iterator<PathNode> iter = getCaller2CalleePath();
while (iter.hasNext()) {
p = iter.next();
// will stop cloning when pathNode is equal, exclude pn
if (excludePn && pn.equals(p)) {
break;
}
clone = (PathNode) p.clone();
nodes.add(clone);
if (!clone.isReplicable()) {
d.isReplicable = false;
}
// will stop cloning when pathNode is equal, include pn
if (!excludePn && pn.equals(p)) {
break;
}
}
d.m_Nodes = nodes;
return d;
}
/**
* The cluster-wide unique identity of the dialog fragment. Consists of
* <ul>
* <li>Call-Id
* <li>Trom-tag
* <li>To-tag
* <li>Server instance name
* <li>Sequence number (within the dialog set)
* </ul>
*
* @return
*/
public String getDialogId() {
return createKey(m_callId.toString(),
m_from.getParameter(AddressImpl.TAG_PARAM), getToTag(), m_FragmentId);
}
public String getToTag() {
// Sometimes the object is not null but the bytes insisde it is. ???
synchronized (this) {
return (m_ToTag == null) ? null : m_ToTag.toString();
}
}
public void registerSession(SipSession s) {
m_SessionCounter.getAndIncrement();
}
/**
* The session is registered with this dialog fragment and MUST invoke
* invalidate once it is invalidated itself. Sets the to tag of this dialog.
*
* @param toTag
* @param s
*/
public void setToTagAndRegisterSession(String toTag, SipSession s) {
registerSession(s);
if (registered == false) {
// doubled checked locking pattern
synchronized (this) {
if (registered == false) {
m_ToTag = new Ascii7String(toTag);
DialogFragmentManager.getInstance()
.registerDialogFragment(this);
registered = true;
}
}
}
}
/**
* Will try to set the toTag of this DialogFragment and register it. If it
* fails the toTag is not set because it already has another toTag.
*
* @param toTag
* the toTag to update this DialogFragment with
* @param isDialogCreational
* If true the DialogFragment is registered for subsequent requests.
* @return true if DialogFragment has same toTag or was able to set the toTag
* or false if another toTag was already assigned to this
* DialogFragment
*/
public boolean tryToSetToTagAndRegisterDialog(String toTag,
boolean isDialogCreational) {
if (registered == false) {
// doubled checked locking pattern
synchronized (this) {
if (registered == false) {
// FIXME use SessionManager.getInstance().putIfAbsent instead
m_ToTag = new Ascii7String(toTag);
if (isDialogCreational) {
DialogFragmentManager.getInstance()
.registerDialogFragment(this);
}
registered = true;
return true;
}
}
}
return m_ToTag.equals(toTag);
}
public String getFragmentId() {
return m_FragmentId;
}
/**
* Returns the number of milliseconds since created Intended to be used by
* the Performance Management functionality when the fragment with id 1 is
* invalidated
*/
public long getDialogLifetime() {
return System.currentTimeMillis() - m__DialogStartedTimestamp;
}
/**
* Removes the dialog fragment from the registry once all registered sessions
* are invalidated. A session MUST call this invalidate method to aviod
* memory leaks.
*/
public void invalidate(boolean hasTimedOut) {
getDialogLifeCycle().markRemoveWhenFinished();
if (m_SessionCounter.decrementAndGet() == 0) {
synchronized (validLock) {
if (isValidForIncomingTraffic()) {
// NOT invalidating since the DF is still ready for incoming traffic
return;
}
if (m_DialogSet != null) {
m_DialogSet.removeDialog(this);
m_DialogSet = null;
}
this.m_IsValid = false;
this.invalidationTimeStamp = System.currentTimeMillis();
Thread t = Thread.currentThread();
this.invalidationThreadInfo = "Id=" + t.getId() + ", name=" +
t.getName();
}
}
}
/**
* Gets the valid state of the dialogfragment.<br>
* If the {@link #invalidate(boolean)} method has been called for this
* object.<br>
* this method should return false.
*
* @return true if valid.
*/
public boolean isValid() {
synchronized (validLock) {
return this.m_IsValid;
}
}
public boolean isValidForIncomingTraffic() {
for (PathNode pn : m_Nodes) {
SipSessionImplBase ss;
ss = (SipSessionImplBase) pn.getSipSession();
// Df can be used for incoming traffic as long as any
// pathnode ss is valid for sip traffic (isReadyToInvalidate()==false)
if (ss != null && ss.isValid() && ss.isReadyToInvalidate()==false) {
return true;
}
}
return false;
}
/**
* This method return the referenceCounter
* This value is increased by a call to registerSession(ss)
* and decreased by a call to invalidate()
* @return
*/
public int getSessionCount() {
return m_SessionCounter.get();
}
/**
* Gets the time when the {@link #invalidate(boolean)} method was called.
*
* @return The time in milliseconds.
*/
public long getInvalidationTimeStamp() {
synchronized (validLock) {
return this.invalidationTimeStamp;
}
}
public String getInvalidationThreadInfo() {
synchronized (validLock) {
return this.invalidationThreadInfo;
}
}
/**
* Builds the application path by adding one node starting from caller.
*
* @param node
* add one node to the internal record route.
*/
public synchronized void addToPath(PathNode node) {
m_Nodes.add(node);
if (m_Logger.isLoggable(Level.WARNING)) {
// it is a mismatch if we have a different replication value as what was set by the first pathnode
if (!isMixedPersistence && (size() > 1) &&
(isReplicable != node.isReplicable())) {
// we only report the first occurrence of a mismatch
isMixedPersistence = true;
m_Logger.log(Level.WARNING,
DialogFragment.class.getCanonicalName() +
".mixed_replication_types",
new Object[] {
node.getApplicationSession().getName(),
m_Nodes.get(0).getApplicationSession().getName()
});
}
}
// only replicate if all node are replicable
isReplicable = isReplicable && node.isReplicable();
}
public PathNode getFirst() {
return m_Nodes.getFirst();
}
public PathNode getLast() {
return m_Nodes.getLast();
}
/**
* Return number of PathNodes
*
* @return number of PathNodes
*/
public synchronized int size() {
return m_Nodes.size();
}
/**
* Returns the path of an application or a transaction, which is the internal
* route for subsequent requests of a dialog respectively the internal via
* for responses. Will iterate from caller to callee
*
* @return an iterator from caller to callee of the path objects representing
* one node of the application or the transaction path.
*/
public synchronized Iterator<PathNode> getCaller2CalleePath() {
List<PathNode> nodes = new ArrayList<PathNode>(m_Nodes);
return nodes.iterator();
}
/**
* Returns the path of an application or a transaction, which is the internal
* route for subsequent requests of a dialog respectively the internal via
* for responses. Will iterate from callee to caller
*
* @return an iterator from callee to caller of the path objects representing
* one node of the application or the transaction path.
*/
public synchronized Iterator<PathNode> getCallee2CallerPath() {
List<PathNode> nodes = new ArrayList<PathNode>(m_Nodes);
return new BackwardListIterator<PathNode>(nodes);
}
/**
* Returns the origin of this message. Returns true if it is sent from the
* caller of the dialog.
*
* @param m
* the message from either the caller or the callee
* @return true if it is sent from the caller of the dialog otherwise false.
* @throws NotEqualDialogException
*/
public boolean isMessageFromCaller(SipServletMessageImpl m)
throws NotEqualDialogException {
if (m.getCallId().equals(m_callId.toString())) {
String fromTag = m.getFromImpl().getParameter(AddressImpl.TAG_PARAM);
String toTag = m.getToImpl().getParameter(AddressImpl.TAG_PARAM);
boolean isFromCaller = false;
boolean isFromCallee = false;
if (!m.hasToTag() || (getToTag() == null)) {
// If no to-tag exist either the message
// is an initial request or a response
// to that initial request and the request
// will orginate from the caller.
// TODO A message without tags could also be a
// valid message from a RFC 2543 compliant UA.
// This is not detected here.
isFromCaller = getFromTag().equals(fromTag);
} else {
isFromCaller = getFromTag().equals(fromTag) &&
getToTag().equals(toTag);
isFromCallee = getFromTag().equals(toTag) &&
getToTag().equals(fromTag);
}
// It's not the same dialog if the direction
// of the request doesn't match either the
// caller or the callee
if (!isFromCaller && !isFromCallee) {
throw new NotEqualDialogException();
}
return isFromCaller;
} else {
throw new NotEqualDialogException();
}
}
@Override
public String toString() {
StringBuilder str = new StringBuilder();
String NEW_LINE = System.getProperty("line.separator");
str.append("[ DialogFragment fragmentId=" + this.m_FragmentId);
str.append(NEW_LINE);
str.append(", dialogStartedTimestamp=" +
this.m__DialogStartedTimestamp);
str.append(", hashCode=" + this.hashCode());
str.append(", dialogLifetime=" + this.getDialogLifetime());
str.append(", isValid=" + this.m_IsValid);
str.append(", sessionCounter=" + this.m_SessionCounter);
str.append(", node size=" + this.m_Nodes.size());
str.append(", m_ToTag=" + this.m_ToTag);
SipApplicationSessionImpl sip = null;
String s = "SipApplicationSessionImpl=";
for (PathNode p : m_Nodes) {
if (p == null) {
continue;
}
str.append(NEW_LINE);
sip = p.getApplicationSession();
str.append(", PathNode: type=" + p.getType() + ", hashCode" +
p.hashCode() + ", servlet=" + sip);
if (sip != null) {
s += (", ID=" + sip.getId() + ", name=" + sip.getName() +
" hashCode=" + sip.hashCode());
}
str.append(sip);
}
str.append(NEW_LINE);
str.append(" ]");
return str.toString();
}
/**
* Returns true if this DialogFragment is replicable, false otherwise.
*
* A DialogFragment is considered replicable if and only if all of its
* PathNodes are replicable.
*
* @return true if this DialogFragment is replicable, false otherwise
*/
public boolean isReplicable() {
return isReplicable;
}
public String getCallId() {
return m_callId.toString();
}
public Address getFrom() {
return m_from;
}
public void setFrom(Address from) {
this.m_from = from;
}
public String getBeKey() {
return m_beKey;
}
/**
* @return the from tag of this Dialog.
*/
public String getFromTag() {
return m_from.getParameter(AddressImpl.TAG_PARAM);
}
public boolean isConfirmed() {
return isConfirmed;
}
public void setConfirmed() {
if (!isConfirmed) {
for (Iterator<PathNode> iterator = m_Nodes.iterator(); iterator.hasNext();) {
PathNode pn = iterator.next();
SipSessionImplBase session = (SipSessionImplBase) pn.getSipSession();
if (session == null || !session.isValid()) {
iterator.remove();
if(session != null) {
session.doCleanup();
}
if (m_Logger.isLoggable(Level.FINEST)) {
m_Logger.log(Level.FINEST, "Removed pathnode since it is obsolete: "+pn + "(SS-ID: "+pn.getSipSessionId()+")");
}
} else if (pn instanceof ProxyContext) {
((ProxyContext) pn).setConfirmed();
}
}
if (isReplicable) {
for (PathNode pn : m_Nodes) {
SipSessionBase ss;
ss = pn.getSipSession();
if ((ss != null) && ss.isValid()) {
ss.setShouldBePersisted();
}
}
}
isConfirmed = true;
}
// Mindnotes for DF/DS refactoring....
// ZZZ m_DialogSet = null;
// let this be done by the transaction on the initial request
// that could remove the early dialog + remove the ds reference from df
// JSR289 team should implement this?
// beware of multiple 200's via for example b2bua helper
}
/**
* 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;
}
/**
* Gets the CSEQ from the contained UA:s.
* Will only contain a value if it there is a single UA in the application path.
* In case there are two or no UA:s null is returned.
* @return the CSEQ from the contained UA:s
*/
public String getCSeq() {
UA[] uas = getUas();
if ((uas[0] != null) && (uas[1] == null)) {
return "" + uas[0].getRemoteCSeq();
} else {
return null;
}
}
/**
* Sets the CSEQ on the contained UA, provided that it is only one UA in the application
* path. In case there are two UA:s (i.e, the application path is terminated in both ends)
* the CSEQ is taken crosswise from their respective SipSession.
*
* (And, of course, if there are no UA:s, i.e. the path contains only proxies, CSEQ has no meaning)
* @param cseqStr
*/
public void setCSeq(String cseqStr) {
UA[] uas = getUas();
if ((cseqStr != null) && (uas[0] != null) && (uas[1] == null)) {
uas[0].setRemoteCSeq(Integer.parseInt(cseqStr));
} else if ((uas[0] != null) && (uas[1] != null)) {
uas[0].setRemoteCSeq(uas[1].getSipSession().getCSeq());
uas[1].setRemoteCSeq(uas[0].getSipSession().getCSeq());
if ((cseqStr != null) && m_Logger.isLoggable(Level.WARNING)) {
m_Logger.log(Level.WARNING,
DialogFragment.class.getCanonicalName() + ".cseq_two_ua");
}
} else if ((cseqStr == null) && (uas[0] != null) && (uas[1] == null)) {
throw new RuntimeException(
"There was no CSEQ given although there exists a UA.");
} else if ((cseqStr != null) && (uas[0] == null) && (uas[1] == null)) {
if (m_Logger.isLoggable(Level.WARNING)) {
m_Logger.log(Level.WARNING,
DialogFragment.class.getCanonicalName() + ".cseq_no_ua");
}
}
}
private UA[] getUas() {
UA[] uas = new UA[2];
int i = 0;
for (PathNode pn : m_Nodes) {
if (pn instanceof UA) {
uas[i++] = ((UA) pn);
if (i >= uas.length) {
break;
}
}
}
return uas;
}
public boolean isInviteDialog() {
return isInviteDialog;
}
public void setInviteDialog(boolean inviteDialog) {
this.isInviteDialog = inviteDialog;
}
/**
* Helper class for backward iterations of a list
*
* @author ehsroha
*/
public class BackwardListIterator<T> implements Iterator<T> {
private List<T> m_List;
private int m_Index = -1;
public BackwardListIterator(List<T> list) {
m_List = list;
m_Index = list.size();
}
public boolean hasNext() {
return m_Index > 0;
}
public T next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
m_Index--;
return m_List.get(m_Index);
}
public void remove() {
// should not work, read only
throw new IllegalStateException();
}
}
public DialogLifeCycle getDialogLifeCycle() {
return dialogLifeCycle;
}
/**
* Checks if the dialog contains a UA
* @return true if there is a UA in the application path
*/
public boolean containsUA() {
for (PathNode pn : m_Nodes) {
if (pn.getType() == Type.Callee || pn.getType() == Type.Caller) return true;
}
return false;
}
/**
* Removes the dialog set and clears the reference to it.
* @param recurse
*/
public synchronized void clearDialogSet(boolean recurse) {
if (m_DialogSet != null) {
if (recurse) {
m_DialogSet.remove();
}
m_DialogSet = null;
}
}
/**
* Remove the caller from the pathnodes.
* <br>
* The caller is expected to be the first element in the list
* of pathnodes.
*
* @param id The sessionId of the requesting sipsession. When logging
* is enabled, a sanity check will be done to see if the requestor is
* indeed the caller and the first entry in the list.
*/
public void removeCallerFromPath(String id) {
PathNode removedPN = m_Nodes.removeFirst();
// do some sanity checks if logging is enabled
if (m_Logger.isLoggable(Level.FINE)) {
if (removedPN.getType() != Type.Caller) {
m_Logger.log(Level.FINE, "type of first pathnode is NOT Caller");
}
if (removedPN.getSipSessionId() != id) {
m_Logger.log(Level.FINE, "id of first pathnode does not match");
}
}
}
}