/*
* Copyright (C) 2011 eXo Platform SAS.
*
* This 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 (at your option) any later version.
*
* This software 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 software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.exoplatform.services.transaction.impl;
import org.exoplatform.commons.utils.SecurityHelper;
import org.exoplatform.container.xml.InitParams;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.exoplatform.services.transaction.TransactionService;
import java.security.PrivilegedExceptionAction;
import javax.transaction.HeuristicMixedException;
import javax.transaction.HeuristicRollbackException;
import javax.transaction.InvalidTransactionException;
import javax.transaction.NotSupportedException;
import javax.transaction.RollbackException;
import javax.transaction.Status;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import javax.transaction.UserTransaction;
import javax.transaction.xa.XAResource;
/**
* This abstract class implements the main logic of all the methods expected for a
* {@link TransactionService}. If you intend to use a {@link TransactionManager} in
* standalone mode (not manager by your Application Server), you can set the
* transaction timeout thanks to the <code>value-param</code> called <code>timeout</code>
* the value of this parameter is expressed in seconds.
*
* @author <a href="mailto:nicolas.filotto@exoplatform.com">Nicolas Filotto</a>
* @version $Id$
*
*/
public abstract class AbstractTransactionService implements TransactionService
{
/**
* The logger
*/
private static final Log LOG = ExoLogger.getLogger("exo.kernel.component.common.AbstractTransactionService");
/**
* The default value of a transaction timeout in seconds
*/
private static final int DEFAULT_TIME_OUT = 60;
/**
* The default timeout
*/
protected final int defaultTimeout;
/**
* Indicates if the timeout has to be enforced
*/
protected final boolean forceTimeout;
/**
* The current Transaction Manager
*/
private volatile TransactionManager tm;
/**
* The current User Transaction
*/
private volatile UserTransaction ut;
public AbstractTransactionService()
{
this(null);
}
public AbstractTransactionService(InitParams params)
{
if (params != null && params.getValueParam("timeout") != null)
{
this.defaultTimeout = Integer.parseInt(params.getValueParam("timeout").getValue());
this.forceTimeout = true;
}
else
{
this.defaultTimeout = DEFAULT_TIME_OUT;
this.forceTimeout = false;
}
}
/**
* {@inheritDoc}
*/
public boolean delistResource(final XAResource xares) throws RollbackException, SystemException
{
TransactionManager tm = getTransactionManager();
final Transaction tx = tm.getTransaction();
if (tx != null)
{
int flag = XAResource.TMSUCCESS;
switch (tx.getStatus())
{
case Status.STATUS_MARKED_ROLLBACK:
case Status.STATUS_ROLLEDBACK:
case Status.STATUS_ROLLING_BACK: flag = XAResource.TMFAIL;
}
return tx.delistResource(xares, flag);
}
else
{
throw new IllegalStateException("Could not delist the XA Resource since there is no active session");
}
}
/**
* {@inheritDoc}
*/
public boolean enlistResource(final XAResource xares) throws RollbackException, SystemException
{
TransactionManager tm = getTransactionManager();
final Transaction tx = tm.getTransaction();
if (tx != null)
{
return tx.enlistResource(xares);
}
else
{
throw new IllegalStateException("Could not enlist the XA Resource since there is no active session");
}
}
/**
* {@inheritDoc}
*/
public int getDefaultTimeout()
{
return defaultTimeout;
}
/**
* {@inheritDoc}
*/
public final TransactionManager getTransactionManager()
{
if (tm == null)
{
synchronized (this)
{
if (tm == null)
{
TransactionManager tm;
try
{
tm = SecurityHelper.doPrivilegedExceptionAction(new PrivilegedExceptionAction<TransactionManager>()
{
public TransactionManager run() throws Exception
{
return findTransactionManager();
}
});
}
catch (Exception e)
{
throw new RuntimeException("Transaction manager not found", e);
}
if (forceTimeout)
{
// Only set the timeout when a timeout has been given into the
// configuration otherwise we assume that the value will be
// set at the AS level
tm = new TransactionManagerTxTimeoutAware(tm, defaultTimeout);
}
this.tm = tm;
}
}
}
return tm;
}
/**
* Indicates whether or not the {@link TransactionManager} has been initialized
*/
protected boolean isTMInitialized()
{
return tm != null;
}
/**
* This method will try to find the current {@link TransactionManager}
* @return the current {@link TransactionManager}
* @throws Exception if an error occurs while looking for the {@link TransactionManager}
*/
protected abstract TransactionManager findTransactionManager() throws Exception;
/**
* {@inheritDoc}
*/
public final UserTransaction getUserTransaction()
{
if (ut == null)
{
synchronized (this)
{
if (ut == null)
{
UserTransaction ut;
try
{
ut = SecurityHelper.doPrivilegedExceptionAction(new PrivilegedExceptionAction<UserTransaction>()
{
public UserTransaction run() throws Exception
{
return findUserTransaction();
}
});
}
catch (Exception e)
{
throw new RuntimeException("UserTransaction not found", e);
}
this.ut = ut;
}
}
}
return ut;
}
/**
* This method will try to find the current {@link UserTransaction}, by default it will
* simply wraps a {@link TransactionManager}
* @return the current {@link UserTransaction}
* @throws Exception if an error occurs while looking for the {@link UserTransaction}
*/
protected UserTransaction findUserTransaction() throws Exception
{
return new UserTransactionWrapper(getTransactionManager());
}
/**
* {@inheritDoc}
*/
public void setTransactionTimeout(int seconds) throws SystemException
{
TransactionManager tm = getTransactionManager();
tm.setTransactionTimeout(seconds);
}
/**
* This class is used to enforce the {@link Transaction} timeout when a new transaction is
* created through the nested {@link TransactionManager}
*
* Created by The eXo Platform SAS
* Author : Nicolas Filotto
* nicolas.filotto@exoplatform.com
* 1 f�vr. 2010
*/
private static class TransactionManagerTxTimeoutAware implements TransactionManager
{
/**
* The nested {@link TransactionManager}
*/
private final TransactionManager tm;
/**
* The default timeout of the {@link Transaction}
*/
private final int defaultTimeout;
/**
* This is used to know if a timeout has already been set for the next transaction
*/
private final ThreadLocal<Boolean> timeoutHasBeenSet = new ThreadLocal<Boolean>();
public TransactionManagerTxTimeoutAware(TransactionManager tm, int defaultTimeout)
{
this.tm = tm;
this.defaultTimeout = defaultTimeout;
}
/**
* {@inheritDoc}
*/
public void begin() throws NotSupportedException, SystemException
{
if (timeoutHasBeenSet.get() != null)
{
// clean the ThreadLocal
timeoutHasBeenSet.set(null);
}
else
{
try
{
// Set the default transaction timeout
tm.setTransactionTimeout(defaultTimeout);
}
catch (Exception e)
{
LOG.warn("Cannot set the transaction timeout", e);
}
}
// Start the transaction
tm.begin();
}
/**
* {@inheritDoc}
*/
public void commit() throws RollbackException, HeuristicMixedException, HeuristicRollbackException,
SecurityException, IllegalStateException, SystemException
{
tm.commit();
}
/**
* {@inheritDoc}
*/
public int getStatus() throws SystemException
{
return tm.getStatus();
}
/**
* {@inheritDoc}
*/
public Transaction getTransaction() throws SystemException
{
return tm.getTransaction();
}
/**
* {@inheritDoc}
*/
public void resume(final Transaction tx) throws InvalidTransactionException, IllegalStateException,
SystemException
{
tm.resume(tx);
}
/**
* {@inheritDoc}
*/
public void rollback() throws IllegalStateException, SecurityException, SystemException
{
tm.rollback();
}
/**
* {@inheritDoc}
*/
public void setRollbackOnly() throws IllegalStateException, SystemException
{
tm.setRollbackOnly();
}
/**
* {@inheritDoc}
*/
public void setTransactionTimeout(int timeout) throws SystemException
{
tm.setTransactionTimeout(timeout);
timeoutHasBeenSet.set(true);
}
/**
* {@inheritDoc}
*/
public Transaction suspend() throws SystemException
{
return tm.suspend();
}
}
/**
* This class is used to propose a default implementation of a {@link UserTransaction}
* from the {@link TransactionManager}
* @author <a href="mailto:nfilotto@exoplatform.com">Nicolas Filotto</a>
* @version $Id$
*
*/
private static class UserTransactionWrapper implements UserTransaction
{
/**
* The {@link TransactionManager} that we will use to simulate a {@link UserTransaction}
*/
private final TransactionManager tm;
/**
* Default Constructor
* @param tm
*/
public UserTransactionWrapper(TransactionManager tm)
{
this.tm = tm;
}
/**
* @see javax.transaction.UserTransaction#begin()
*/
public void begin() throws NotSupportedException, SystemException
{
tm.begin();
}
/**
* @see javax.transaction.UserTransaction#commit()
*/
public void commit() throws RollbackException, HeuristicMixedException, HeuristicRollbackException,
SecurityException, IllegalStateException, SystemException
{
tm.commit();
}
/**
* @see javax.transaction.UserTransaction#rollback()
*/
public void rollback() throws IllegalStateException, SecurityException, SystemException
{
tm.rollback();
}
/**
* @see javax.transaction.UserTransaction#setRollbackOnly()
*/
public void setRollbackOnly() throws IllegalStateException, SystemException
{
tm.setRollbackOnly();
}
/**
* @see javax.transaction.UserTransaction#getStatus()
*/
public int getStatus() throws SystemException
{
return tm.getStatus();
}
/**
* @see javax.transaction.UserTransaction#setTransactionTimeout(int)
*/
public void setTransactionTimeout(int timeout) throws SystemException
{
tm.setTransactionTimeout(timeout);
}
}
}