All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.deltaspike.jpa.impl.transaction.ResourceLocalTransactionStrategy Maven / Gradle / Ivy

There is a newer version: 2.0.0
Show newest version
/*
 * 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 java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.enterprise.context.Dependent;
import javax.enterprise.inject.Default;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.BeanManager;
import javax.inject.Inject;
import javax.interceptor.InvocationContext;
import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;

import org.apache.deltaspike.core.api.literal.AnyLiteral;
import org.apache.deltaspike.core.util.ProxyUtils;
import org.apache.deltaspike.jpa.api.transaction.Transactional;
import org.apache.deltaspike.jpa.impl.transaction.context.EntityManagerEntry;
import org.apache.deltaspike.jpa.impl.transaction.context.TransactionBeanStorage;
import org.apache.deltaspike.jpa.spi.entitymanager.ActiveEntityManagerHolder;
import org.apache.deltaspike.jpa.spi.transaction.TransactionStrategy;

/**
 * 

Default implementation of our plugable TransactionStrategy. * It supports nested Transactions with the MANDATORY behaviour.

* *

The outermost @Transactional interceptor for the given * {@link javax.inject.Qualifier} will open an {@link javax.persistence.EntityTransaction} * and the outermost @Transactional interceptor for all * EntityManagers will flush and subsequently close all open transactions.

* *

If an Exception occurs in flushing the EntityManagers or any other Exception * gets thrown inside the intercepted method chain and not gets catched * until the outermost @Transactional interceptor gets reached, then all * open transactions will get rollbacked.

* *

If you like to implement your own TransactionStrategy, then use the * standard CDI @Alternative mechanism.

*/ @Dependent public class ResourceLocalTransactionStrategy implements TransactionStrategy { private static final long serialVersionUID = -1432802805095533499L; private static final Logger LOGGER = Logger.getLogger(ResourceLocalTransactionStrategy.class.getName()); @Inject private BeanManager beanManager; @Inject private TransactionStrategyHelper transactionHelper; @Inject private ActiveEntityManagerHolder emHolder; @Override public Object execute(InvocationContext invocationContext) throws Exception { Transactional transactionalAnnotation = transactionHelper.extractTransactionalAnnotation(invocationContext); Class targetClass = ProxyUtils.getUnproxiedClass(invocationContext.getTarget().getClass()); //see DELTASPIKE-517 // all the configured qualifier keys Set> emQualifiers = emHolder.isSet() ? new HashSet>(Arrays.asList(Default.class)) : transactionHelper.resolveEntityManagerQualifiers(transactionalAnnotation, targetClass); TransactionBeanStorage transactionBeanStorage = TransactionBeanStorage.getInstance(); boolean isOutermostInterceptor = transactionBeanStorage.isEmpty(); boolean outermostTransactionAlreadyExisted = false; if (isOutermostInterceptor) { // a new Context needs to get started transactionBeanStorage.startTransactionScope(); } // the 'layer' of the transactional invocation, aka the refCounter @SuppressWarnings("UnusedDeclaration") int transactionLayer = transactionBeanStorage.incrementRefCounter(); Exception firstException = null; try { for (Class emQualifier : emQualifiers) { EntityManager entityManager = resolveEntityManagerForQualifier(emQualifier); EntityManagerEntry entityManagerEntry = createEntityManagerEntry(entityManager, emQualifier); transactionBeanStorage.storeUsedEntityManager(entityManagerEntry); EntityTransaction transaction = getTransaction(entityManagerEntry); if (!transaction.isActive()) { beforeBegin(invocationContext, entityManagerEntry, transaction); transaction.begin(); } else if (isOutermostInterceptor) { outermostTransactionAlreadyExisted = true; } //don't move it before EntityTransaction#begin() and invoke it in any case beforeProceed(invocationContext, entityManagerEntry, transaction); } return invocationContext.proceed(); } catch (Exception e) { firstException = e; // we only cleanup and rollback all open transactions in the outermost interceptor! // this way, we allow inner functions to catch and handle exceptions properly. if (isOutermostInterceptor) { Set entityManagerEntryList = transactionBeanStorage.getUsedEntityManagerEntries(); if (!outermostTransactionAlreadyExisted) { // We only commit transactions we opened ourselfs. // If the transaction got opened outside of our interceptor chain // we must not handle it. // This e.g. happens if a Stateless EJB invokes a Transactional CDI bean // which uses the BeanManagedUserTransactionStrategy. rollbackAllTransactions(entityManagerEntryList); } // drop all EntityManagers from the request-context cache transactionBeanStorage.cleanUsedEntityManagers(); } // give any extensions a chance to supply a better error message e = prepareException(e); // rethrow the exception throw e; } finally { // will get set if we got an Exception while committing // in this case, we rollback all later transactions too. boolean commitFailed = false; // commit all open transactions in the outermost interceptor! // For Resource-local this is a 'JTA for poor men' only, and will not guaranty // commit stability over various databases! // In case of JTA we will just commit the UserTransaction. if (isOutermostInterceptor) { if (!outermostTransactionAlreadyExisted) { // We only commit transactions we opened ourselfs. // If the transaction got opened outside of our interceptor chain // we must not handle it. // This e.g. happens if a Stateless EJB invokes a Transactional CDI bean // which uses the BeanManagedUserTransactionStrategy. if (firstException == null) { // only commit all transactions if we didn't rollback // them already Set entityManagerEntryList = transactionBeanStorage.getUsedEntityManagerEntries(); boolean rollbackOnly = isRollbackOnly(transactionalAnnotation); if (!rollbackOnly) { // but first try to flush all the transactions and write the updates to the database for (EntityManagerEntry currentEntityManagerEntry : entityManagerEntryList) { EntityTransaction transaction = getTransaction(currentEntityManagerEntry); if (transaction != null && transaction.isActive()) { try { if (!commitFailed) { currentEntityManagerEntry.getEntityManager().flush(); if (!rollbackOnly && transaction.getRollbackOnly()) { // don't set commitFailed to true directly // (the order of the entity-managers isn't deterministic // -> tests would break) rollbackOnly = true; } } } catch (Exception e) { firstException = e; commitFailed = true; break; } } } } if (rollbackOnly) { commitFailed = true; } // and now either commit or rollback all transactions for (EntityManagerEntry currentEntityManagerEntry : entityManagerEntryList) { EntityTransaction transaction = getTransaction(currentEntityManagerEntry); if (transaction != null && transaction.isActive()) { try { // last chance to check it (again) if (commitFailed || transaction.getRollbackOnly()) { beforeRollback(invocationContext, currentEntityManagerEntry, transaction); transaction.rollback(); } else { beforeCommit(invocationContext, currentEntityManagerEntry, transaction); transaction.commit(); } } catch (Exception e) { firstException = e; commitFailed = true; } finally { afterProceed(invocationContext,currentEntityManagerEntry, firstException); } } } } } // and now we close the open transaction scope transactionBeanStorage.endTransactionScope(); onCloseTransactionScope(); } transactionBeanStorage.decrementRefCounter(); if (commitFailed && firstException != null /*null if just #getRollbackOnly is true*/) { throwException(firstException); } } } protected void beforeBegin(InvocationContext invocationContext, EntityManagerEntry entityManagerEntry, EntityTransaction transaction) { //override if needed } protected void beforeProceed(InvocationContext invocationContext, EntityManagerEntry entityManagerEntry, EntityTransaction transaction) { //override if needed } protected void beforeCommit(InvocationContext invocationContext, EntityManagerEntry entityManagerEntry, EntityTransaction transaction) { //override if needed } protected void beforeRollback(InvocationContext invocationContext, EntityManagerEntry entityManagerEntry, EntityTransaction transaction) { //override if needed } /** * @param invocationContext current invocation-context * @param entityManagerEntry current entity-manager entry * @param exception the exception which occurred or null if everything went fine */ protected void afterProceed(InvocationContext invocationContext, EntityManagerEntry entityManagerEntry, Exception exception) { //override if needed } protected void throwException(Exception exception) throws Exception { //override if needed throw exception; } //allows to use a custom tx-controller in a custom strategy protected boolean isRollbackOnly(Transactional transactionalAnnotation) { return transactionalAnnotation != null && transactionalAnnotation.readOnly(); } private void rollbackAllTransactions(Set entityManagerEntryList) { for (EntityManagerEntry currentEntityManagerEntry : entityManagerEntryList) { EntityTransaction transaction = getTransaction(currentEntityManagerEntry); if (transaction != null && transaction.isActive()) { try { transaction.rollback(); } catch (Exception eRollback) { if (LOGGER.isLoggable(Level.SEVERE)) { LOGGER.log(Level.SEVERE, "Got additional Exception while subsequently " + "rolling back other SQL transactions", eRollback); } } } } } protected EntityManagerEntry createEntityManagerEntry( EntityManager entityManager, Class qualifier) { return new EntityManagerEntry(entityManager, qualifier); } /** * * @param entityManagerEntry entry of the current entity-manager * @return per default the {@link EntityTransaction} of the given {@link EntityManager}. * A subclass can also return an adapter e.g. for an UserTransaction */ protected EntityTransaction getTransaction(EntityManagerEntry entityManagerEntry) { return entityManagerEntry.getEntityManager().getTransaction(); } private EntityManager resolveEntityManagerForQualifier(Class emQualifier) { if (emHolder.isSet()) { return emHolder.get(); } Bean entityManagerBean = resolveEntityManagerBean(emQualifier); if (entityManagerBean == null) { throw new IllegalStateException("Cannot find an EntityManager qualified with [" + emQualifier.getName() + "]. Did you add a corresponding producer?"); } return (EntityManager) beanManager.getReference(entityManagerBean, EntityManager.class, beanManager.createCreationalContext(entityManagerBean)); } /** * This method might get overridden in subclasses to supply better error messages. * This is useful if e.g. a JPA provider only provides a stubborn Exception for * their ConstraintValidationExceptions. * @return the wrapped or unwrapped Exception */ protected Exception prepareException(Exception e) { return e; } protected void onCloseTransactionScope() { TransactionBeanStorage.close(); } protected Bean resolveEntityManagerBean(Class qualifierClass) { Set> entityManagerBeans = beanManager.getBeans(EntityManager.class, new AnyLiteral()); if (entityManagerBeans == null) { entityManagerBeans = new HashSet>(); } for (Bean currentEntityManagerBean : entityManagerBeans) { Set foundQualifierAnnotations = currentEntityManagerBean.getQualifiers(); for (Annotation currentQualifierAnnotation : foundQualifierAnnotations) { if (currentQualifierAnnotation.annotationType().equals(qualifierClass)) { return (Bean) currentEntityManagerBean; } } } return null; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy