/**
* JOnAS: Java(TM) Open Application Server
* Copyright (C) 1999-2004 Bull S.A.
* Contact: jonas-team@objectweb.org
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA
*
* --------------------------------------------------------------------------
* $Id: JStatefulSwitch.java 7900 2006-01-18 16:04:21Z durieuxp $
* --------------------------------------------------------------------------
*/
package org.objectweb.jonas_ejb.container;
import java.rmi.NoSuchObjectException;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import javax.ejb.EJBException;
import javax.ejb.NoSuchObjectLocalException;
import javax.ejb.RemoveException;
import javax.ejb.SessionSynchronization;
import javax.ejb.TransactionRolledbackLocalException;
import javax.transaction.InvalidTransactionException;
import javax.transaction.RollbackException;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import org.objectweb.transaction.jta.ResourceManagerEvent;
import org.objectweb.util.monolog.api.BasicLevel;
/**
* JStatefulSwitch is the implementation of JSessionSwitch dedicated to the
* Stateful Session Bean.
* @author Philippe Durieux
*/
public class JStatefulSwitch extends JSessionSwitch {
private int sessionId;
/**
* Count all curent accesses (transactional or not)
* A null value means that this instance is not currently used.
*/
private int usedcount = 0;
private JStatefulContext bctx = null;
private Transaction currTx = null;
private boolean mustCommit = false;
private boolean expired = false;
private Transaction beanTx = null; // only for bean managed tx
private long lastaccesstime;
private boolean passivated = false;
/**
* Saved connectionList for this instance. The real connectionList
* is maintained in a ThreadLocal variable in ThreadData.
* Used only for session stateful methods, if they keep
* connection along several calls, not always in same thread.
* This list must not be shared between all instances.
* This info is transient because JDBC connection have not to be
* saved on disk at passivation time : They should be closed in
* ejbPassivate().
*/
private List connectionList = Collections.synchronizedList(new ArrayList());
/**
* constructor.
* @param bf The Bean Factory
* @param sid the unique statefulSession ident
*/
public JStatefulSwitch(JStatefulFactory bf) throws RemoteException {
super(bf);
if (TraceEjb.isDebugIc()) {
TraceEjb.interp.log(BasicLevel.DEBUG, "");
}
}
public int getSessionId() {
return sessionId;
}
/**
* @return true if instance can be passivated
*/
public boolean canPassivate() {
if (usedcount > 0) {
TraceEjb.ssfpool.log(BasicLevel.DEBUG, "Victim is busy");
return false;
}
// Don't try to passivate if no matching session context!
return (! passivated && bctx != null);
}
/**
* @return true if instance has been passivated.
*/
public boolean isPassivated() {
return passivated;
}
/**
* Passivate this instance
*/
public synchronized boolean passivate() {
if (currTx != null || beanTx != null) {
TraceEjb.ssfpool.log(BasicLevel.DEBUG, "Cannot passivate: busy");
return false;
}
TraceEjb.ssfpool.log(BasicLevel.DEBUG, "Instance will be passivated");
passivated = ((JStatefulFactory)bf).passivateStateful(this);
if (passivated) {
// Disconnect instance so that it will be garbaged.
bctx.setInstance(null);
}
return passivated;
}
/**
* Set the connection list associated to the current thread
* with the list associated to this stateful session.
*/
public void pushConnectionList() {
if (TraceEjb.isDebugTx()) {
TraceEjb.tx.log(BasicLevel.DEBUG, "pushed connectionList =" + connectionList);
}
bf.getTransactionManager().pushThreadLocalRMEventList(connectionList);
}
/**
* save the current connectionList for future use (next preInvoke).
*/
public void popConnectionList() {
connectionList = bf.getTransactionManager().popThreadLocalRMEventList();
if (TraceEjb.isDebugTx()) {
TraceEjb.tx.log(BasicLevel.DEBUG, "poped connectionList =" + connectionList);
}
}
/**
* Save the Connection List after a create method.
*/
public void setConnectionList(List cl) {
connectionList = cl;
if (TraceEjb.isDebugTx()) {
TraceEjb.tx.log(BasicLevel.DEBUG, "init connectionList =" + connectionList);
}
}
/**
* enlist all connection of the list
*/
public void enlistConnections(Transaction tx) {
if (tx != null && connectionList != null) {
try {
for (Iterator it = connectionList.iterator(); it.hasNext();) {
ResourceManagerEvent rme = (ResourceManagerEvent) it.next();
if (rme != null) {
rme.enlistConnection(tx);
} else {
TraceEjb.tx.log(BasicLevel.WARN, "Null ResourceManagerEvent in Connection List");
}
}
} catch (SystemException e) {
TraceEjb.tx.log(BasicLevel.ERROR, "cannot enlist connection", e);
}
}
// push the connection list in case a tx is started by the bean
// or if the method close a connection previously enlisted.
pushConnectionList();
}
/**
* delist all connections of the list
*/
public void delistConnections(Transaction tx) {
popConnectionList();
}
// ===============================================================
// TimerEventListener implementation
// ===============================================================
/**
* The session timeout has expired
* @param arg Not Used.
*/
public synchronized void timeoutExpired(Object arg) {
if (TraceEjb.isVerbose()) {
TraceEjb.logger.log(BasicLevel.WARN, "stateful session timeout expired");
}
mytimer = null;
// Do not remove if still used in a transaction
if (currTx != null) {
expired = true;
} else {
if (bctx != null) {
try {
bctx.setRemoved();
} catch (RemoteException e) {
if (TraceEjb.isVerbose()) {
TraceEjb.logger.log(BasicLevel.WARN, "timeout expired", e);
}
} catch (RemoveException e) {
if (TraceEjb.isVerbose()) {
TraceEjb.logger.log(BasicLevel.WARN, "timeout expired", e);
}
}
}
noLongerUsed();
}
}
// ===============================================================
// other public methods
// ===============================================================
/**
* @return the StatefulContext (for passivation)
*/
public JStatefulContext getStatefulContext() {
if (sessionId == -1) {
throw new EJBException("This Session has been removed");
}
if (bctx == null) {
throw new EJBException("Already passivated");
}
return bctx;
}
/**
* At each business method, get a BeanContext to run it
* @param tx The Transaction Context
* @return The Session Context
*/
public synchronized JSessionContext getICtx(Transaction tx) {
if (TraceEjb.isDebugIc()) {
TraceEjb.interp.log(BasicLevel.DEBUG, "");
}
// If session has been removed, we must throw
// NoSuchObject[Local]Exception
// to the caller.
if (sessionId == -1) {
throw new NoSuchObjectLocalException("This Session has been removed");
}
lastaccesstime = System.currentTimeMillis();
// reload Context if it was passivated
if (passivated) {
TraceEjb.ssfpool.log(BasicLevel.DEBUG, "Bean has been passivated. Reactivate it");
((JStatefulFactory)bf).activateStateful(this);
passivated = false;
}
// Check Transaction
checkTx(tx);
usedcount++;
return bctx;
}
/**
* At each create, bind the Context to the transaction
* @param tx The current Transaction Context
* @param bctx The Context to bind
*/
public synchronized void bindICtx(Transaction tx, JStatefulContext bctx) {
TraceEjb.interp.log(BasicLevel.DEBUG, "");
sessionId = ((JStatefulFactory)bf).getNewSessionId(this);
this.bctx = bctx;
bctx.initSessionContext(this);
lastaccesstime = System.currentTimeMillis();
usedcount++;
// Check Transaction
checkTx(tx);
}
/**
* Release the Context after use.
* @param tx The current Transaction Context
* @param discard if true, instance must be discarded
*/
public synchronized void releaseICtx(RequestCtx req, boolean discard) {
TraceEjb.interp.log(BasicLevel.DEBUG, "");
usedcount--;
// In case getICtx failed, bctx may be null.
if (bctx == null) {
return;
}
if (bctx.isMarkedRemoved() || discard) {
stopTimer();
noLongerUsed();
}
}
/**
* This Session is no longer used: - unexport Remote Object - return the
* Session in the pool
*/
public void noLongerUsed() {
if (TraceEjb.isDebugIc()) {
TraceEjb.interp.log(BasicLevel.DEBUG, "");
}
// Unexport the EJBObject from the Orb
if (myremote != null) {
try {
myremote.unexportObject();
} catch (NoSuchObjectException e) {
TraceEjb.logger.log(BasicLevel.ERROR, "unexportObject failed", e);
}
}
// Forget transaction that could be uncommitted.
// Avoids to get it in another session bean later.
if (beanTx != null) {
TraceEjb.tx.log(BasicLevel.WARN, "transaction not ended. forget it");
beanTx = null;
}
// return the SessionSwitch in the pool.
// will be reused for another Session.
bf.removeEJB(this);
// Remove this object.
// Stateful session contexts are not pooled. (EJB spec.)
((JStatefulFactory)bf).removeStateful(sessionId);
bctx = null;
sessionId = -1;
passivated = false;
usedcount = 0;
}
/**
* End of Transaction
*/
public void txCompleted() {
if (TraceEjb.isDebugIc()) {
TraceEjb.interp.log(BasicLevel.DEBUG, "");
}
currTx = null;
if (expired) {
timeoutExpired(null); // try again
}
}
/**
* This is used for remove on stateful session beans only.
* @return True if bean is participating in a client transaction
*/
public boolean isInTransaction() {
return (currTx != null && !mustCommit);
}
/**
* set a flag to remember that the transaction must be committed
*/
public void setMustCommit(boolean mc) {
mustCommit = mc;
}
/**
* Keep the bean opened transaction for later use in other methods. Stateful
* session bean may open a transaction and use it in other methods. This is
* called at postInvoke
*/
public void saveBeanTx() {
if (bf.isTxBeanManaged()) {
if (TraceEjb.isDebugTx()) {
TraceEjb.tx.log(BasicLevel.DEBUG, "");
}
try {
beanTx = bf.getTransactionManager().suspend();
} catch (SystemException e) {
TraceEjb.logger.log(BasicLevel.ERROR, "cannot suspend transaction:", e);
}
}
}
/**
* @return the last access time in milliseconds
*/
public long getLastAccessTime() {
return lastaccesstime;
}
// ===============================================================
// private methods
// ===============================================================
/**
* Check Transaction This is called at preInvoke
* @param tx The current Transaction Context
* @throws EJBException
* @throws TransactionRolledbackLocalException
*/
private synchronized void checkTx(Transaction tx) {
// No check if no Synchro, except for bean managed transaction.
if (bf.isSessionSynchro() == false) {
// Resume bean associated transaction.
// Stateful session bean may open a transaction and use it in other
// methods.
if (bf.isTxBeanManaged() && beanTx != null) {
if (TraceEjb.isDebugTx()) {
TraceEjb.tx.log(BasicLevel.DEBUG, "resuming Bean Managed Tx");
}
try {
bf.getTransactionManager().resume(beanTx);
} catch (SystemException e) {
TraceEjb.logger.log(BasicLevel.ERROR, "cannot resume transaction", e);
} catch (InvalidTransactionException e) {
TraceEjb.logger.log(BasicLevel.ERROR, "Cannot resume transaction", e);
}
} else {
if (TraceEjb.isDebugTx()) {
TraceEjb.tx.log(BasicLevel.DEBUG, "no checkTx");
}
}
return;
}
if (tx == null) {
if (TraceEjb.isDebugTx()) {
TraceEjb.tx.log(BasicLevel.DEBUG, "(No Tx)");
}
if (currTx != null) {
// A synchronized session must be called in the same transaction
// between afterBegin and beforeCompletion calls
TraceEjb.logger.log(BasicLevel.ERROR, "synchronized session called outside transaction context");
throw new EJBException("Synchronized session called outside transaction context");
}
} else {
if (TraceEjb.isDebugTx()) {
TraceEjb.tx.log(BasicLevel.DEBUG, "");
}
if (currTx == null) {
// A new transaction starts on this synchronized session
try {
SessionSynchronization ssbean = (SessionSynchronization) bctx.getInstance();
if (ssbean == null) {
throw new EJBException("Instance should have been reactivated first.");
}
tx.registerSynchronization(bctx);
ssbean.afterBegin();
} catch (RollbackException e) {
throw new TransactionRolledbackLocalException("Session rolled back");
} catch (SystemException e) {
throw new EJBException("checkTx error", e);
} catch (RemoteException e) {
throw new EJBException("checkTx error", e);
}
currTx = tx;
} else {
// A synchronized session must be called in the same transaction
// between afterBegin and beforeCompletion calls
if (tx.equals(currTx) == false) {
TraceEjb.logger.log(BasicLevel.ERROR, "synchronized session called in another transaction context");
throw new EJBException("Synchronized session called in another transaction context");
}
}
}
}
}
|