/** * 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.test; import java.lang.reflect.AccessibleObject; import java.lang.reflect.Method; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.concurrent.Callable; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.Query; import javax.persistence.metamodel.EntityType; import javax.persistence.metamodel.Metamodel; import org.aopalliance.intercept.MethodInvocation; import org.apereo.portal.concurrency.CallableWithoutResult; import org.apereo.portal.spring.MockitoFactoryBean; import org.junit.After; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.orm.jpa.JpaInterceptor; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.TransactionCallback; import org.springframework.transaction.support.TransactionOperations; /** * Base class for JPA based unit tests that want TX and entity manager support. Also deletes all * hibernate managed data from the database after each test execution * */ public abstract class BaseJpaDaoTest { protected final Logger logger = LoggerFactory.getLogger(getClass()); protected JpaInterceptor jpaInterceptor; protected TransactionOperations transactionOperations; @Autowired public final void setJpaInterceptor(JpaInterceptor jpaInterceptor) { this.jpaInterceptor = jpaInterceptor; } @Autowired public void setTransactionOperations(TransactionOperations transactionOperations) { this.transactionOperations = transactionOperations; } protected abstract EntityManager getEntityManager(); /** Deletes ALL entities from the database */ @After public final void deleteAllEntities() { final EntityManager entityManager = getEntityManager(); final EntityManagerFactory entityManagerFactory = entityManager.getEntityManagerFactory(); final Metamodel metamodel = entityManagerFactory.getMetamodel(); Set<EntityType<?>> entityTypes = new LinkedHashSet<EntityType<?>>(metamodel.getEntities()); do { final Set<EntityType<?>> failedEntitieTypes = new HashSet<EntityType<?>>(); for (final EntityType<?> entityType : entityTypes) { final String entityClassName = entityType.getBindableJavaType().getName(); try { this.executeInTransaction( new CallableWithoutResult() { @Override protected void callWithoutResult() { logger.trace("Purging all: " + entityClassName); final Query query = entityManager.createQuery( "SELECT e FROM " + entityClassName + " AS e"); final List<?> entities = query.getResultList(); logger.trace( "Found " + entities.size() + " " + entityClassName + " to delete"); for (final Object entity : entities) { entityManager.remove(entity); } } }); } catch (DataIntegrityViolationException e) { logger.trace( "Failed to delete " + entityClassName + ". Must be a dependency of another entity"); failedEntitieTypes.add(entityType); } } entityTypes = failedEntitieTypes; } while (!entityTypes.isEmpty()); //Reset all spring managed mocks after every test MockitoFactoryBean.resetAllMocks(); } /** Executes the callback inside of a {@link JpaInterceptor}. */ @SuppressWarnings("unchecked") public final <T> T execute(final Callable<T> callable) { try { return (T) this.jpaInterceptor.invoke(new MethodInvocationCallable<T>(callable)); } catch (Throwable e) { if (e instanceof RuntimeException) { throw (RuntimeException) e; } if (e instanceof Error) { throw (Error) e; } throw new RuntimeException(e); } } /** * Executes the callback inside of a {@link JpaInterceptor} inside of a {@link * TransactionCallback} */ public final <T> T executeInTransaction(final Callable<T> callable) { return execute( new Callable<T>() { @Override public T call() throws Exception { return transactionOperations.execute( new TransactionCallback<T>() { @Override public T doInTransaction(TransactionStatus status) { try { return callable.call(); } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new RuntimeException(e); } } }); } }); } /** * Executes the callback in a new thread inside of a {@link JpaInterceptor}. Waits for the * Thread to return. */ public final <T> T executeInThread(String name, final Callable<T> callable) { final List<RuntimeException> exception = new LinkedList<RuntimeException>(); final List<T> retVal = new LinkedList<T>(); final Thread t2 = new Thread( new Runnable() { @Override public void run() { try { final T val = execute(callable); retVal.add(val); } catch (Throwable e) { if (e instanceof RuntimeException) { exception.add((RuntimeException) e); } else { exception.add(new RuntimeException(e)); } } } }, name); t2.start(); try { t2.join(); } catch (InterruptedException e) { throw new RuntimeException(e); } if (exception.size() == 1) { throw exception.get(0); } return retVal.get(0); } private static final class MethodInvocationCallable<T> implements MethodInvocation { private final Callable<T> callable; private MethodInvocationCallable(Callable<T> callable) { this.callable = callable; } @Override public Object proceed() throws Throwable { return callable.call(); } @Override public Object getThis() { throw new UnsupportedOperationException(); } @Override public AccessibleObject getStaticPart() { throw new UnsupportedOperationException(); } @Override public Object[] getArguments() { throw new UnsupportedOperationException(); } @Override public Method getMethod() { throw new UnsupportedOperationException(); } } }