All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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