org.apache.deltaspike.jpa.impl.transaction.BeanManagedUserTransactionStrategy Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.deltaspike.jpa.impl.transaction;
import org.apache.deltaspike.core.api.provider.BeanProvider;
import org.apache.deltaspike.core.api.provider.DependentProvider;
import org.apache.deltaspike.core.impl.util.JndiUtils;
import org.apache.deltaspike.core.util.ExceptionUtils;
import org.apache.deltaspike.jpa.api.config.base.JpaBaseConfig;
import org.apache.deltaspike.jpa.api.transaction.TransactionConfig;
import org.apache.deltaspike.jpa.impl.transaction.context.EntityManagerEntry;
import javax.enterprise.context.Dependent;
import javax.enterprise.inject.Alternative;
import javax.enterprise.inject.spi.BeanManager;
import javax.inject.Inject;
import javax.interceptor.InvocationContext;
import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import javax.transaction.Status;
import javax.transaction.SystemException;
import javax.transaction.TransactionSynchronizationRegistry;
import javax.transaction.UserTransaction;
import java.lang.annotation.Annotation;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* {@link org.apache.deltaspike.jpa.spi.transaction.TransactionStrategy} for using JTA (bean-managed-)transactions
* (including XA transactions with a XA DataSource).
* The basic features are identical to the {@link ResourceLocalTransactionStrategy} (for
* persistent-unit-transaction-type 'RESOURCE_LOCAL' only).
*/
@Dependent
@Alternative
@SuppressWarnings("UnusedDeclaration")
//TODO move to a separated ds-jta module and use @Specializes -> no additional config is needed
public class BeanManagedUserTransactionStrategy extends ResourceLocalTransactionStrategy
{
protected static final String TRANSACTION_SYNC_REGISTRY_JNDI_NAME = "java:comp/TransactionSynchronizationRegistry";
private static final long serialVersionUID = -2432802805095533499L;
private static final Logger LOGGER = Logger.getLogger(BeanManagedUserTransactionStrategy.class.getName());
@Inject
private BeanManager beanManager;
private transient TransactionConfig transactionConfig;
@Override
protected EntityManagerEntry createEntityManagerEntry(
EntityManager entityManager, Class extends Annotation> qualifier)
{
applyTransactionTimeout(); //needs to be done before UserTransaction#begin - TODO move this call
return super.createEntityManagerEntry(entityManager, qualifier);
}
protected void applyTransactionTimeout()
{
Integer transactionTimeout = getDefaultTransactionTimeoutInSeconds();
if (transactionTimeout == null)
{
//the default configured for the container will be used
return;
}
try
{
UserTransaction userTransaction = resolveUserTransaction();
if (userTransaction != null && userTransaction.getStatus() != Status.STATUS_ACTIVE)
{
userTransaction.setTransactionTimeout(transactionTimeout);
}
}
catch (SystemException e)
{
LOGGER.log(Level.WARNING, "UserTransaction#setTransactionTimeout failed", e);
}
}
protected Integer getDefaultTransactionTimeoutInSeconds()
{
if (this.transactionConfig == null)
{
lazyInit();
}
return transactionConfig.getUserTransactionTimeoutInSeconds();
}
protected synchronized void lazyInit()
{
if (this.transactionConfig != null)
{
return;
}
this.transactionConfig = BeanProvider.getContextualReference(TransactionConfig.class, true);
if (this.transactionConfig == null)
{
this.transactionConfig = createDefaultTransactionConfig();
}
}
protected TransactionConfig createDefaultTransactionConfig()
{
return new TransactionConfig()
{
private static final long serialVersionUID = -3915439087580270117L;
@Override
public Integer getUserTransactionTimeoutInSeconds()
{
return JpaBaseConfig.UserTransaction.TIMEOUT_IN_SECONDS;
}
};
}
@Override
protected EntityTransaction getTransaction(EntityManagerEntry entityManagerEntry)
{
return new UserTransactionAdapter();
}
/**
* Needed because the {@link EntityManager} might get created outside of the {@link UserTransaction}
* (e.g. depending on the implementation of the producer).
* Can't be in {@link BeanManagedUserTransactionStrategy.UserTransactionAdapter#begin()}
* because {@link ResourceLocalTransactionStrategy} needs to do
*
* if (!transaction.isActive())
* {
* transaction.begin();
* }
*
* for the {@link EntityTransaction} of every {@link EntityManager}
* and {@link BeanManagedUserTransactionStrategy.UserTransactionAdapter#isActive()}
* can only use the status information of the {@link UserTransaction} and therefore
* {@link BeanManagedUserTransactionStrategy.UserTransactionAdapter#begin()}
* will only executed once, but {@link javax.persistence.EntityManager#joinTransaction()}
* needs to be called for every {@link EntityManager}.
* @param invocationContext current invocation-context
* @param entityManagerEntry entry of the current entity-manager
* @param transaction current JTA transaction wrapped in an EntityTransaction adapter
*/
@Override
protected void beforeProceed(InvocationContext invocationContext,
EntityManagerEntry entityManagerEntry,
EntityTransaction transaction)
{
entityManagerEntry.getEntityManager().joinTransaction();
}
protected UserTransaction resolveUserTransaction()
{
//manual lookup needed because injecting UserTransactionResolver can fail (see the comment there)
try
{
DependentProvider provider =
BeanProvider.getDependent(this.beanManager, UserTransactionResolver.class);
UserTransaction userTransaction = provider.get().resolveUserTransaction();
provider.destroy();
return userTransaction;
}
catch (Exception e)
{
return null;
}
}
protected TransactionSynchronizationRegistry resolveTransactionRegistry()
{
return JndiUtils.lookup(TRANSACTION_SYNC_REGISTRY_JNDI_NAME, TransactionSynchronizationRegistry.class);
}
private class UserTransactionAdapter implements EntityTransaction
{
private final UserTransaction userTransaction;
//needed for calls through an EJB with CMT
private final TransactionSynchronizationRegistry transactionSynchronizationRegistry;
public UserTransactionAdapter()
{
this.userTransaction = resolveUserTransaction();
if (this.userTransaction == null)
{
this.transactionSynchronizationRegistry = resolveTransactionRegistry();
if (this.transactionSynchronizationRegistry.getTransactionStatus() != Status.STATUS_ACTIVE)
{
throw new IllegalStateException(
"The CMT is not active. Please check the config of the Data-Source.");
}
}
else
{
this.transactionSynchronizationRegistry = null;
}
}
/**
* Only delegate to the {@link UserTransaction} if the state of the
* {@link UserTransaction} is {@link Status#STATUS_NO_TRANSACTION}
* (= the status before and after a started transaction).
*/
@Override
public void begin()
{
if (this.userTransaction == null)
{
throw new UnsupportedOperationException("A CMT is active. This operation is only supported with BMT.");
}
try
{
//2nd check (already done by #isActive triggered by ResourceLocalTransactionStrategy directly before)
//currently to filter STATUS_UNKNOWN - see to-do -> TODO re-visit it
if (this.userTransaction.getStatus() == Status.STATUS_NO_TRANSACTION)
{
this.userTransaction.begin();
}
}
catch (Exception e)
{
throw ExceptionUtils.throwAsRuntimeException(e);
}
}
/**
* Only delegate to the {@link UserTransaction} if the state of the
* {@link UserTransaction} is one of
*
* - {@link Status#STATUS_ACTIVE}
* - {@link Status#STATUS_PREPARING}
* - {@link Status#STATUS_PREPARED}
*
*/
@Override
public void commit()
{
if (this.userTransaction == null)
{
throw new UnsupportedOperationException("A CMT is active. This operation is only supported with BMT.");
}
try
{
if (isTransactionReadyToCommit())
{
this.userTransaction.commit();
}
}
catch (Exception e)
{
throw ExceptionUtils.throwAsRuntimeException(e);
}
}
/**
* Only delegate to the {@link UserTransaction} if the state of the
* {@link UserTransaction} is one of
*
* - {@link Status#STATUS_ACTIVE}
* - {@link Status#STATUS_PREPARING}
* - {@link Status#STATUS_PREPARED}
* - {@link Status#STATUS_MARKED_ROLLBACK}
* - {@link Status#STATUS_COMMITTING}
*
*/
@Override
public void rollback()
{
if (this.userTransaction == null)
{
throw new UnsupportedOperationException("A CMT is active. This operation is only supported with BMT.");
}
try
{
if (isTransactionAllowedToRollback())
{
this.userTransaction.rollback();
}
}
catch (SystemException e)
{
throw ExceptionUtils.throwAsRuntimeException(e);
}
}
@Override
public void setRollbackOnly()
{
try
{
if (this.userTransaction != null)
{
this.userTransaction.setRollbackOnly();
}
else
{
this.transactionSynchronizationRegistry.setRollbackOnly();
}
}
catch (SystemException e)
{
throw ExceptionUtils.throwAsRuntimeException(e);
}
}
@Override
public boolean getRollbackOnly()
{
try
{
return getTransactionStatus() == Status.STATUS_MARKED_ROLLBACK;
}
catch (SystemException e)
{
throw ExceptionUtils.throwAsRuntimeException(e);
}
}
/**
* @return true if the transaction has been started and not ended
*/
@Override
public boolean isActive()
{
//we can't use the status of the overall
try
{
return this.getTransactionStatus() != Status.STATUS_NO_TRANSACTION &&
this.getTransactionStatus() != Status.STATUS_UNKNOWN; //TODO re-visit it
}
catch (SystemException e)
{
throw ExceptionUtils.throwAsRuntimeException(e);
}
}
protected boolean isTransactionAllowedToRollback() throws SystemException
{
//if the following gets changed, it needs to be tested with different constellations
//(normal exception, timeout,...) as well as servers
return this.getTransactionStatus() != Status.STATUS_COMMITTED &&
this.getTransactionStatus() != Status.STATUS_NO_TRANSACTION &&
this.getTransactionStatus() != Status.STATUS_UNKNOWN;
}
protected boolean isTransactionReadyToCommit() throws SystemException
{
return getTransactionStatus() == Status.STATUS_ACTIVE ||
getTransactionStatus() == Status.STATUS_PREPARING ||
getTransactionStatus() == Status.STATUS_PREPARED;
}
protected int getTransactionStatus() throws SystemException
{
if (this.userTransaction != null)
{
return this.userTransaction.getStatus();
}
else
{
return this.transactionSynchronizationRegistry.getTransactionStatus();
}
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy