/**
* JOnAS: Java(TM) Open Application Server
* Copyright (C) 1999 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: JEntitySwitchCS.java 9805 2006-10-27 08:28:32Z durieuxp $
* --------------------------------------------------------------------------
*/
package org.objectweb.jonas_ejb.container;
import javax.ejb.EJBException;
import javax.ejb.TransactionRolledbackLocalException;
import javax.transaction.Status;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import org.objectweb.jonas_ejb.deployment.api.EntityDesc;
import org.objectweb.util.monolog.api.BasicLevel;
/**
* Container Serialized (CS) lock-policy.
* Transaction Isolation managed by the container.
* Non transactional methods of the bean can modify the bean.
* @author Philippe Durieux
*/
public class JEntitySwitchCS extends JEntitySwitchCST {
/**
* time in millisec. to keep instance state in memory without storing it
* After this time, instance is stored on database.
* before.
*/
protected long passivationTimeout; // millisec.
/**
* timestamp uses for passivationTimeout
*/
protected long ptimestamp;
/**
* empty constructor. Object is initialized via init() because it is
* implemented differently according to jorm mappers.
*/
public JEntitySwitchCS() {
lockpolicy = EntityDesc.LOCK_CONTAINER_SERIALIZED;
txUpdates = false;
}
protected void initpolicy(JEntityFactory bf) {
lazyregister = false;
passivationTimeout = bf.getPassivationTimeout() * 1000;
ptimestamp = System.currentTimeMillis();
}
public void waitmyturn(Transaction tx) {
// Synchronization.
if (tx == null) {
// Must wait in case of TX
while (runningtx != null) {
if (TraceEjb.isDebugSynchro()) {
TraceEjb.synchro.log(BasicLevel.DEBUG, ident + "mapICtx IH: WAIT end IT");
}
waiters++;
try {
wait(10000L);
if (TraceEjb.isDebugSynchro())
TraceEjb.synchro.log(BasicLevel.DEBUG, ident + "mapICtx IH: NOTIFIED");
} catch (InterruptedException e) {
if (TraceEjb.isDebugSynchro())
TraceEjb.synchro.log(BasicLevel.DEBUG, ident + "mapICtx IH: INTERRUPTED");
} catch (Exception e) {
throw new EJBException("JEntitySwitch synchronization pb", e);
} finally {
waiters--;
}
}
} else {
int waitcount = 0;
Transaction lastrunning = null;
// Must wait in case of TX or if instance has been modified outside
// transaction.
// Don't wait transactions if 1 instance per transaction.
while (inDirtyList || (runningtx != null && !tx.equals(runningtx))) {
if (inDirtyList) {
// instance is dirty : must write it before working on it again.
// this is mandatory to be able to retrieve a good state in case
// of rollback.
if (TraceEjb.isDebugSynchro())
TraceEjb.synchro.log(BasicLevel.DEBUG, ident + "mapICtx IT: WAIT end IH");
// Cannot passivate here, because must be done outside tx
// context
bf.synchronizeEntities();
} else {
if (TraceEjb.isDebugSynchro())
TraceEjb.synchro.log(BasicLevel.DEBUG, ident + "mapICtx IT: WAIT end IT");
// deadlock detection
blockedtx.add(tx);
if (waitcount > 0 && runningtx.equals(lastrunning) && bf.isDeadLocked(runningtx)) {
blockedtx.remove(tx);
try {
tx.setRollbackOnly();
} catch (SystemException e) {
TraceEjb.logger.log(BasicLevel.ERROR, ident
+ "getICtx IT: unexpected exception setting rollbackonly");
}
TraceEjb.logger.log(BasicLevel.WARN, ident + "getICtx IT: transaction rolled back");
throw new TransactionRolledbackLocalException("possible deadlock");
}
lastrunning = runningtx;
}
waitcount++;
waiters++;
try {
wait(deadlockTimeout);
if (TraceEjb.isDebugSynchro())
TraceEjb.synchro.log(BasicLevel.DEBUG, ident + "mapICtx IT: NOTIFIED");
} catch (InterruptedException e) {
if (TraceEjb.isDebugSynchro())
TraceEjb.synchro.log(BasicLevel.DEBUG, ident + "mapICtx IT: INTERRUPTED");
} catch (Exception e) {
throw new EJBException("JEntitySwitch synchronization pb", e);
} finally {
waiters--;
if (lastrunning != null) {
blockedtx.remove(tx);
}
}
// If transaction has been rolledback or set rollback only, give
// up.
int status = Status.STATUS_ROLLEDBACK;
try {
status = tx.getStatus();
} catch (SystemException e) {
TraceEjb.logger.log(BasicLevel.ERROR, ident
+ "getICtx IT: unexpected exception getting transaction status");
}
switch (status) {
case Status.STATUS_MARKED_ROLLBACK:
case Status.STATUS_ROLLEDBACK:
case Status.STATUS_ROLLING_BACK:
TraceEjb.logger.log(BasicLevel.WARN, ident + "getICtx IT: transaction rolled back");
throw new TransactionRolledbackLocalException("rollback occured while waiting");
}
}
}
}
/**
* try to passivate instances
* @param store If we want to store the bean state first.
* @param passivate If we want to pasivate the bean also
* @return int result of operation:
* ALL_DONE = just remove from dirty list.
* STORED = remove from dirty list and call endIH
* NOT_DONE = keep it in the list.
*/
public synchronized int passivateIH(boolean store, boolean passivate) {
JEntityContext jec = getContext4Tx(null);
// Check if we should store the bean state first.
// This is CS policy specific.
boolean toStore = store;
if (! store && inDirtyList) {
toStore = System.currentTimeMillis() - ptimestamp > passivationTimeout;
}
// If instance busy, do nothing now.
if (runningtx != null || countIT > 0) {
if (TraceEjb.isDebugSynchro()) {
TraceEjb.synchro.log(BasicLevel.DEBUG, ident + " used in TX");
}
return NOT_DONE;
}
if (countIH > 0) {
// will be stored later, when released by last thread (countIH = 0)
if (TraceEjb.isDebugSynchro()) {
TraceEjb.synchro.log(BasicLevel.DEBUG, ident + " used off TX");
}
mustStore = toStore;
return toStore ? ALL_DONE : STORED;
}
int ret = toStore ? STORED : NOT_DONE;
if (jec != null) {
// If marked removed, don't need to store.
if (jec.isMarkedRemoved()) {
discardContext(null, true, true);
return STORED;
}
// Store the bean state
if (toStore) {
if (TraceEjb.isDebugContext()) {
TraceEjb.context.log(BasicLevel.DEBUG, ident + " store object");
}
try {
jec.storeIfModified();
} catch (Exception e) {
TraceEjb.logger.log(BasicLevel.ERROR, ident, " error while storing bean state:", e);
}
mustStore = false;
// restart timer to compute next time to store.
ptimestamp = System.currentTimeMillis();
// don't set dirty false here, will be done in endIH.
// this prevent duplicates in dirty list.
}
// passivation has not been required: just return now.
if (! passivate) {
return ret;
}
// Don't passivate too recent instances to avoid problems at create
if (System.currentTimeMillis() < estimestamp) {
TraceEjb.context.log(BasicLevel.DEBUG, "too recent ");
return ret;
}
// Passivate the instance.
if (TraceEjb.isDebugContext()) {
TraceEjb.context.log(BasicLevel.DEBUG, "passivate: " + jec);
}
if (!jec.passivate()) {
return ret;
}
if (jec.getMyTx() != null) {
TraceEjb.context.log(BasicLevel.WARN, "Will forget Tx???");
}
// Will be pooled only if min-pool-size not reached in free list.
bf.releaseJContext(jec, 1);
removeContext4Tx(null);
// notify waiters for new instances
if (waiters > 0) {
notifyAll();
}
}
// Instance is passivated, look if we can destroy the objects.
if (inactivityTimeout > 0 && System.currentTimeMillis() - estimestamp > inactivityTimeout) {
detachPk();
estimestamp = System.currentTimeMillis();
ret = ALL_DONE;
}
return ret;
}
/**
* Instance is ready to use for new transaction.
*/
public synchronized void endIH() {
inDirtyList = false;
if (getContext4Tx(null) == null) {
if (TraceEjb.isDebugSynchro()) TraceEjb.synchro.log(BasicLevel.DEBUG, ident + " discarded!");
return;
}
if (TraceEjb.isDebugSynchro()) {
if (countIH == 0) {
TraceEjb.synchro.log(BasicLevel.DEBUG, ident + " ready again");
} else {
TraceEjb.synchro.log(BasicLevel.DEBUG, ident + " busy!");
}
}
if (waiters > 0) {
if (TraceEjb.isDebugSynchro()) {
TraceEjb.synchro.log(BasicLevel.DEBUG, ident + " notify");
}
notifyAll();
}
}
}
|