/* * 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.handler; import java.lang.reflect.Method; import java.util.Dictionary; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import javax.persistence.EntityManagerFactory; import org.apache.felix.ipojo.ConfigurationException; import org.apache.felix.ipojo.PrimitiveHandler; import org.apache.felix.ipojo.metadata.Element; import org.apache.felix.ipojo.parser.MethodMetadata; import org.krakenapps.jpa.EntityManagerFactoryListener; import org.krakenapps.jpa.JpaService; import org.krakenapps.jpa.ThreadLocalEntityManagerService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Provide declarative transaction support using iPOJO component handler. * TransactionHandler starts transaction at method entry, and commit/rollback * transaction at method exit. * * @author xeraph * */ public class TransactionalHandler extends PrimitiveHandler implements EntityManagerFactoryListener { final Logger logger = LoggerFactory.getLogger(Transactional.class.getName()); private String factoryName; private JpaService jpaService; private ThreadLocalEntityManagerService threadLocalEntityManager; private String pojoClassName; private Map<Method, TransactionOption> methodFactoryMap = new ConcurrentHashMap<Method, TransactionOption>(); /** * Find and hook all transactional methods. */ @SuppressWarnings("rawtypes") @Override public void configure(Element metadata, Dictionary configuration) throws ConfigurationException { // Get factory name Element[] elements = metadata.getElements("Transactional", "org.krakenapps.jpa.handler"); if (elements != null && elements.length > 0) { Element transactionalElement = elements[0]; factoryName = transactionalElement.getAttribute("name"); } // try external handler annotation if (factoryName == null) { Element[] el = metadata.getElements("JpaConfig", "org.krakenapps.jpa.handler"); if (el == null || el.length == 0) throw new IllegalStateException("@JpaConfig configuration not found."); Element entityManagerFactoryElement = el[0]; factoryName = entityManagerFactoryElement.getAttribute("factory"); } Object o = getInstanceManager().getPojoObject(); pojoClassName = o.getClass().getName(); logger.trace("JPA: using [" + factoryName + "] entity manager factory for [{}].", pojoClassName); for (Method m : o.getClass().getDeclaredMethods()) { Transactional t = m.getAnnotation(Transactional.class); if (t != null) { logger.trace("JPA: transactional method detected: " + m.getName()); String[] parameterTypes = getMethodParameterTypes(m); MethodMetadata mm = getPojoMetadata().getMethod(m.getName(), parameterTypes); if (mm != null) { logger.trace("JPA: annotated method injected: " + getMethodSignature(m)); methodFactoryMap.put(m, t.value()); getInstanceManager().register(mm, this); } } } } private String[] getMethodParameterTypes(Method m) { Class<?>[] types = m.getParameterTypes(); String[] typeNames = new String[types.length]; for (int i = 0; i < types.length; ++i) { if (types[i].isArray()) { String typeName = types[i].getComponentType().getName(); typeNames[i] = typeName + "[]"; } else { typeNames[i] = types[i].getName(); } } return typeNames; } private String getMethodSignature(Method m) { StringBuilder sr = new StringBuilder(); String[] types = getMethodParameterTypes(m); if (types.length == 0) return "()"; sr.append("("); sr.append(types[0]); for (int i = 1; i < types.length; i++) { sr.append(", "); sr.append(types[i]); } sr.append(")"); return sr.toString(); } /** * Check if entity manager factory is available and register factory life * cycle event listener */ @Override public void start() { logger.trace("JPA: transactional handler for [{}] started ", pojoClassName); setValidity(false); if (jpaService != null) { validateEntityManagerFactory(factoryName); } else { logger.trace("JPA: factory not found for [{}] transaction handler.", pojoClassName); } } private void validateEntityManagerFactory(String factoryName) { logger.trace("JPA: adding [{}] entity manager factory listener for {}", factoryName, pojoClassName); jpaService.addEntityManagerFactoryListener(this); if (jpaService.getEntityManagerFactory(factoryName) != null) { logger.trace("JPA: [{}] factory found. [{}] transaction handler validated.", factoryName, pojoClassName); setValidity(true); } } /** * Unregister factory life cycle event listener. */ @Override public void stop() { logger.trace("JPA: transactional handler for [{}] stopped.", pojoClassName); if (jpaService != null) { jpaService.removeEntityManagerFactoryListener(this); } } /** * Find the entity manager in the current thread context and begin * transaction at entry of transactional method. */ @Override public void onEntry(Object pojo, Method method, Object[] args) { logger.debug("JPA: TransactionalHandler method [{}] entry", method.getName()); TransactionOption transactionMode = methodFactoryMap.get(method); if (threadLocalEntityManager != null) { threadLocalEntityManager.setEntityManagerFactory(factoryName, transactionMode); threadLocalEntityManager.beginTransaction(); } } /** * Commit transaction in the current thread context at exit of transactional * method. onError method will be called if commit failed. */ @Override public void onExit(Object pojo, Method method, Object returnedObj) { String methodName = method.getName(); if (threadLocalEntityManager != null) threadLocalEntityManager.commitTransaction(); logger.debug("JPA: TransactionalHandler method [{}] exit", methodName); } /** * Rollback transaction in the current thread context if an exception * raised. */ @Override public void onError(Object pojo, Method method, Throwable throwable) { if (threadLocalEntityManager != null) threadLocalEntityManager.rollbackTransaction(); logger.error("JPA: TransactionalHandler onError: ", throwable); } /** * Close the entity manager in the current thread context. */ @Override public void onFinally(Object pojo, Method method) { if (threadLocalEntityManager != null) threadLocalEntityManager.closeEntityManager(); else logger.warn("JPA: thread local entity manager is null."); } /** * Set new JPA service and register event listener to JPA service. * * @param jpaService * new JPA service */ public void setJpaService(JpaService jpaService) { this.jpaService = jpaService; validateEntityManagerFactory(factoryName); } /** * Set new thread local entity manager service * * @param threadLocalEntityManager * the thread local entity manager service */ public void setThreadLocalEntityManager(ThreadLocalEntityManagerService threadLocalEntityManager) { this.threadLocalEntityManager = threadLocalEntityManager; } /** * Move to valid state if associated entity manager factory is added. */ @Override public void factoryAdded(String factoryName, EntityManagerFactory factory) { if (this.factoryName.compareTo(factoryName) == 0) { logger.trace("JPA: {} entity manager factory added. validating [{}]", factoryName, pojoClassName); setValidity(true); } } /** * Move to invalid state if associated entity manager factory is removed. */ @Override public void factoryRemoved(String factoryName, EntityManagerFactory factory) { if (this.factoryName.compareTo(factoryName) == 0) { logger.trace("JPA: {} entity manager factory removed. invalidating [{}]", factoryName, pojoClassName); setValidity(false); } } }