/*
* 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.dialog;
import com.ericsson.ssa.sip.DialogFragment;
import com.ericsson.ssa.sip.DialogFragmentManager;
import com.ericsson.ssa.sip.PathNode;
import com.ericsson.ssa.sip.RemoteLockException;
import com.ericsson.ssa.sip.SipApplicationSessionImpl;
import com.ericsson.ssa.sip.SipSessionDialogImpl;
import com.ericsson.ssa.sip.SipSessionImplBase;
import com.ericsson.ssa.sip.persistence.ReplicationUnitOfWork;
import com.ericsson.ssa.sip.transaction.TransactionManager;
import org.jvnet.glassfish.comms.util.LogUtil;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* This is the life cycle of a dialog. It handles various activities:
* - keeping track of UOW on dialog (inside or outside transaction)
* - orchestrating locking/unlocking and saving
* - handle removal of dialog structure at invalidation
* - (future) handle removal of obsolete DialogSet.
*
*/
public class DialogLifeCycle implements Cleanable {
private final static Logger logger = LogUtil.SIP_LOGGER.getLogger();
private volatile boolean removeDialog = false;
private ReplicationUnitOfWork uow = new ReplicationUnitOfWork(false);
private DialogFragment df;
private Set<String> ongoingTransactionIds = new LinkedHashSet<String>();
public DialogLifeCycle(DialogFragment df) {
this.df = df;
}
/**
* Mark this dialog to be removed when all transactions have finished (or
* in case no transactions exist: when supervision period expires).
*/
public void markRemoveWhenFinished() {
if (logger.isLoggable(Level.FINEST)) {
logger.log(Level.FINEST, "ENTER: {" + this + "}");
}
boolean doRegister = false;
synchronized (this) {
if (!removeDialog) { // Only do this the first time
if (ongoingTransactionIds.isEmpty()) {
doRegister = true;
}
removeDialog = true;
}
}
if (doRegister) {
DialogCleaner.getInstance().registerForSupervision(this);
}
if (logger.isLoggable(Level.FINEST)) {
logger.log(Level.FINEST, "EXIT: {" + this + "}");
}
}
/**
* Associates the transaction to this dialog and connects the life cycle (
* save UOW, release locks) with that transaction (and other possible
* transactions working on the dialog).
*
* Unregister from supervision.
*
* @param transactionId
*/
public void associateTransaction(String transactionId) {
if (logger.isLoggable(Level.FINEST)) {
logger.log(Level.FINEST, "ENTER: transactionId: " + transactionId + " {" + this + "}");
}
synchronized (this) {
if ((transactionId == null) || !TransactionManager.getInstance().transactionExists(transactionId)) {
if (logger.isLoggable(Level.FINEST)) {
logger.log(Level.FINEST, "Tried to associated transaction: " + transactionId + " with dialog: " + df.getDialogId() + " but transaction did not exist.");
}
return;
} else {
if (logger.isLoggable(Level.FINEST)) {
logger.log(Level.FINEST, "Associated transaction: " + transactionId + " with dialog: " + df.getDialogId());
}
ongoingTransactionIds.add(transactionId);
}
}
DialogCleaner.getInstance().unregisterFromSupervision(this);
if (logger.isLoggable(Level.FINEST)) {
logger.log(Level.FINEST, "EXIT: {" + this + "}");
}
}
/**
* Acts on the event that a transaction has been removed. If this was the
* last of the associated transactions a possible UOW is saved and the
* dialog is unlocked.
*
* @param transactionId
* @param delay indicates that a possible finish shall be delayed until
* {@link #trigDelayedFinish()} is called; however, it will
* register the DLC for supervision.
*/
public void onTransactionRemoved(String transactionId, boolean delay) {
if (logger.isLoggable(Level.FINEST)) {
logger.log(Level.FINEST, "ENTER: transactionId: " + transactionId + ", delay: " + delay + " {" + this + "}");
}
boolean doFinish = false;
synchronized (this) {
ongoingTransactionIds.remove(transactionId);
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "Removed transaction: " + transactionId + " from dialog: " + df.getDialogId());
}
if (ongoingTransactionIds.isEmpty()) {
doFinish = true;
}
}
if (doFinish) {
if (delay) {
// Delay until trigDelayedFinish() has been passed
DialogCleaner.getInstance().registerForSupervision(this);
} else {
handleFinish();
}
}
if (logger.isLoggable(Level.FINEST)) {
logger.log(Level.FINEST, "EXIT: {" + this + "}");
}
}
public void trigDelayedFinish() {
if (logger.isLoggable(Level.FINEST)) {
logger.log(Level.FINEST, "ENTER: {" + this + "}");
}
boolean doFinish = false;
synchronized (this) {
if (ongoingTransactionIds.isEmpty()) {
doFinish = true;
}
}
if (doFinish) {
DialogCleaner.getInstance().unregisterFromSupervision(this);
handleFinish();
}
if (logger.isLoggable(Level.FINEST)) {
logger.log(Level.FINEST, "EXIT: {" + this + "}");
}
}
/**
* Force a save of the UOW (but keep the dialog lock).
*/
public void save() {
if (logger.isLoggable(Level.FINEST)) {
logger.log(Level.FINEST, "ENTER: {" + this + "}");
}
uow.save();
if (logger.isLoggable(Level.FINEST)) {
logger.log(Level.FINEST, "EXIT: {" + this + "}");
}
}
/**
* Acts on timeout from the supervision of dialogs that have no transaction
* associated.
*/
public void doCleanup() {
if (logger.isLoggable(Level.FINEST)) {
logger.log(Level.FINEST, "ENTER: {" + this + "}");
}
handleFinish();
if (logger.isLoggable(Level.FINEST)) {
logger.log(Level.FINEST, "EXIT: {" + this + "}");
}
}
/**
* Starts a unit of work on this dialog.
* @throws RemoteLockException
*/
public void initUnitOfWork() {
initUnitOfWork(null);
}
/**
* Starts a unit of work on this dialog. Optionally locks dialog structure
* based on the SAS.
*
* @param sas the SAS making up the dialog structure (for initial requests
* where no application path has been established); if null
* the dialog structure is deduced from the DF.
* @throws RemoteLockException
*/
public void initUnitOfWork(SipApplicationSessionImpl sas) {
if (logger.isLoggable(Level.FINEST)) {
logger.log(Level.FINEST, "ENTER: sas: " + sas + " {" + this + "}");
}
if (sas == null) {
uow.lockDialog(df);
} else {
uow.lockApplicationSession(sas);
}
boolean doRegister = false;
synchronized (this) {
if (ongoingTransactionIds.isEmpty()) {
doRegister = true;
}
}
if (doRegister) {
DialogCleaner.getInstance().registerForSupervision(this);
}
if (logger.isLoggable(Level.FINEST)) {
logger.log(Level.FINEST, "EXIT: {" + this + "}");
}
}
/**
* Activates the UOW of this dialog on the current thread, i.e. sets the thread local UOW.
*/
public void setThreadLocalUnitOfWork() {
if (logger.isLoggable(Level.FINEST)) {
logger.log(Level.FINEST, "ENTER: {" + this + "}");
}
uow.setThreadLocal();
if (logger.isLoggable(Level.FINEST)) {
logger.log(Level.FINEST, "EXIT: {" + this + "}");
}
}
DialogFragment getDialogFragment() {
return df;
}
private void handleFinish() {
if (logger.isLoggable(Level.FINEST)) {
logger.log(Level.FINEST, "Handle finish {" + this + "}");
}
uow.saveAndUnlock();
boolean localRemove = false;
synchronized(this) {
localRemove = removeDialog;
removeDialog = false;
}
if (localRemove) {
removeDialog(df);
}
}
/**
* Checks if this dialog is associated with at least one ongoing transaction.
* @return true if this UOW is associated with at least one ongoing transaction.
*/
synchronized boolean hasOngoingTransaction() {
return !ongoingTransactionIds.isEmpty();
}
private void removeDialog(DialogFragment df) {
if (logger.isLoggable(Level.FINER)) {
logger.log(Level.FINER, "Checked invalidation of dialog: " + df.getDialogId());
}
boolean validSSExists = false;
boolean forcedInvalidation = false;
for (Iterator<PathNode> pnIt = df.getCallee2CallerPath(); pnIt.hasNext();) {
SipSessionDialogImpl ss;
try {
ss = (SipSessionDialogImpl) (SipSessionImplBase) pnIt.next().getSipSession();
if (ss != null) {
if (ss.isValid()) {
validSSExists = true;
} else {
if (ss.invalidatedDueIWR() == false) {
// we have got a explicit or sas expire invalidation
// I.e SipSession ends before sip dialog ends, a.k.a "forcedInvalidation"
forcedInvalidation = true;
break;
} else {
ss.doCleanup();
}
}
} else {
// This is quite OK if there is two or more sipsessions in df have different lifecycles
if (logger.isLoggable(Level.FINEST)) {
logger.log(Level.FINEST, "When removing dialog" + df.getDialogId() + ": Found a reference from a path node to a SipSession which was not found. Ignored, this is quite OK if there is two or more sipsessions in df having different lifecycles");
}
}
} catch (Throwable t) {
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "Exception at invalidation" + t + "; Continue with all other sessions in the DF");
}
}
}
if (forcedInvalidation) {
// go through all SS'es again and this time invalidate all of them, no mercy :-)
forcedInvalidate(df);
validSSExists = false;
}
if (validSSExists && (df.getSessionCount() >= 1)) {
// We keep the df if there is a valid ss still referring to it
// But we also check the reference count to cater for the ss.reset() case
// (ss.reset() means we will have a valid ss but df ref counter is down to zero --> df must be removed
if (logger.isLoggable(Level.FINEST)) {
logger.log(Level.FINEST, "Valid SipSession(s) remains, keeping DialogFragment: " + df.getDialogId());
}
} else {
DialogFragmentManager.getInstance().removeDialogFragment(df, false);
if (logger.isLoggable(Level.FINEST)) {
logger.log(Level.FINEST, "Removed DialogFragment: " + df.getDialogId());
}
}
}
private void forcedInvalidate(DialogFragment df) {
for (Iterator<PathNode> pnIt = df.getCallee2CallerPath(); pnIt.hasNext();) {
SipSessionDialogImpl ss;
try {
ss = (SipSessionDialogImpl) (SipSessionImplBase) pnIt.next().getSipSession();
if (ss != null) {
if (ss.isValid()) {
if (logger.isLoggable(Level.FINEST)) {
logger.log(Level.FINEST, "Forced invalidation of SipSession involved in a dialog, id: " + ss.getId());
}
ss.invalidate();
}
ss.doCleanup();
}
} catch (Throwable t) {
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "Exception at invalidation" + t + "; Continue with all other sessions in the DF");
}
}
}
}
/**
* Checks that this UOW is the current UOW in the thread.
*/
public void checkUnitOfWork() {
if (logger.isLoggable(Level.FINEST)) {
logger.log(Level.FINEST, "ENTER: {" + this + "}");
}
if (ReplicationUnitOfWork.getThreadLocalUnitOfWork() != uow) {
assert false : "The thread local unit of work: " + ReplicationUnitOfWork.getThreadLocalUnitOfWork() + " is not the same as the local unit of work: " + uow;
if (logger.isLoggable(Level.WARNING)) {
logger.log(Level.WARNING, "The thread local unit of work: " + ReplicationUnitOfWork.getThreadLocalUnitOfWork() + " is not the same as the local unit of work: " + uow);
}
}
if (logger.isLoggable(Level.FINEST)) {
logger.log(Level.FINEST, "EXIT: {" + this + "}");
}
}
@Override
public String toString() {
return super.toString()+"{df=" + df.getDialogId() + ",trs=" + ongoingTransactionIds + ", uow=" + uow + ", removeDialog=" + removeDialog+"}";
}
}