/* **********************************************************************
/*
* NOTE: This copyright does *not* cover user programs that use Hyperic
* program services by normal system calls through the application
* program interfaces provided as part of the Hyperic Plug-in Development
* Kit or the Hyperic Client Development Kit - this is merely considered
* normal use of the program, and does *not* fall under the heading of
* "derived work".
*
* Copyright (C) [2004-2010], VMware, Inc.
* This file is part of Hyperic.
*
* Hyperic is free software; you can redistribute it and/or modify
* it under the terms version 2 of the GNU General Public License as
* published by the Free Software Foundation. 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA.
*
*/
package org.hyperic.hq.tests.context;
import java.io.File;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import junit.framework.Assert;
import org.hyperic.hq.api.rest.AbstractRestTestDataPopulator;
import org.hyperic.hq.api.rest.AuthenticationTest;
import org.hyperic.hq.api.rest.RestTestCaseBase;
import org.hyperic.hq.api.rest.AbstractRestTestDataPopulator.RestTestData;
import org.hyperic.hq.context.Bootstrap;
import org.hyperic.hq.context.IntegrationTestContextLoader;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.orm.hibernate3.HibernateTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.StringUtils;
import com.meterware.httpunit.HttpNotFoundException;
import com.meterware.httpunit.HttpUnitOptions;
import com.meterware.servletunit.ServletRunner;
public class WebContainerContextLoader extends IntegrationTestContextLoader{
private WebContextConfiguration webContextConfig ;
public WebContainerContextLoader() {super() ;}//EOM
protected static final String DEFAULT_WEB_XML = "/WEB-INF/web-spring.xml" ;
protected static final String DEFAULT_CONTEXT = "/tests" ;
protected static final String DEFAULT_CONTEXT_URL = "http://localhost" + DEFAULT_CONTEXT ;
@Override
public final ApplicationContext loadContext(final String... locations) throws Exception {
if (logger.isDebugEnabled()) {
logger.debug("Loading ApplicationContext for locations [" +
StringUtils.arrayToCommaDelimitedString(locations) + "].");
}//EO if logger is enabled
//attempt to retrieve the class level webContextConfiguration annotations
//if undefined use the default values defined in this class
this.webContextConfig = AnnotationUtils.findAnnotation(this.testClass, WebContextConfiguration.class) ;
String webxml = null, contextRoot = null, contextUrl = null ;
if(this.webContextConfig == null) {
webxml = DEFAULT_CONTEXT ;
contextRoot = DEFAULT_CONTEXT ;
contextUrl = DEFAULT_CONTEXT_URL ;
}else {
webxml = this.webContextConfig.webXml() ;
contextRoot = this.webContextConfig.contextRoot() ;
contextUrl = this.webContextConfig.contextUrl() ;
}//EO else no custom annotation was defined
//set the spring context files locations in the bootstrap to be picked up by the TestBootstapContextListener class and
//injected into the mock servlet configuration as the contextConfigLocation context-param
Bootstrap.setSpringConfigLocations(locations) ;
//create the mock web container which shall be initialized in accordance with the webxmlFile's configuration
//and which shall initalize the spring context using the Bootstrap.getSpringConfigLocations
final File webxmlFile = new File(this.testClass.getResource(webxml).getFile()) ;
final ServletRunner sr = new ServletRunner(webxmlFile, contextRoot);
try {
//lazy load the services
sr.newClient().getResponse(contextUrl + "/services");
} catch (HttpNotFoundException e) {
// ignore, we just want to boot up the servlet
}//EO catch block
HttpUnitOptions.setExceptionsThrownOnErrorStatus(true);
//at this stage the webApplicationContext would have already been set in the bootstrap by the TestBootstapContextListener
final ConfigurableApplicationContext applicationContext = (ConfigurableApplicationContext) Bootstrap.getApplicationContext() ;
//register the service runner in the application context for injection purposes
applicationContext.getBeanFactory().registerSingleton("servletRunner", sr) ;
//wrap the context with a ProxyingGenericApplicationContext instance so as to control its shutdown
final ProxyingGenericApplicationContext actualContext = new ProxyingGenericApplicationContext((AbstractApplicationContext) applicationContext) ;
final TestDataPopulator testDataPopulator = this.getTestDataPopulator(applicationContext) ;
//if the test class defines a TestData Annotation instantiate the test data populator class it defines and invoke its populate()
if(testDataPopulator != null) {
//the populate() invocation shall be executed in the context of a transaction so that it could be rolledback when the context
//shuts down
final PlatformTransactionManager txManager = (PlatformTransactionManager)applicationContext.getBean("transactionManager") ;
//set explicit nested tx support
if(txManager instanceof HibernateTransactionManager) ((HibernateTransactionManager) txManager).setNestedTransactionAllowed(true) ;
//retrieve the txManager bean and invoke the populate in a new top level transaction.
//the transaction would be closed during the application context's shut down.
//all test methods are expected to be marked with Trasnactional(propagation=NESTED)
//which would cause spring to create a save point in already opened top level transaation
//and rollback onto it after each test method
final TransactionTemplate txTemplate = new TransactionTemplate(txManager) ;
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
final TransactionStatus txStatus = txManager.getTransaction(txTemplate);
try{
//invoke the populate() which is responsible for initializing the one-off test data
//shared across all test methods
testDataPopulator.populate() ;
}catch(Throwable t) {
//ensure that the rx is rolledback prior to closing the session so that the top level transaction
//would be rolled back
txStatus.setRollbackOnly() ;
actualContext.close() ;
throw (t instanceof RuntimeException ? (RuntimeException)t : new RuntimeException(t)) ;
}//EO catch block
//Ensure that the context is shutdown prior to JVM shutdown so that resources are cleared properly.
Runtime.getRuntime().addShutdownHook(new Thread() {
private TestDataPopulator populator = testDataPopulator ;
@Override
public final void run() {
try{
actualContext.destroy() ;
}catch(Throwable t) {
t.printStackTrace() ;
}//EO catch block
}//EOM
}) ;
//Register shutdown sequence in which the webcontainer is shutdown and any top level transaction
//is rolledback so that the actions of the test data populator are reverted.
applicationContext.addApplicationListener(new ApplicationListener<ContextClosedEvent>() {
// @Override
public void onApplicationEvent(ContextClosedEvent event) {
sr.shutDown() ;
if(txStatus != null) {
//rollback test data populator
txStatus.setRollbackOnly() ;
txManager.commit(txStatus) ;
}//EO if managed to create
}//EOM
});
}//EO if there was a test data annotation handler
return actualContext ;
}//EOM
private final TestDataPopulator getTestDataPopulator(final ConfigurableApplicationContext applicationContext) {
TestDataPopulator testDataPopulator = null ;
//first attempt to locate a RestTestdata annotation, if found, attempt
Class<? extends TestDataPopulator> testDataPopulatorClass = null ;
//first attempt to retrieve a RestTestData annotation
final RestTestData restTestDataAnnotation = AnnotationUtils.findAnnotation(this.testClass, RestTestData.class) ;
final TestData testDataAnnotation = AnnotationUtils.findAnnotation(this.testClass, TestData.class) ;
if(testDataAnnotation != null) {
testDataPopulatorClass = testDataAnnotation.value() ;
}else if(RestTestCaseBase.class.isAssignableFrom(this.testClass) ) {
final ParameterizedType genericSuprtClassType = (ParameterizedType) AuthenticationTest.class.getGenericSuperclass() ;
final ParameterizedType testDataPopulatorType = (ParameterizedType) genericSuprtClassType.getActualTypeArguments()[1] ;
testDataPopulatorClass = (Class<? extends TestDataPopulator>) testDataPopulatorType.getRawType() ;
}//EO else if not an instance of ResttestCaseBase
//if the test class defines a TestData Annotation instantiate the test data populator class it defines and invoke its populate()
if(testDataPopulatorClass != null) {
final DefaultListableBeanFactory beansFactory = (DefaultListableBeanFactory) applicationContext.getBeanFactory() ;
final AbstractBeanDefinition beanDefinition =
BeanDefinitionBuilder.rootBeanDefinition(testDataPopulatorClass).getBeanDefinition() ;
beansFactory.registerBeanDefinition(testDataPopulatorClass.getName(), beanDefinition) ;
testDataPopulator = (TestDataPopulator) applicationContext.getBean(testDataPopulatorClass) ;
//set the rest test data in the instance
if(testDataPopulator instanceof AbstractRestTestDataPopulator) {
((AbstractRestTestDataPopulator) testDataPopulator).setRestTestData(restTestDataAnnotation) ;
}//EO if instanceof AbstractRestTestDataPopulator
}///EO if a testdatapopulator class was inferred
return testDataPopulator ;
}//EOM
}//EOC