StartModuleExecutionListener.java

/**
 * The contents of this file are subject to the OpenMRS Public License
 * Version 1.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://license.openmrs.org
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 * License for the specific language governing rights and limitations
 * under the License.
 *
 * Copyright (C) OpenMRS, LLC.  All Rights Reserved.
 */
package org.openmrs.test;

import java.net.URL;
import java.util.Enumeration;
import java.util.Properties;

import org.apache.commons.lang.StringUtils;
import org.junit.Assert;
import org.openmrs.api.context.Context;
import org.openmrs.api.db.SerializedObjectDAOTest;
import org.openmrs.module.ModuleConstants;
import org.openmrs.module.ModuleFactory;
import org.openmrs.module.ModuleInteroperabilityTest;
import org.openmrs.module.ModuleUtil;
import org.openmrs.util.OpenmrsClassLoader;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.io.UrlResource;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.support.AbstractTestExecutionListener;

/**
 * To use this annotation, add the @StartModule( { "/path/to/your/module.omod" } ) annotation to the
 * test class
 * 
 * @see SerializedObjectDAOTest
 * @see ModuleInteroperabilityTest
 * @see BaseContextSensitiveTest
 */
public class StartModuleExecutionListener extends AbstractTestExecutionListener {
	
	// stores the last class that restarted the module system because we only 
	// want it restarted once per class, not once per method
	private static String lastClassRun = "";
	
	/**
	 * called before @BeforeTransaction methods
	 * 
	 * @see org.springframework.test.context.support.AbstractTestExecutionListener#prepareTestInstance(org.springframework.test.context.TestContext)
	 */
	@Override
	public void prepareTestInstance(TestContext testContext) throws Exception {
		StartModule startModuleAnnotation = testContext.getTestClass().getAnnotation(StartModule.class);
		
		// if the developer listed some modules with the @StartModule annotation on the class
		if (startModuleAnnotation != null) {
			
			if (!lastClassRun.equals(testContext.getTestClass().getSimpleName())) {
				// mark this with our class so that the services are only restarted once
				lastClassRun = testContext.getTestClass().getSimpleName();
				
				if (!Context.isSessionOpen())
					Context.openSession();
				
				// load the omods that the dev defined for this class
				String modulesToLoad = StringUtils.join(startModuleAnnotation.value(), " ");
				
				Properties props = BaseContextSensitiveTest.runtimeProperties;
				props.setProperty(ModuleConstants.RUNTIMEPROPERTY_MODULE_LIST_TO_LOAD, modulesToLoad);
				try {
					ModuleUtil.startup(props);
				}
				catch (Exception e) {
					System.out.println("Error while starting modules: ");
					e.printStackTrace(System.out);
					throw e;
				}
				Assert.assertTrue("Some of the modules did not start successfully for "
				        + testContext.getTestClass().getSimpleName() + ". Only " + ModuleFactory.getStartedModules().size()
				        + " modules started instead of " + startModuleAnnotation.value().length, startModuleAnnotation
				        .value().length <= ModuleFactory.getStartedModules().size());
				
				/*
				 * Refresh spring so the Services are recreated (aka serializer gets put into the SerializationService)
				 * To do this, wrap the applicationContext from the testContext into a GenericApplicationContext, allowing
				 * loading beans from moduleApplicationContext into it and then calling ctx.refresh()
				 * This approach ensures that the application context remains consistent
				 */
				GenericApplicationContext ctx = new GenericApplicationContext(testContext.getApplicationContext());
				XmlBeanDefinitionReader xmlReader = new XmlBeanDefinitionReader(ctx);
				
				Enumeration<URL> list = OpenmrsClassLoader.getInstance().getResources("moduleApplicationContext.xml");
				while (list.hasMoreElements()) {
					xmlReader.loadBeanDefinitions(new UrlResource(list.nextElement()));
				}
				
				//ensure that when refreshing, we use the openmrs class loader for the started modules.
				boolean useSystemClassLoader = Context.isUseSystemClassLoader();
				Context.setUseSystemClassLoader(false);
				try {
					ctx.refresh();
				}
				finally {
					Context.setUseSystemClassLoader(useSystemClassLoader);
				}
				
				// session is closed by the test framework
				//Context.closeSession();
			}
		}
	}
	
	@Override
	public void afterTestClass(TestContext testContext) throws Exception {
		StartModule startModuleAnnotation = testContext.getTestClass().getAnnotation(StartModule.class);
		
		if (startModuleAnnotation != null) {
			ModuleUtil.shutdown();
		}
	}
}