/** * Licensed to Apereo under one or more contributor license agreements. See the NOTICE file * distributed with this work for additional information regarding copyright ownership. Apereo * 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 the * following location: * * <p>http://www.apache.org/licenses/LICENSE-2.0 * * <p>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.apereo.portal.jpa; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import org.apereo.portal.utils.cache.CacheKey; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.orm.jpa.EntityManagerFactoryUtils; import org.springframework.orm.jpa.EntityManagerHolder; import org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter; import org.springframework.stereotype.Component; import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** * Aspect opens an entity manager around a method invocation * * @see OpenEntityManager * @see org.springframework.orm.jpa.JpaInterceptor */ @Aspect @Component("openEntityManagerAspect") public class OpenEntityManagerAspect implements ApplicationContextAware { protected final Logger logger = LoggerFactory.getLogger(getClass()); private final Map<CacheKey, EntityManagerFactory> entityManagerFactories = new ConcurrentHashMap<CacheKey, EntityManagerFactory>(); private ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } @Pointcut(value = "execution(public * *(..))") public void anyPublicMethod() {} @Around("anyPublicMethod() && @annotation(openEntityManager)") public Object openEntityManager(ProceedingJoinPoint pjp, OpenEntityManager openEntityManager) throws Throwable { final EntityManagerFactory emf = getEntityManagerFactory(openEntityManager); EntityManager em = getTransactionalEntityManager(emf); boolean isNewEm = false; if (em == null) { logger.debug("Opening JPA EntityManager in OpenEntityManagerAspect"); em = createEntityManager(emf); isNewEm = true; TransactionSynchronizationManager.bindResource(emf, new EntityManagerHolder(em)); } else { logger.debug("Using Existing JPA EntityManager in OpenEntityManagerAspect"); } try { return pjp.proceed(); } finally { if (isNewEm) { logger.debug("Closing JPA EntityManager in OpenEntityManagerAspect"); TransactionSynchronizationManager.unbindResource(emf); EntityManagerFactoryUtils.closeEntityManager(em); } } } /** * Obtain the transactional EntityManager for this accessor's EntityManagerFactory, if any. * * @return the transactional EntityManager, or <code>null</code> if none * @throws IllegalStateException if this accessor is not configured with an EntityManagerFactory * @see * EntityManagerFactoryUtils#getTransactionalEntityManager(javax.persistence.EntityManagerFactory) * @see * EntityManagerFactoryUtils#getTransactionalEntityManager(javax.persistence.EntityManagerFactory, * java.util.Map) */ protected EntityManager getTransactionalEntityManager(EntityManagerFactory emf) throws IllegalStateException { Assert.state(emf != null, "No EntityManagerFactory specified"); return EntityManagerFactoryUtils.getTransactionalEntityManager(emf); } /** * Get the EntityManagerFactory that this filter should use. * * @return the EntityManagerFactory to use * @see #lookupEntityManagerFactory(OpenEntityManager) */ protected EntityManagerFactory getEntityManagerFactory(OpenEntityManager openEntityManager) { final CacheKey key = this.createEntityManagerFactoryKey(openEntityManager); EntityManagerFactory emf = this.entityManagerFactories.get(key); if (emf == null) { emf = this.lookupEntityManagerFactory(openEntityManager); this.entityManagerFactories.put(key, emf); } return emf; } /** * Look up the EntityManagerFactory that this filter should use. * * <p>The default implementation looks for a bean with the specified name in Spring's root * application context. * * @return the EntityManagerFactory to use * @see #getEntityManagerFactoryBeanName */ protected EntityManagerFactory lookupEntityManagerFactory(OpenEntityManager openEntityManager) { String emfBeanName = openEntityManager.name(); String puName = openEntityManager.unitName(); if (StringUtils.hasLength(emfBeanName)) { return this.applicationContext.getBean(emfBeanName, EntityManagerFactory.class); } else if (!StringUtils.hasLength(puName) && this.applicationContext.containsBean( OpenEntityManagerInViewFilter.DEFAULT_ENTITY_MANAGER_FACTORY_BEAN_NAME)) { return this.applicationContext.getBean( OpenEntityManagerInViewFilter.DEFAULT_ENTITY_MANAGER_FACTORY_BEAN_NAME, EntityManagerFactory.class); } else { // Includes fallback search for single EntityManagerFactory bean by type. return EntityManagerFactoryUtils.findEntityManagerFactory( this.applicationContext, puName); } } /** * Create a JPA EntityManager to be bound to a request. * * <p>Can be overridden in subclasses. * * @param emf the EntityManagerFactory to use * @see javax.persistence.EntityManagerFactory#createEntityManager() */ protected EntityManager createEntityManager(EntityManagerFactory emf) { return emf.createEntityManager(); } /** * @param openEntityManager The annotation to create a key for * @return The key used to lookup the entity manager for an annotation */ protected final CacheKey createEntityManagerFactoryKey(OpenEntityManager openEntityManager) { return CacheKey.build("", openEntityManager.name(), openEntityManager.unitName()); } }