
org.unitils.core.ModulesLoader Maven / Gradle / Ivy
The newest version!
/*
* Copyright 2008, Unitils.org
* Licensed under the Apache 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.apache.org/licenses/LICENSE-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.unitils.core;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.unitils.util.PropertyUtils.getBoolean;
import static org.unitils.util.PropertyUtils.getString;
import static org.unitils.util.PropertyUtils.getStringList;
import static org.unitils.util.ReflectionUtils.createInstanceOfType;
/**
* A class for loading unitils modules.
*
* The core names set by the {@link #PROPKEY_MODULES} property which modules will be loaded. These names can then
* be used to construct properties that define the classnames and optionally the dependencies of these modules. E.g.
*
*
*
* unitils.modules= a, b, c, d
* unitils.module.a.className= org.unitils.A
* unitils.module.a.runAfter= b, c
* unitils.module.b.className= org.unitils.B
* unitils.module.b.runAfter= c
* unitils.module.c.className= org.unitils.C
* unitils.module.d.enabled= false
*
*
*
* The above configuration will load 3 core classes A, B and C and will always perform processing in
* order C, B, A.
*
* If a circular dependency is found in the runAfter configuration, a runtime exception will be thrown.
*
* @author Filip Neven
* @author Tim Ducheyne
*/
public class ModulesLoader {
/**
* Property that contains the names of the modules that are to be loaded.
*/
public static final String PROPKEY_MODULES = "unitils.modules";
/**
* First part of all core specific properties.
*/
public static final String PROPKEY_MODULE_PREFIX = "unitils.module.";
/**
* Last part of the core specific property that specifies whether the core should be loaded.
*/
public static final String PROPKEY_MODULE_SUFFIX_ENABLED = ".enabled";
/**
* Last part of the core specific property that specifies the classname of the core.
*/
public static final String PROPKEY_MODULE_SUFFIX_CLASS_NAME = ".className";
/**
* Last part of the core specific property that specifies the names of the modules that should be run before this core.
*/
public static final String PROPKEY_MODULE_SUFFIX_RUN_AFTER = ".runAfter";
/**
* The logger instance for this class.
*/
private static Logger logger = LoggerFactory.getLogger(ModulesLoader.class);
/**
* Loads all unitils modules as described in the class javadoc.
*
* @param configuration
* the configuration, not null
* @return the modules, not null
*/
public List loadModules(Properties configuration) {
// get all declared modules (filter doubles)
Set moduleNames = new TreeSet<>(getStringList(PROPKEY_MODULES, configuration));
// remove all disable modules
removeDisabledModules(moduleNames, configuration);
// get all core dependencies
Map> runAfters = new HashMap<>();
for (String moduleName : moduleNames) {
// get dependencies for core
List runAfterModuleNames = getStringList(PROPKEY_MODULE_PREFIX + moduleName + PROPKEY_MODULE_SUFFIX_RUN_AFTER, configuration);
runAfters.put(moduleName, runAfterModuleNames);
}
// Count each time a core is (indirectly) used in runAfter and order by count
Map> runAfterCounts = new TreeMap<>();
for (String moduleName : moduleNames) {
// calculate the nr of times a core is (indirectly) referenced
int count = countRunAfters(moduleName, runAfters, new HashMap<>());
// store in map with count as key and a list corresponding modules as values
List countModuleNames = runAfterCounts.get(count);
if (countModuleNames == null) {
countModuleNames = new ArrayList<>();
runAfterCounts.put(count, countModuleNames);
}
countModuleNames.add(moduleName);
}
// Create core instances in the correct sequence
List result = new ArrayList<>();
for (List moduleNameList : runAfterCounts.values()) {
List modules = createAndInitializeModules(moduleNameList, configuration);
result.addAll(modules);
}
return result;
}
/**
* Creates the modules with the given class names and calls initializes them with the given configuration.
*
* @param moduleNames
* the module class names, not null
* @param configuration
* the configuration, not null
* @return the modules, not null
*/
protected List createAndInitializeModules(List moduleNames, Properties configuration) {
List result = new ArrayList<>();
for (String moduleName : moduleNames) {
// get module class name
String className = getString(PROPKEY_MODULE_PREFIX + moduleName + PROPKEY_MODULE_SUFFIX_CLASS_NAME, configuration);
if (!classFileExistsInClasspath(className)) {
logger.debug("Skipping module " + moduleName + ". Module class not found in classpath. Module class name: " + className);
continue;
}
try {
// create module instance
Object module = createInstanceOfType(className, true);
if (!(module instanceof Module)) {
throw new UnitilsException("Unable to load core. Module class is not of type UnitilsModule: " + className);
}
// initialize module
((Module) module).init(configuration);
result.add((Module) module);
} catch (Throwable t) {
throw new UnitilsException("An exception occured during the loading of core module " + moduleName + " with module class name " + className, t);
}
}
return result;
}
/**
* Count each time a core is (indirectly) used in runAfter and order by count.
*
* This way all modules can be ordered in such a way that all core dependencies (runAfterz) are met.
* If no such order can be found (circular dependency) a runtime exception is thrown
*
* @param moduleName
* the core to count, not null
* @param allRunAfters
* all dependencies as (moduleName, run-after moduleNames) entries, not null
* @param traversedModuleNames
* all moduleNames that were already counted as (moduleName, moduleName) entries, not null
* @return the count
* @throws RuntimeException
* if an infinite loop (circular dependency) is found
*/
private int countRunAfters(String moduleName, Map> allRunAfters, Map traversedModuleNames) {
// Check for infinite loops
if (traversedModuleNames.containsKey(moduleName)) {
throw new UnitilsException("Unable to load modules. Circular dependency found for modules: " + traversedModuleNames.keySet());
}
traversedModuleNames.put(moduleName, moduleName);
int count = 1;
List runAfters = allRunAfters.get(moduleName);
if (runAfters != null) {
for (String currentModuleName : runAfters) {
// recursively count all dependencies
count += countRunAfters(currentModuleName, allRunAfters, traversedModuleNames);
}
}
traversedModuleNames.remove(moduleName);
return count;
}
/**
* Removes all modules that have a value false for the enabled property.
*
* @param moduleNames
* the module names, not null
* @param configuration
* the configuration, not null
*/
protected void removeDisabledModules(Set moduleNames, Properties configuration) {
Iterator moduleNameIterator = moduleNames.iterator();
while (moduleNameIterator.hasNext()) {
String moduleName = moduleNameIterator.next();
boolean enabled = getBoolean(PROPKEY_MODULE_PREFIX + moduleName + PROPKEY_MODULE_SUFFIX_ENABLED, true, configuration);
if (!enabled) {
moduleNameIterator.remove();
}
}
}
/**
* @param className
* The name of the class to check, not null
* @return True if the classfile exists in the classpath
*/
protected boolean classFileExistsInClasspath(String className) {
String classFileName = className.replace('.', '/') + ".class";
return getClass().getClassLoader().getResource(classFileName) != null;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy