001: /*
002: * Copyright 2006-2007, Unitils.org
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package org.unitils.core;
017:
018: import org.apache.commons.logging.Log;
019: import org.apache.commons.logging.LogFactory;
020: import static org.unitils.util.PropertyUtils.*;
021: import static org.unitils.util.ReflectionUtils.createInstanceOfType;
022:
023: import java.util.*;
024:
025: /**
026: * A class for loading unitils modules.
027: * <p/>
028: * The core names set by the {@link #PROPKEY_MODULES} property which modules will be loaded. These names can then
029: * be used to construct properties that define the classnames and optionally the dependencies of these modules. E.g.
030: * <pre><code>
031: * unitils.modules= a, b, c, d
032: * unitils.module.a.className= org.unitils.A
033: * unitils.module.a.runAfter= b, c
034: * unitils.module.b.className= org.unitils.B
035: * unitils.module.b.runAfter= c
036: * unitils.module.c.className= org.unitils.C
037: * unitils.module.d.enabled= false
038: * </code></pre>
039: * The above configuration will load 3 core classes A, B and C and will always perform processing in
040: * order C, B, A.
041: * <p/>
042: * If a circular dependency is found in the runAfter configuration, a runtime exception will be thrown.
043: *
044: * @author Filip Neven
045: * @author Tim Ducheyne
046: */
047: public class ModulesLoader {
048:
049: /**
050: * Property that contains the names of the modules that are to be loaded
051: */
052: public static final String PROPKEY_MODULES = "unitils.modules";
053:
054: /**
055: * First part of all core specific properties
056: */
057: public static final String PROPKEY_MODULE_PREFIX = "unitils.module.";
058:
059: /**
060: * Last part of the core specific property that specifies whehter the core should be loaded
061: */
062: public static final String PROPKEY_MODULE_SUFFIX_ENABLED = ".enabled";
063:
064: /**
065: * Last part of the core specific property that specifies the classname of the core
066: */
067: public static final String PROPKEY_MODULE_SUFFIX_CLASS_NAME = ".className";
068:
069: /**
070: * Last part of the core specific property that specifies the names of the modules that should be run before this core
071: */
072: public static final String PROPKEY_MODULE_SUFFIX_RUN_AFTER = ".runAfter";
073:
074: /* The logger instance for this class */
075: private static Log logger = LogFactory.getLog(ModulesLoader.class);
076:
077: /**
078: * Loads all unitils modules as described in the class javadoc.
079: *
080: * @param configuration the configuration, not null
081: * @return the modules, not null
082: */
083: public List<Module> loadModules(Properties configuration) {
084: // get all declared modules (filter doubles)
085: Set<String> moduleNames = new TreeSet<String>(getStringList(
086: PROPKEY_MODULES, configuration));
087:
088: // remove all disable modules
089: removeDisabledModules(moduleNames, configuration);
090:
091: // get all core dependencies
092: Map<String, List<String>> runAfters = new HashMap<String, List<String>>();
093: for (String moduleName : moduleNames) {
094:
095: // get dependencies for core
096: List<String> runAfterModuleNames = getStringList(
097: PROPKEY_MODULE_PREFIX + moduleName
098: + PROPKEY_MODULE_SUFFIX_RUN_AFTER,
099: configuration);
100: runAfters.put(moduleName, runAfterModuleNames);
101: }
102:
103: // Count each time a core is (indirectly) used in runAfter and order by count
104: Map<Integer, List<String>> runAfterCounts = new TreeMap<Integer, List<String>>();
105: for (String moduleName : moduleNames) {
106:
107: // calculate the nr of times a core is (indirectly) referenced
108: int count = countRunAfters(moduleName, runAfters,
109: new HashMap<String, String>());
110:
111: // store in map with count as key and a list corresponding modules as values
112: List<String> countModuleNames = runAfterCounts.get(count);
113: if (countModuleNames == null) {
114: countModuleNames = new ArrayList<String>();
115: runAfterCounts.put(count, countModuleNames);
116: }
117: countModuleNames.add(moduleName);
118: }
119:
120: // Create core instances in the correct sequence
121: List<Module> result = new ArrayList<Module>();
122: for (List<String> moduleNameList : runAfterCounts.values()) {
123: List<Module> modules = createAndInitializeModules(
124: moduleNameList, configuration);
125: result.addAll(modules);
126: }
127: return result;
128: }
129:
130: /**
131: * Creates the modules with the given class names and calls initializes them with the given configuration.
132: *
133: * @param moduleNameList the module class names, not null
134: * @param configuration the configuration, not null
135: * @return the modules, not null
136: */
137: protected List<Module> createAndInitializeModules(
138: List<String> moduleNameList, Properties configuration) {
139: List<Module> result = new ArrayList<Module>();
140: for (String moduleName : moduleNameList) {
141: // get core class name
142: String className = getString(PROPKEY_MODULE_PREFIX
143: + moduleName + PROPKEY_MODULE_SUFFIX_CLASS_NAME,
144: configuration);
145: try {
146: // create core instance
147: Object module = createInstanceOfType(className, true);
148: if (!(module instanceof Module)) {
149: throw new UnitilsException(
150: "Unable to load core. Module class is not of type UnitilsModule: "
151: + className);
152: }
153: // run initializer
154: ((Module) module).init(configuration);
155: result.add((Module) module);
156:
157: } catch (UnitilsException e) {
158:
159: if (e.getCause() instanceof ClassNotFoundException
160: || e.getCause() instanceof NoClassDefFoundError) {
161: // Class not found, maybe this is caused by a library that is not in the classpath
162: // Log warning and ingore exception
163: logger
164: .warn("Unable to create module instance for module class: "
165: + className
166: + ". The module will "
167: + "not be loaded. If this is caused by a library that is not used by your project and thus not "
168: + "in the classpath, this warning can be avoided by explicitly disabling the module.");
169: logger
170: .debug(
171: "Ignored exception during module initialisation.re",
172: e);
173: continue;
174: }
175: throw e;
176: }
177: }
178: return result;
179: }
180:
181: /**
182: * Count each time a core is (indirectly) used in runAfter and order by count.
183: * <p/>
184: * This way all modules can be ordered in such a way that all core dependencies (runAfterz) are met.
185: * If no such order can be found (circular dependency) a runtime exception is thrown
186: *
187: * @param moduleName the core to count, not null
188: * @param allRunAfters all dependencies as (moduleName, run-after moduleNames) entries, not null
189: * @param traversedModuleNames all moduleNames that were already counted as (moduleName, moduleName) entries, not null
190: * @return the count
191: * @throws RuntimeException if an infinite loop (circular dependency) is found
192: */
193: private int countRunAfters(String moduleName,
194: Map<String, List<String>> allRunAfters,
195: Map<String, String> traversedModuleNames) {
196: // Check for infinite loops
197: if (traversedModuleNames.containsKey(moduleName)) {
198: throw new UnitilsException(
199: "Unable to load modules. Circular dependency found for modules: "
200: + traversedModuleNames.keySet());
201: }
202: traversedModuleNames.put(moduleName, moduleName);
203:
204: int count = 1;
205: List<String> runAfters = allRunAfters.get(moduleName);
206: if (runAfters != null) {
207: for (String currentModuleName : runAfters) {
208: // recursively count all dependencies
209: count += countRunAfters(currentModuleName,
210: allRunAfters, traversedModuleNames);
211: }
212: }
213:
214: traversedModuleNames.remove(moduleName);
215: return count;
216: }
217:
218: /**
219: * Removes all modules that have a value false for the enabled property.
220: *
221: * @param moduleNames the module names, not null
222: * @param configuration the configuration, not null
223: */
224: protected void removeDisabledModules(Set<String> moduleNames,
225: Properties configuration) {
226: Iterator<String> moduleNameIterator = moduleNames.iterator();
227: while (moduleNameIterator.hasNext()) {
228:
229: String moduleName = moduleNameIterator.next();
230: boolean enabled = getBoolean(PROPKEY_MODULE_PREFIX
231: + moduleName + PROPKEY_MODULE_SUFFIX_ENABLED, true,
232: configuration);
233: if (!enabled) {
234: moduleNameIterator.remove();
235: }
236: }
237: }
238: }
|