/*
* RHQ Management Platform
* Copyright (C) 2005-2014 Red Hat, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
package org.rhq.core.domain.test;
import java.io.File;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Properties;
import java.util.regex.Pattern;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.PersistenceContext;
import javax.transaction.TransactionManager;
import org.testng.AssertJUnit;
import org.testng.ITestResult;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.test.api.ArquillianResource;
import org.jboss.arquillian.testng.Arquillian;
import org.jboss.shrinkwrap.api.ArchivePaths;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.EmptyAsset;
import org.jboss.shrinkwrap.api.spec.EnterpriseArchive;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.jboss.shrinkwrap.resolver.api.maven.Maven;
import org.jboss.shrinkwrap.resolver.api.maven.MavenResolverSystem;
import org.rhq.core.domain.shared.TransactionCallback;
import org.rhq.core.util.MessageDigestGenerator;
import org.rhq.core.util.exception.ThrowableUtil;
import org.rhq.core.util.stream.StreamUtil;
import org.rhq.test.AssertUtils;
import org.rhq.test.MatchResult;
import org.rhq.test.PropertyMatchException;
import org.rhq.test.PropertyMatcher;
public abstract class AbstractEJB3Test extends Arquillian {
protected static final String JNDI_RHQDS = "java:jboss/datasources/RHQDS";
@PersistenceContext
protected EntityManager em;
@ArquillianResource
protected InitialContext initialContext;
// We originally deployed the domain jar as a JavaArchive (JAR file). But this ran into problems because
// of the dependent 3rd party jars. There isn't an obvious way to deploy the dependent jars of a jar.
// Also, AS7 makes it hard to put them in a globally accessibly /lib directory due to
// its classloader isolation and module approach. So, we now deploy an EnterpriseArchive (ear)
// where the domain jar is deployed as a module and the 3rd party libraries are put in the ear's /lib.
@Deployment
protected static EnterpriseArchive getBaseDeployment() {
// Create the domain jar which will subsequently be packaged in the ear deployment
JavaArchive ejbJar = ShrinkWrap.create(JavaArchive.class, "rhq-core-domain-ejb3.jar") //
.addAsManifestResource("ejb-jar.xml") // the empty ejb jar descriptor
.addAsManifestResource("test-persistence.xml", "persistence.xml") // the test persistence context
.addAsManifestResource(EmptyAsset.INSTANCE, ArchivePaths.create("beans.xml") // add CDI injection (needed by arquillian injection)
);
// non-domain RHQ classes in use by domain test classes
ejbJar.addClass(ThrowableUtil.class) //
.addClass(MessageDigestGenerator.class) //
.addClass(StreamUtil.class) //
.addClass(AssertUtils.class) //
.addClasses(PropertyMatcher.class, MatchResult.class, PropertyMatchException.class);
// domain classes
ejbJar = addClasses(ejbJar, new File("target/classes/org"), null);
// SetupBean class
ejbJar.addClass(SetupBean.class);
JavaArchive testClassesJar = ShrinkWrap.create(JavaArchive.class, "test-classes.jar");
testClassesJar = addClasses(testClassesJar, new File("target/test-classes/org"), null);
// create test ear
EnterpriseArchive ear = ShrinkWrap.create(EnterpriseArchive.class, "test-domain.ear") //
.addAsModule(ejbJar) // the domain jar under test
.addAsLibrary(testClassesJar) // the actual test classes
.setApplicationXML("application.xml"); // the application xml declaring the ejb jar
// Adding the 3rd party jars is not easy. There were basically two approaches I could think of:
//
// 1) Use the MavenDependencyResolver to figure out and add all of the test deps and put them in lib
//
// This immediately ran into trouble as there were way more jars sucked in than were actually necessary.
// Furthermore, it included arquillian, shrinkwrap, jboss, etc.. lots of jars that actually caused
// issues like locking and other horrible things due, I suppose, to just stepping all over the test env
// set up by Arquillian. So, the next step was to try and start excluding the unwanted jars. This was
// tedious and difficult and I didn't really get it close to working before giving up and going with
// option 2.
//
// 2) Use the MavenDependencyResolver to locate and pull just the artifacts we need.
//
// This is annoying because it's basically duplicating the same sort of effort that we already
// use maven to do for us. It involves running, failing on NoClassDefFound, fixing it, repeat. It
// does pull in necessary transitive deps but sometimes it pulls in unwanted transitive deps. So,
// we still end up having to do some exclusion filtering. Since Shrinkwrap has weak and buggy
// filtering, we have some homegrown filtering methods below.
// TODO: Is there any way to not have to specify the versions for the transitive deps? This is brittle as is.
// Can pass the version in as a system prop if necessary...
//load 3rd party deps explicitly
Collection<String> thirdPartyDeps = new ArrayList<String>();
thirdPartyDeps.add("commons-beanutils:commons-beanutils:1.8.2");
thirdPartyDeps.add("commons-codec:commons-codec");
thirdPartyDeps.add("commons-io:commons-io");
thirdPartyDeps.add("org.unitils:unitils-testng:3.1");
thirdPartyDeps.add("org.rhq:rhq-core-dbutils:" + System.getProperty("project.version")); // needed by SetupBean
MavenResolverSystem resolver = Maven.resolver();
Collection<JavaArchive> dependencies = new HashSet<JavaArchive>();
dependencies.addAll(Arrays.asList(resolver.loadPomFromFile("pom.xml").resolve(thirdPartyDeps)
.withTransitivity().as(JavaArchive.class)));
String[] excludeFilters = { "testng.*jdk" };
dependencies = exclude(dependencies, excludeFilters);
ear = ear.addAsLibraries(dependencies);
System.out.println("** The Deployment EAR: " + ear.toString(true) + "\n");
return ear;
}
/**
* @param dependencies The current set of deps - THIS IS FILTERED, not copied
* @param filters regex filters
* @return the filtered set of deps
*/
public static Collection<JavaArchive> exclude(Collection<JavaArchive> dependencies, String... filters) {
for (String filter : filters) {
Pattern p = Pattern.compile(filter);
for (Iterator<JavaArchive> i = dependencies.iterator(); i.hasNext();) {
JavaArchive dependency = i.next();
if (p.matcher(dependency.getName()).find()) {
i.remove();
}
}
}
return dependencies;
}
/**
* @param dependencies The current set of deps - this is not changed
* @param filters regex filters
* @return set set of JavaArchives in dependencies that matched an include filter
*/
public static Collection<JavaArchive> include(Collection<JavaArchive> dependencies, String... filters) {
Collection<JavaArchive> result = new HashSet<JavaArchive>();
for (String filter : filters) {
Pattern p = Pattern.compile(filter);
for (JavaArchive dependency : dependencies) {
if (p.matcher(dependency.getName()).find()) {
result.add(dependency);
}
}
}
return result;
}
public static JavaArchive addClasses(JavaArchive archive, File dir, String packageName) {
packageName = (null == packageName) ? "" + dir.getName() : packageName + "." + dir.getName();
for (File file : dir.listFiles()) {
String fileName = file.getName();
if (file.isDirectory()) {
archive = addClasses(archive, file, packageName);
} else if (fileName.endsWith(".class")) {
int dot = fileName.indexOf('.');
try {
archive.addClass(packageName + "." + fileName.substring(0, dot));
} catch (Exception e) {
System.out.println("WARN: Could not add class:" + e);
}
}
}
return archive;
}
/**
* <p>DO NOT OVERRIDE.</p>
* <p>DO NOT DEFINE AN @BeforeMethod</p>
*
* Instead, override {@link #beforeMethod()}. If you must override, for example, if you
* need to use special attributes on your annotation, then ensure you protect the code with
* and {@link #inContainer()} call.
*/
@BeforeMethod
protected void __beforeMethod(Method method) throws Throwable {
// Note that Arquillian calls the testng BeforeMethod twice (as of 1.0.2.Final, once
// out of container and once in container. In general the expectation is to execute it
// one time, and doing it in container allows for the expected injections and context.
if (inContainer()) {
try {
beforeMethod();
} catch (Throwable t) {
// Arquillian is eating these, make sure they show up in some way
System.out.println("BEFORE METHOD FAILURE, TEST DID NOT RUN!!! [" + method.getName() + "]");
t.printStackTrace();
throw t;
}
}
}
/**
* <p>DO NOT OVERRIDE.</p>
* <p>DO NOT DEFINE AN @AfterMethod</p>
*
* Instead, override {@link #afterMethod()}.
*/
@AfterMethod(alwaysRun = true)
protected void __afterMethod(ITestResult result, Method method) throws Throwable {
try {
if (inContainer()) {
afterMethod();
}
} catch (Throwable t) {
System.out
.println("AFTER METHOD FAILURE, TEST CLEAN UP FAILED!!! MAY NEED TO CLEAN DB BEFORE RUNNING MORE TESTS! ["
+ method.getName() + "]");
t.printStackTrace();
throw t;
}
}
protected boolean inContainer() {
// If the injection is done we're running in the container.
return (null != initialContext);
}
/**
* Override Point! Do not implement a @BeforeMethod, instead override this method.
*/
protected void beforeMethod() throws Exception {
// do nothing if we're not overridden
}
/**
* Override Point! Do not implement an @AfterMethod, instead override this method.
*/
protected void afterMethod() throws Exception {
// do nothing if we're not overridden
}
protected void startTransaction() throws Exception {
getTransactionManager().begin();
}
protected void commitTransaction() throws Exception {
getTransactionManager().commit();
}
protected void rollbackTransaction() throws Exception {
getTransactionManager().rollback();
}
protected InitialContext getInitialContext() {
// may be null if not yet injected
if (null != initialContext) {
return initialContext;
}
InitialContext result;
try {
Properties jndiProperties = new Properties();
jndiProperties.put(Context.INITIAL_CONTEXT_FACTORY, "org.jboss.as.naming.InitialContextFactory");
result = new InitialContext(jndiProperties);
} catch (Exception e) {
throw new RuntimeException("Failed to get InitialContext", e);
}
return result;
}
public TransactionManager getTransactionManager() {
TransactionManager result;
try {
result = (TransactionManager) getInitialContext().lookup("java:jboss/TransactionManager");
if (null == result) {
result = (TransactionManager) getInitialContext().lookup("TransactionManager");
}
} catch (Exception e) {
throw new RuntimeException("Failed to get TransactionManager", e);
}
return result;
}
protected EntityManager getEntityManager() {
// may be null if not yet injected (as of 1.0.1.Final, only injected inside @Test)
if (null != em) {
return em;
}
EntityManager result;
try {
EntityManagerFactory emf = (EntityManagerFactory) getInitialContext().lookup(
"java:jboss/RHQEntityManagerFactory");
result = emf.createEntityManager();
} catch (Exception e) {
throw new RuntimeException("Failed to get EntityManagerFactory", e);
}
return result;
}
/**
* Equivalent to <pre>executeInTransaction(true, callback)</pre>.
* @param callback
*/
protected void executeInTransaction(TransactionCallback callback) {
executeInTransaction(true, callback);
}
protected void executeInTransaction(boolean rollback, TransactionCallback callback) {
try {
startTransaction();
callback.execute();
} catch (Throwable t) {
rollback = true;
RuntimeException re = new RuntimeException(ThrowableUtil.getAllMessages(t), t);
throw re;
} finally {
try {
if (!rollback) {
commitTransaction();
} else {
rollbackTransaction();
}
} catch (Exception e) {
//noinspection ThrowFromFinallyBlock
throw new RuntimeException("Failed to " + (rollback ? "rollback" : "commit") + " transaction", e);
}
}
}
/* The old AbstractEJB3Test impl extended AssertJUnit. Continue to support the used methods
* with various call-thru methods.
*/
protected void assertNotNull(String msg, Object o) {
AssertJUnit.assertNotNull(msg, o);
}
protected void assertNull(String msg, Object o) {
AssertJUnit.assertNull(msg, o);
}
protected void assertFalse(String msg, boolean b) {
AssertJUnit.assertFalse(msg, b);
}
protected void assertTrue(String msg, boolean b) {
AssertJUnit.assertTrue(msg, b);
}
protected void assertEquals(String msg, int expected, int actual) {
AssertJUnit.assertEquals(msg, expected, actual);
}
protected void assertEquals(String msg, String expected, String actual) {
AssertJUnit.assertEquals(msg, expected, actual);
}
protected void fail(String message) {
AssertJUnit.fail(message);
}
private static final long DEFAULT_OFFSET = 50;
private long referenceTime = new Date().getTime();
public Date getAnotherDate() {
return getAnotherDate(DEFAULT_OFFSET);
}
public Date getAnotherDate(long offset) {
referenceTime += offset;
return new Date(referenceTime);
}
}