/**********************************************************************************
*
* $Id: ComponentContainerEmulator.java 105077 2012-02-24 22:54:29Z ottenhoff@longsight.com $
*
***********************************************************************************
*
* Copyright (c) 2008 The Sakai Foundation
*
* Licensed under the Educational Community 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.opensource.org/licenses/ECL-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.sakaiproject.test;
import java.io.File;
import java.io.FileFilter;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Emulate the Sakai component environment set up by a running Tomcat container.
*/
public class ComponentContainerEmulator {
private static final Log log = LogFactory.getLog(ComponentContainerEmulator.class);
private static Object componentManager;
/**
* Configures the emulated component container to run integration tests.
*/
public static void startComponentManagerForTest() {
startComponentManager(findTestTomcatHome(), findTestSakaiHome());
}
/**
* Configures the emulated component container to run against the default deployment environment.
*/
public static void startComponentManager() {
startComponentManager(findTomcatHome(), null);
}
public static void startComponentManager(String tomcatHome, String sakaiHome) {
if (log.isDebugEnabled()) log.debug("Starting the component manager; sakaiHome=" + sakaiHome + ", tomcatHome=" + tomcatHome);
if (isStarted()) {
if (log.isInfoEnabled()) log.info("Component manager already exists, so not starting after all");
return;
}
// Normalize file path.
char lastChar = tomcatHome.charAt(tomcatHome.length() - 1);
if ((lastChar != '/') && (lastChar != '\\')) {
tomcatHome += "/";
}
// Set the system properties needed by the sakai component manager
if ((sakaiHome != null) && (sakaiHome.length() > 0)) {
System.setProperty("sakai.home", sakaiHome);
}
System.setProperty("sakai.components.root", tomcatHome + "components/");
// Add the sakai jars to the current classpath. Note: We are limited to using the sun jvm now
URL[] sakaiUrls = getJarUrls(new String[] {tomcatHome + "common/endorsed/",
tomcatHome + "common/lib/", tomcatHome + "shared/lib/"});
URLClassLoader appClassLoader = (URLClassLoader)Thread.currentThread().getContextClassLoader();
try {
Method addMethod = URLClassLoader.class.getDeclaredMethod("addURL", new Class[] {URL.class});
addMethod.setAccessible(true);
for(int i=0; i<sakaiUrls.length; i++) {
addMethod.invoke(appClassLoader, new Object[] {sakaiUrls[i]});
}
Class<?> clazz = Class.forName("org.sakaiproject.component.cover.ComponentManager");
componentManager = clazz.getDeclaredMethod("getInstance", (Class[])null).invoke((Object[])null, (Object[])null);
} catch (Exception e) {
// Wrap as runtime exception, since it's unlikely the caller will want to do
// anything but die.
if (e instanceof RuntimeException) {
throw (RuntimeException)e;
} else {
throw new RuntimeException(e);
}
}
if (log.isDebugEnabled()) log.debug("Finished starting the component manager");
}
public static void stopComponentManager() {
if (log.isDebugEnabled()) log.debug("Starting the component manager");
if(componentManager != null) {
try {
Method closeMethod = componentManager.getClass().getMethod("close", new Class[0]);
closeMethod.invoke(componentManager, new Object[0]);
componentManager = null;
} catch (Exception e) {
log.error(e);
}
} else {
if (log.isInfoEnabled()) log.info("Component manager already stopped");
}
}
public static boolean isStarted() {
return (componentManager != null);
}
/**
* @return a Spring ApplicationContext which can be specified as the parent
* for a client-loaded application context. It is NOT guaranteed to be useful
* for any other purpose.
*/
public static Object getContainerApplicationContext() {
Object applicationContext = null;
if (componentManager != null) {
try {
Method getContextMethod = componentManager.getClass().getMethod("getApplicationContext", new Class[0]);
applicationContext = getContextMethod.invoke(componentManager, new Object[0]);
} catch (Exception e) {
log.error(e);
}
}
return applicationContext;
}
/**
* Convenience method to get a service bean from the Sakai component manager.
*
* @param beanId The id of the service
*
* @return The service, or null if the ID is not registered
*/
public static final Object getService(String beanId) {
try {
Method getMethod = componentManager.getClass().getMethod("get", new Class[] {String.class});
return getMethod.invoke(componentManager, new Object[] {beanId});
} catch (Exception e) {
log.error(e, e);
return null;
}
}
/**
* Convenience method to get a singleton service bean whose ID is the same
* as the input interface.
*
* @param clazz the interface of the singleton service
* @return the implementing service
*/
public static final <T> T getService(Class<T> clazz) {
String beanId = clazz.getName();
return (T) getService(beanId);
}
/**
* Work around Maven's inability to simply pass through selected system properties.
* By default, Surefire tests inherit no Java properties from the Maven process itself.
* Only property names explicitly set in the plug-in's "systemProperties" configuration
* will be available. So, for example, to pass the "sakai.home" property, you'd need
* something like this:
*
* <pre>
* <plugin>
* <groupId>org.apache.maven.plugins</groupId>
* <artifactId>maven-surefire-plugin</artifactId>
* <configuration>
* <systemProperties>
* <property>
* <name>sakai.home</name>
* <value>${sakai.home}</value>
* </property>
* </systemProperties>
* </configuration>
* </plugin>
* </pre>
*
* This doesn't work for optional Java properties, however. In the example above,
* if "sakai.home" is undefined, the Surefire plug-in will pass through a
* bogus string value of "${sakai.home}" rather than null. This utiltiy method
* checks for that condition.
*
* @return The Java system property value, or null if the value appears to
* be a Maven property reference itself.
*/
public static final String getPassthroughSystemProperty(String propertyName) {
String value = System.getProperty(propertyName);
if ((value != null) && value.matches("\\$\\{.+\\}")) {
value = null;
}
return value;
}
/**
* Point test.sakai.home at a directory in the test's resources area (typically
* "target/test-classes" for a Maven build) so that test-specific sakai.properties
* can be loaded.
*
* @param pathPrefix the directory holding "sakai.properties" or "sakai-configuration.xml".
* an empty string indicates that the top of the classes directory should be used
*/
public static final void setTestSakaiHome(String pathPrefix) {
URL sakaiHomeUrl = ComponentContainerEmulator.class.getClassLoader().getResource(pathPrefix);
if (log.isDebugEnabled()) log.debug("sakaiHomeUrl=" + sakaiHomeUrl);
if (sakaiHomeUrl != null) {
System.setProperty("test.sakai.home", sakaiHomeUrl.getFile());
}
}
/**
* Looks for a Tomcat home.
*
* @return the value of the Java system property "maven.tomcat.home" or of the CATALINA_HOME
* environment variable
* @throws Exception
*/
public static final String findTomcatHome() {
String tomcatHome = getPassthroughSystemProperty("maven.tomcat.home");
if ( tomcatHome != null && tomcatHome.length() > 0 ) {
if (log.isDebugEnabled()) log.debug("Using maven.tomcat.home: " + tomcatHome);
} else {
// For the sake of Eclipse, provide a non-Maven-ish approach.
tomcatHome = System.getenv("CATALINA_HOME");
if (log.isDebugEnabled()) log.debug("Using CATALINA_HOME: " + tomcatHome);
}
return tomcatHome;
}
/**
* For backwards compatibility, checks for the Java system property
* "test.tomcat.home" or environment variable "TEST_CATALINA_HOME".
* If either is set, use it. If neither is set, go ahead and use the
* standard Tomcat home logic (which is the preferred approach).
*
* @return
*/
public static final String findTestTomcatHome() {
String tomcatHome = getPassthroughSystemProperty("test.tomcat.home");
if ((tomcatHome != null) && (tomcatHome.length() > 0)) {
if (log.isDebugEnabled()) log.debug("Using test.tomcat.home: " + tomcatHome);
} else {
// For the sake of Eclipse, provide a non-Maven-ish approach.
tomcatHome = System.getenv("TEST_CATALINA_HOME");
if ((tomcatHome != null) && (tomcatHome.length() > 0)) {
if (log.isDebugEnabled()) log.debug("Using TEST_CATALINA_HOME: " + tomcatHome);
} else {
tomcatHome = findTomcatHome();
}
}
return tomcatHome;
}
/**
* @return the value of the "test.sakai.home" Java system property, the value
* of the TEST_SAKAI_HOME environment variable, or null if neither is set.
*/
public static final String findTestSakaiHome() {
String sakaiHome = getPassthroughSystemProperty("test.sakai.home"); // Can be null
if ((sakaiHome != null) && (sakaiHome.length() > 0)) {
if (log.isDebugEnabled()) log.debug("Using test.sakai.home: " + sakaiHome);
} else {
// For the sake of Eclipse, provide a non-Maven-ish approach.
sakaiHome = System.getenv("TEST_SAKAI_HOME");
if ((sakaiHome != null) && (sakaiHome.length() > 0)) {
if (log.isDebugEnabled()) log.debug("Using TEST_SAKAI_HOME: " + sakaiHome);
}
}
return sakaiHome;
}
/**
* Builds an array of file URLs from a directory path.
*
* @param dirPath
* @return
* @throws Exception
*/
private static URL[] getJarUrls(String dirPath) {
File dir = new File(dirPath);
if (log.isInfoEnabled()) log.info("dirPath=" + dirPath + ", dir=" + dir);
File[] jars = dir.listFiles(new FileFilter() {
public boolean accept(File pathname) {
if(pathname.getName().startsWith("xml-apis")) {
return false;
}
return true;
}
});
URL[] urls = new URL[jars.length];
for(int i = 0; i < jars.length; i++) {
try {
urls[i] = jars[i].toURL();
} catch (MalformedURLException e) {
log.error(e, e);
}
}
return urls;
}
private static URL[] getJarUrls(String[] dirPaths) {
List<URL> jarList = new ArrayList<URL>();
// Add all of the tomcat jars
for(int i=0; i<dirPaths.length; i++) {
jarList.addAll(Arrays.asList(getJarUrls(dirPaths[i])));
}
URL[] urlArray = new URL[jarList.size()];
jarList.toArray(urlArray);
return urlArray;
}
}