/*
* Copyright 2009 NCHOVY
*
* Licensed 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.krakenapps.jpa.impl;
import java.util.EmptyStackException;
import java.util.Stack;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import org.krakenapps.jpa.JpaService;
import org.krakenapps.jpa.ThreadLocalEntityManagerService;
import org.krakenapps.jpa.handler.TransactionOption;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Implementation for thread local entity manager service interface
*
* @author xeraph
*
*/
public class ThreadLocalEntityManagerServiceImpl implements ThreadLocalEntityManagerService {
final Logger logger = LoggerFactory.getLogger(ThreadLocalEntityManagerServiceImpl.class.getName());
private JpaService jpa;
private ThreadLocal<Stack<TransactionState>> transactionStacks = new ThreadLocal<Stack<TransactionState>>();
class TransactionState {
public String factoryName;
public EntityManager entityManager;
public boolean isOwner;
}
@Override
public void setEntityManagerFactory(String factoryName, TransactionOption transactionMode) {
logger.debug("JPA: setting new entity manager factory [{}] mode [{}] ", factoryName, transactionMode);
if (jpa == null) {
logger.warn("JPA: JPA service not found");
return;
}
EntityManagerFactory factory = jpa.getEntityManagerFactory(factoryName);
if (factory == null) {
logger.warn("JPA: entity manager factory [{}] not found", factoryName);
return;
}
TransactionState newState = new TransactionState();
newState.factoryName = factoryName;
EntityManager em = findEntityManager(factoryName);
if (em == null || (em != null && transactionMode == TransactionOption.RequiresNew)) {
newState.entityManager = factory.createEntityManager();
newState.isOwner = true;
if (logger.isDebugEnabled())
logger.debug("JPA: new entity manager created: {}", factoryName);
} else {
newState.entityManager = em;
newState.isOwner = false;
if (logger.isDebugEnabled())
logger.debug("JPA: entity manager found in the current thread context: {}", factoryName);
}
Stack<TransactionState> stack = getTransactionStack();
stack.push(newState);
}
private Stack<TransactionState> getTransactionStack() {
if (transactionStacks.get() == null)
transactionStacks.set(new Stack<TransactionState>());
return transactionStacks.get();
}
private EntityManager findEntityManager(String factoryName) {
Stack<TransactionState> stack = getTransactionStack();
for (TransactionState state : stack) {
if (factoryName.equals(state.factoryName)) {
return state.entityManager;
}
}
return null;
}
@Override
public EntityManager getEntityManager() {
TransactionState state = getTransactionState();
if (state == null)
throw new IllegalStateException(
"Transaction is not active. Check @Transactional annotation and/or entity manager factory.");
return state.entityManager;
}
public TransactionState getTransactionState() {
try {
return getTransactionStack().peek();
} catch (EmptyStackException e) {
return null;
}
}
@Override
public void beginTransaction() {
beginTransaction(false);
}
@Override
public void beginTransaction(boolean failOnActiveTransaction) {
TransactionState state = getTransactionState();
if (state.isOwner == false) {
logger.debug("JPA: overlapped transaction. begin() ignored.");
return;
}
logger.debug("JPA: begin transaction");
EntityManager em = state.entityManager;
if (isOpen(em) == false) {
throw new IllegalStateException("JPA: EntityManager is null or already closed.");
}
if (em.getTransaction().isActive() == false) {
em.getTransaction().begin();
} else if (failOnActiveTransaction) {
logger.warn("JPA: Transaction is already active.");
throw new IllegalStateException("JPA: Transaction is already active.");
}
}
@Override
public void commitTransaction() {
TransactionState state = getTransactionState();
if (state.isOwner == false) {
logger.debug("JPA: overlapped transaction. commit() ignored.");
return;
}
logger.debug("JPA: commit transaction");
EntityManager em = state.entityManager;
if (isInActiveTransaction(em) == false)
return;
em.flush();
em.getTransaction().commit();
}
@Override
public void rollbackTransaction() {
TransactionState state = getTransactionState();
if (state.isOwner == false) {
logger.debug("JPA: overlapped transaction. rollback() ignored.");
return;
}
logger.debug("JPA: rollback transaction");
EntityManager em = getEntityManager();
if (isInActiveTransaction(em) == false)
return;
em.getTransaction().rollback();
}
@Override
public void setRollbackOnlyTransaction() {
TransactionState state = getTransactionState();
if (state.isOwner) {
logger.debug("JPA: set transaction rollback-only");
getEntityManager().getTransaction().setRollbackOnly();
}
}
@Override
public void closeEntityManager() {
TransactionState state = getTransactionState();
try {
getTransactionStack().pop();
} catch (EmptyStackException e) {
logger.warn("JPA: illegal closeEntityManager() call");
return;
}
if (state.isOwner == false) {
logger.debug("JPA: overlapped transaction. close() ignored.");
return;
}
EntityManager em = state.entityManager;
if (isOpen(em))
em.close();
}
private boolean isOpen(EntityManager em) {
return em != null && em.isOpen();
}
private boolean isInActiveTransaction(EntityManager em) {
return isOpen(em) && em.getTransaction().isActive();
}
}