/*
* 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.testcontrol.impl.transaction;
import junit.framework.AssertionFailedError;
import org.apache.deltaspike.core.api.provider.BeanProvider;
import org.apache.deltaspike.core.util.ExceptionUtils;
import org.apache.deltaspike.testcontrol.spi.junit.TestStatementDecoratorFactory;
import org.junit.After;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.MultipleFailureException;
import org.junit.runners.model.Statement;
import org.junit.runners.model.TestClass;
import javax.persistence.EntityManager;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Not compatible with too short scopes, because they are closed too early
*/
public class TransactionStatementDecoratorFactory implements TestStatementDecoratorFactory
{
private static final Logger LOG = Logger.getLogger(TransactionStatementDecoratorFactory.class.getName());
static
{
LOG.setLevel(Level.INFO);
}
@Override
public Statement createBeforeStatement(Statement originalStatement, TestClass testClass, Object target)
{
return originalStatement;
}
@Override
public Statement createAfterStatement(Statement originalStatement, TestClass testClass, Object target)
{
final List<FrameworkMethod> afters = testClass.getAnnotatedMethods(After.class);
return new TransactionAwareRunAfters(originalStatement, afters, target);
}
@Override
public int getOrdinal()
{
return 1000; //default in ds
}
//see org.junit.internal.runners.statements.RunAfters
private class TransactionAwareRunAfters extends Statement
{
private final Statement wrapped;
private final List<FrameworkMethod> afters;
private final Object target;
public TransactionAwareRunAfters(Statement wrapped, List<FrameworkMethod> afters, Object target)
{
this.wrapped = wrapped;
this.afters = afters;
this.target = target;
}
@Override
public void evaluate() throws Throwable
{
List<Throwable> result = new ArrayList<Throwable>();
try
{
this.wrapped.evaluate();
}
catch (Throwable e)
{
result.add(performConsistencyCheck(e));
}
finally
{
Throwable t = performConsistencyCheck(null);
if (t != null)
{
result.add(t);
}
for (FrameworkMethod each : this.afters)
{
try
{
each.invokeExplosively(this.target);
}
catch (Throwable e)
{
result.add(e);
}
}
}
if (!result.isEmpty())
{
MultipleFailureException.assertEmpty(result);
}
}
private Throwable performConsistencyCheck(Throwable t)
{
Throwable result = t;
if (t instanceof InvocationTargetException)
{
result = t.getCause();
}
List<EntityManager> entityManagerList =
BeanProvider.getContextualReferences(EntityManager.class, true, false);
for (Field field : this.target.getClass().getDeclaredFields())
{
if (EntityManager.class.isAssignableFrom(field.getType()))
{
field.setAccessible(true);
try
{
entityManagerList.add((EntityManager) field.get(this.target));
}
catch (Exception e)
{
throw ExceptionUtils.throwAsRuntimeException(e);
}
}
}
for (EntityManager entityManager : entityManagerList)
{
if (entityManager.getTransaction().isActive())
{
if (t instanceof AssertionFailedError)
{
LOG.severe("assert failed within a transaction");
}
LOG.severe("start manual rollback");
//force rollback - otherwise there can be side-effects in other tests or cleanup-code
// (e.g. 'TRUNCATE SCHEMA' would fail)
entityManager.getTransaction().rollback();
if (result == null)
{
result = new IllegalStateException("open transaction found");
}
}
}
return result;
}
}
}