/**
* Copyright (C) 2001-2005 France Telecom R&D
*
* 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 of the License, or (at your option) 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
*/
package org.objectweb.speedo.workingset.lib;
import org.objectweb.fractal.api.Component;
import org.objectweb.fractal.api.control.LifeCycleController;
import org.objectweb.jorm.api.PMapper;
import org.objectweb.medor.eval.prefetch.api.PrefetchCache;
import org.objectweb.perseus.persistence.api.ConnectionHolder;
import org.objectweb.perseus.persistence.api.PersistenceException;
import org.objectweb.perseus.persistence.api.State;
import org.objectweb.perseus.persistence.api.TransactionalPersistenceManager;
import org.objectweb.perseus.persistence.api.TransactionalWorkingSet;
import org.objectweb.perseus.persistence.api.VirtualState;
import org.objectweb.perseus.persistence.api.WorkingSet;
import org.objectweb.perseus.persistence.api.WorkingSetLifeCycle;
import org.objectweb.perseus.persistence.lib.BasicWorkingSet;
import org.objectweb.speedo.api.ExceptionHelper;
import org.objectweb.speedo.api.SpeedoProperties;
import org.objectweb.speedo.api.SpeedoRuntimeException;
import org.objectweb.speedo.api.TransactionListener;
import org.objectweb.speedo.mim.api.StateItf;
import org.objectweb.speedo.pm.api.POManagerItf;
import org.objectweb.speedo.workingset.api.TransactionItf;
import org.objectweb.util.monolog.api.BasicLevel;
import java.util.ArrayList;
import java.util.Iterator;
import javax.transaction.Status;
import javax.transaction.Synchronization;
/**
*
*
* @author S.Chassande-Barrioz
*/
public abstract class AbstractTransaction
extends BasicWorkingSet
implements TransactionItf, LifeCycleController {
public final static String PO_MANAGER_BINDING = "po-manager";
public final static String MAPPER_BINDING = "mapper";
public final static String TRANSACTIONAL_PERSISTENCE_MANAGER_BINDING
= "transactional-persistence-manager";
public final static String COMPONENT_BINDING = "component";
public static TransactionListener txListener = null;
/**
* is the mapper permitting to reach the prefetch cache and to invalidate
* prefetched buffer at working set closing time.
*/
protected PMapper mapper = null;
/**
* Is used to delegates working set/transaction demercation
*/
protected TransactionalPersistenceManager tpm = null;
protected boolean nontransactionalRead;
protected boolean nontransactionalWrite;
/**
* Indicates if the transaction is optimistic.
*/
protected boolean optimistic;
/**
* The JDO user synchronization registered (can be null if none has been
* registered).
*/
protected Synchronization synchronization = null;
/**
* indicates if the transaction is managed by a J2EE environnement.
*/
protected boolean managedEnv = false;
/**
* Indicates if the jdo transaction must be rolledback.
*/
protected boolean rollbackOnly = false;
/**
* the reference to this component in Speedo
*/
protected TransactionItf thisT;
/**
* Is the linked po manager.
*/
protected POManagerItf pm = null;
/**
*
*/
public AbstractTransaction() {
super();
}
/**
* Attaches an entry to the transaction.
* Plus version update.
* @param state the state which must be attached to the transaction
* @param mode the action that stared the binding: either read or write intention
*/
public State bind(State state, Object oid, byte mode) {
State old = super.bind(state, oid, mode);
if(! (state instanceof VirtualState)){
if (mode == BasicWorkingSet.WRITE_INTENTION) {
StateItf sa = (StateItf) state;
sa.speedoChangeVersion();
}
}
return old;
}
/**
* Invalidates the prefetch buffer associated to this working set.
*
* @throws PersistenceException
*/
public void beforeWSPrepare() throws PersistenceException {
logger.log(BasicLevel.DEBUG, "Starting beforeWSPrepare");
Iterator it = oid2state.values().iterator();
ArrayList exceptions = null;
while(it.hasNext()) {
org.objectweb.perseus.persistence.api.State state = (org.objectweb.perseus.persistence.api.State) it.next();
if (state == VirtualState.instance) {
continue;
}
try {
((StateItf) state).prepareWrite();
} catch (Exception e) {
if (exceptions == null) {
exceptions = new ArrayList();
}
exceptions.add(e);
int level = e instanceof RuntimeException ? BasicLevel.DEBUG : BasicLevel.ERROR ;
if (logger.isLoggable(level)) {
logger.log(level,
"Error on StateItf preparation for flushing: "
+ "\n\tstate.ce.identifier=" + state.getCacheEntry().getCeIdentifier()
+ "\n\tstate=" + state
+ "\n\texception: ", e);
}
}
}
// close the prefetch buffers associated to the context
PrefetchCache pc = mapper.getPrefetchCache();
if (pc != null) {
pc.invalidatePrefetchBuffer(thisT);
}
logger.log(BasicLevel.DEBUG, "Ending beforeWSPrepare");
if (exceptions != null) {
throw new PersistenceException(new SpeedoRuntimeException(
"Impossible to prepare instances before flushing",
(Exception[]) exceptions.toArray(
new Exception[exceptions.size()])));
}
}
/**
* Signal to the persistent instances reached in the working set that the
* current working set is closed. Some actions on persistent instances at
* this time can be done, such as reference unswizlling
*/
public void onWSEnd() {
logger.log(BasicLevel.DEBUG, "Starting onWSEnd");
ArrayList exceptions = null;
if (!oid2state.isEmpty()) {
Iterator it = oid2state.values().iterator();
while(it.hasNext()) {
org.objectweb.perseus.persistence.api.State state = (org.objectweb.perseus.persistence.api.State) it.next();
if (state == VirtualState.instance) {
continue;
}
try {
((StateItf) state).workingSetClosed();
} catch (Exception e) {
if (exceptions == null) {
exceptions = new ArrayList();
}
exceptions.add(e);
if (!(e instanceof RuntimeException)) {
logger.log(BasicLevel.ERROR,
"Error on workingSetClosed for the StateItf: "
+ "\n\tstate.ce.identifier=" + state.getCacheEntry().getCeIdentifier()
+ "\n\tstate=" + state
+ "\n\texception: ", e);
}
}
}
}
if (txListener != null) {
txListener.transactionPreValidate(this, oid2state.size());
}
logger.log(BasicLevel.DEBUG, "Ending onWSEnd");
if (exceptions != null) {
throw new SpeedoRuntimeException(
"Error when signal the close of working set on states:",
(Exception[]) exceptions.toArray(new Exception[exceptions.size()]));
}
}
// IMPLEMENTATION OF THE LifeCycleController INTERFACE //
//-----------------------------------------------------//
public String getFcState() {
return null;
}
public void startFc() {
managedEnv = pm.getPOManagerFactory().getProperties()
.getProperty(SpeedoProperties.MANAGED, "")
.equals("true");
}
public void stopFc() {
}
// IMPLEMENTATION OF THE UserBindingController INTERFACE //
//-------------------------------------------------------//
public String[] listFc() {
String[] names = super.listFc();
String[] itfs = new String[names.length + 3];
itfs[0] = PO_MANAGER_BINDING;
itfs[1] = TRANSACTIONAL_PERSISTENCE_MANAGER_BINDING;
itfs[2] = MAPPER_BINDING;
System.arraycopy(names, 0, itfs, 3, names.length);
return itfs;
}
public Object lookupFc(String c) {
if (PO_MANAGER_BINDING.equals(c))
return pm;
else if (TRANSACTIONAL_PERSISTENCE_MANAGER_BINDING.equals(c))
return tpm;
else if (MAPPER_BINDING.equals(c))
return mapper;
else
return super.lookupFc(c);
}
public void bindFc(String c, Object s) {
if (PO_MANAGER_BINDING.equals(c))
pm = (POManagerItf) s;
else if (TRANSACTIONAL_PERSISTENCE_MANAGER_BINDING.equals(c))
tpm = (TransactionalPersistenceManager) s;
else if (MAPPER_BINDING.equals(c))
mapper = (PMapper) s;
else if (COMPONENT_BINDING.equals(c)) {
try {
thisT = (TransactionItf) ((Component) s).getFcInterface("transaction");
} catch (Exception e) {
throw new SpeedoRuntimeException(
"Impossible to get self transaction",
ExceptionHelper.getNested(e));
}
} else
super.bindFc(c, s);
}
public void unbindFc(String c) {
if (PO_MANAGER_BINDING.equals(c))
pm = null;
else if (TRANSACTIONAL_PERSISTENCE_MANAGER_BINDING.equals(c))
tpm = null;
else if (MAPPER_BINDING.equals(c))
mapper = null;
else
super.unbindFc(c);
}
public void setStatus(byte status) throws PersistenceException {
try {
switch(status) {
case TransactionalWorkingSet.CTX_PREPARED:
beforeWSPrepare();
break;
case TransactionalWorkingSet.CTX_COMMITTED:
case TransactionalWorkingSet.CTX_ABORTED:
onWSEnd();
break;
case TransactionalWorkingSet.CTX_CLOSED:
if (oid2state.isEmpty()) {
// close the prefetch buffers associated to the context
PrefetchCache pc = mapper.getPrefetchCache();
if (pc != null) {
pc.invalidatePrefetchBuffer(thisT);
}
}
onWSEnd();
break;
}
} finally {
super.setStatus(status);
}
}
public boolean isActive() {
switch(status){
case TransactionalWorkingSet.CTX_ACTIVE_TRANSACTIONAL:
case TransactionalWorkingSet.CTX_PREPARED:
case TransactionalWorkingSet.CTX_PREPARED_OK:
case TransactionalWorkingSet.CTX_PREPARED_FAIL:
return true;
case TransactionalWorkingSet.CTX_ACTIVE:
case TransactionalWorkingSet.CTX_COMMITTED:
case TransactionalWorkingSet.CTX_ABORTED:
case TransactionalWorkingSet.CTX_CLOSED:
default:
return false;
}
}
/**
* @see org.objectweb.speedo.workingset.api.TransactionItf#begin()
*/
public void begin() {
logger.log(BasicLevel.INFO, "Begin the transaction");
rollbackOnly = false; //initialize the flag
try {
tpm.begin(thisT);
} catch (PersistenceException e) {
Exception ie = ExceptionHelper.getNested(e);
logger.log(BasicLevel.ERROR,
"Error during the begin of the transaction:", ie);
throw new SpeedoRuntimeException("", ie);
}
if (txListener != null) {
txListener.transactionBegun(this);
}
}
/**
* @see org.objectweb.speedo.workingset.api.TransactionItf#commit()
*/
public void commit() {
int size = oid2state.size();
logger.log(BasicLevel.INFO, "Commit the transaction, working set size: " + size);
if (synchronization != null) {
synchronization.beforeCompletion();
}
//register working set size for statistics
boolean validated = rollbackOnly;
try {
if (rollbackOnly) {
tpm.rollback(thisT);
} else {
tpm.commit(thisT);
validated = true;
}
} catch (PersistenceException e) {
Exception ie = ExceptionHelper.getNested(e);
if (ie instanceof RuntimeException) {
throw (RuntimeException) ie;
} else {
ie = new SpeedoRuntimeException(
"JDOTransactionItf rolledback due to an exception at commit time: ", ie);
logger.log(BasicLevel.INFO, ie.getMessage(), ie);
throw (SpeedoRuntimeException) ie;
}
} finally {
if (synchronization != null)
synchronization.afterCompletion((validated
? Status.STATUS_COMMITTED : Status.STATUS_ROLLEDBACK));
if (txListener != null) {
if (validated) {
txListener.transactionCommitted(this, size);
} else {
txListener.transactionAborted(this, size);
}
}
}
}
/**
* @see org.objectweb.speedo.workingset.api.TransactionItf#rollback()
*/
public void rollback() {
logger.log(BasicLevel.INFO, "Roll back a transaction: ");
int size = oid2state.size();
try {
tpm.rollback(thisT);
} catch (PersistenceException e) {
Exception ie = ExceptionHelper.getNested(e);
logger.log(BasicLevel.ERROR,
"Error during the rollback of the transaction:", ie);
throw new SpeedoRuntimeException("", ie);
} finally {
if (txListener != null) {
txListener.transactionAborted(this, size);
}
if (synchronization != null) {
synchronization.afterCompletion(Status.STATUS_ROLLEDBACK);
}
}
}
/**
* It activates the working set. This is used to delimit the begining of
* the working set.
*/
public void activate() throws PersistenceException {
tpm.createWS(thisT);
try {
status = WorkingSetLifeCycle.getNextStatus(
status, WorkingSetLifeCycle.ACTIVE_ACTION);
} catch (PersistenceException e) {
logger.log(BasicLevel.WARN, "Bad initial state of the working set:", e);
status = WorkingSet.CTX_ACTIVE;
}
}
public boolean isManagedEnv() {
return managedEnv;
}
public void setConnectionHolder(ConnectionHolder ch) {
connectionHolder = ch;
connectionHolder.bindWorkingSet(thisT);
}
public boolean getRollbackOnly() {
return rollbackOnly;
}
public void setRollbackOnly() {
rollbackOnly = true;
}
public POManagerItf getPOManager() {
return pm;
}
}