/**********************************************************************************
* $URL: https://source.sakaiproject.org/svn/kernel/trunk/component-manager/src/main/java/org/sakaiproject/util/ComponentsLoader.java $
* $Id: ComponentsLoader.java 105077 2012-02-24 22:54:29Z ottenhoff@longsight.com $
***********************************************************************************
*
* Copyright (c) 2005, 2006, 2007, 2008 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.util;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Vector;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
/**
* <p>
* Load the available Sakai components into the shared component manager's Spring ApplicationContext
* </p>
*/
public class ComponentsLoader
{
/** Our logger */
private static Log M_log = LogFactory.getLog(ComponentsLoader.class);
public ComponentsLoader()
{
}
/**
*
*/
public void load(ConfigurableApplicationContext ac, String componentsRoot)
{
try
{
// get a list of the folders in the root
File root = new File(componentsRoot);
// make sure it's a dir.
if (!root.isDirectory())
{
M_log.warn("load: root not directory: " + componentsRoot);
return;
}
// what component packages are there?
File[] packageArray = root.listFiles();
if (packageArray == null)
{
M_log.warn("load: empty directory: " + componentsRoot);
return;
}
List<File> packages = new ArrayList<File>(Arrays.asList(packageArray));
// for testing, we might reverse load order
final int reverse = System.getProperty("sakai.components.reverse.load") != null ? -1 : 1;
// assure a consistent order - sort these files
Collections.sort(packages, new Comparator<Object>() {
public int compare(Object o1, Object o2)
{
File f1 = (File) o1;
File f2 = (File) o2;
int sort = f1.compareTo(f2);
return sort * reverse;
}
});
M_log.info("load: loading components from: " + componentsRoot);
// process the packages
for (File packageDir : packages)
{
// if a valid components directory
if (validComponentsPackage(packageDir))
{
loadComponentPackage(packageDir, ac);
}
else
{
M_log.warn("load: skipping non-package entry: " + packageDir);
}
}
}
catch (Exception e) {
M_log.warn("load: exception: " + e, e);
}
}
/**
* Load one component package into the AC
*
* @param packageRoot
* The file path to the component package
* @param ac
* The ApplicationContext to load into
*/
protected void loadComponentPackage(File dir, ConfigurableApplicationContext ac)
{
// setup the classloader onto the thread
ClassLoader current = Thread.currentThread().getContextClassLoader();
ClassLoader loader = newPackageClassLoader(dir);
M_log.info("loadComponentPackage: " + dir);
Thread.currentThread().setContextClassLoader(loader);
File xml = null;
try
{
// load this xml file
File webinf = new File(dir, "WEB-INF");
xml = new File(webinf, "components.xml");
// make a reader
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader((BeanDefinitionRegistry) ac.getBeanFactory());
// In Spring 2, classes aren't loaded during bean parsing unless this
// classloader property is set.
reader.setBeanClassLoader(loader);
List<Resource> beanDefList = new ArrayList<Resource>();
beanDefList.add(new FileSystemResource(xml.getCanonicalPath()));
// Load the demo components, if necessary
File demoXml = new File(webinf, "components-demo.xml");
if("true".equalsIgnoreCase(System.getProperty("sakai.demo")))
{
if(M_log.isDebugEnabled()) M_log.debug("Attempting to load demo components");
if(demoXml.exists())
{
if(M_log.isInfoEnabled()) M_log.info("Loading demo components from " + dir);
beanDefList.add(new FileSystemResource(demoXml.getCanonicalPath()));
}
}
else
{
if(demoXml.exists())
{
// Only log that we're skipping the demo components if they exist
if(M_log.isInfoEnabled()) M_log.info("Skipping demo components from " + dir);
}
}
reader.loadBeanDefinitions(beanDefList.toArray(new Resource[0]));
}
catch (Exception e)
{
M_log.warn("loadComponentPackage: exception loading: " + xml + " : " + e, e);
}
finally
{
// restore the context loader
Thread.currentThread().setContextClassLoader(current);
}
}
/**
* Test if this File is a valid components package directory.
*
* @param dir
* The file to test
* @return true if it is a valid components package directory, false if not.
*/
protected boolean validComponentsPackage(File dir)
{
// valid if this is a directory with a WEB-INF directory below with a components.xml file
if ((dir != null) && (dir.isDirectory()))
{
File webinf = new File(dir, "WEB-INF");
if ((webinf != null) && (webinf.isDirectory()))
{
File xml = new File(webinf, "components.xml");
if ((xml != null) && (xml.isFile()))
{
return true;
}
}
}
return false;
}
/**
* Create the class loader for this component package
*
* @param dir
* The package's root directory.
* @return A class loader, whose parent is this class's loader, which has the classes/ and jars for this component.
*/
protected ClassLoader newPackageClassLoader(File dir)
{
// collect as a List, turn into an array after
List urls = new Vector();
File webinf = new File(dir, "WEB-INF");
// put classes/ on the classpath
File classes = new File(webinf, "classes");
if ((classes != null) && (classes.isDirectory()))
{
try {
URL url = new URL("file:" + classes.getCanonicalPath() + "/");
urls.add(url);
} catch (Exception e) {
M_log.warn("Bad url for classes: "+classes.getPath()+" : "+e);
}
}
// put each .jar file onto the classpath
File lib = new File(webinf, "lib");
if ((lib != null) && (lib.isDirectory()))
{
File[] jars = lib.listFiles(new FileFilter()
{
public boolean accept(File file)
{
return (file.isFile() && file.getName().endsWith(".jar"));
}
});
if (jars != null)
{
for (int j = 0; j < jars.length; j++)
{
if (jars[j] != null) {
try {
URL url = new URL("file:" + jars[j].getCanonicalPath());
urls.add(url);
} catch (Exception e) {
M_log.warn("Bad url for jar: "+jars[j].getPath()+" : "+e);
}
}
}
}
}
// make the array from the list
URL[] urlArray = (URL[]) urls.toArray(new URL[urls.size()]);
ClassLoader loader = null;
// Check to see if Terracotta clustering is turned on
// String clusterTerracotta = ServerConfigurationService.getString("cluster.terracotta","false");
String clusterTerracotta = System.getProperty("sakai.cluster.terracotta");
if ("true".equals(clusterTerracotta)) {
// If Terracotta clustering is turned on then use the Special Terracotta Class loader
loader = new TerracottaClassLoader(urlArray, getClass().getClassLoader(), dir.getName());
} else {
// Terracotta clustering is turned off, so use the normal URLClassLoader
loader = new URLClassLoader(urlArray, getClass().getClassLoader());
}
return loader;
}
}