001: /* ========================================================================
002: * JCommon : a free general purpose class library for the Java(tm) platform
003: * ========================================================================
004: *
005: * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
006: *
007: * Project Info: http://www.jfree.org/jcommon/index.html
008: *
009: * This library is free software; you can redistribute it and/or modify it
010: * under the terms of the GNU Lesser General Public License as published by
011: * the Free Software Foundation; either version 2.1 of the License, or
012: * (at your option) any later version.
013: *
014: * This library is distributed in the hope that it will be useful, but
015: * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
016: * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
017: * License for more details.
018: *
019: * You should have received a copy of the GNU Lesser General Public
020: * License along with this library; if not, write to the Free Software
021: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
022: * USA.
023: *
024: * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
025: * in the United States and other countries.]
026: *
027: * -------------------
028: * PackageManager.java
029: * -------------------
030: * (C)opyright 2003, 2004, by Thomas Morgner and Contributors.
031: *
032: * Original Author: Thomas Morgner;
033: * Contributor(s): David Gilbert (for Object Refinery Limited);
034: *
035: * $Id: PackageManager.java,v 1.10 2006/11/02 13:10:35 taqua Exp $
036: *
037: * Changes
038: * -------
039: * 26-Jun-2003 : Initial version
040: * 07-Jun-2004 : Added JCommon header (DG);
041: *
042: */
043:
044: package org.jfree.base.modules;
045:
046: import java.io.PrintStream;
047: import java.util.ArrayList;
048: import java.util.Arrays;
049: import java.util.HashMap;
050: import java.util.Iterator;
051:
052: import org.jfree.base.AbstractBoot;
053: import org.jfree.base.config.HierarchicalConfiguration;
054: import org.jfree.base.config.PropertyFileConfiguration;
055: import org.jfree.base.log.PadMessage;
056: import org.jfree.util.Configuration;
057: import org.jfree.util.Log;
058: import org.jfree.util.ObjectUtilities;
059:
060: /**
061: * The PackageManager is used to load and configure the modules of JFreeReport.
062: * Modules are used to extend the basic capabilities of JFreeReport by providing
063: * a simple plugin-interface.
064: * <p/>
065: * Modules provide a simple capability to remove unneeded functionality from the
066: * JFreeReport system and to reduce the overall code size. The modularisation provides
067: * a very strict way of removing unnecessary dependencies beween the various packages.
068: * <p/>
069: * The package manager can be used to add new modules to the system or to check
070: * the existence and state of installed modules.
071: *
072: * @author Thomas Morgner
073: */
074: public final class PackageManager {
075: /**
076: * The PackageConfiguration handles the module level configuration.
077: *
078: * @author Thomas Morgner
079: */
080: public static class PackageConfiguration extends
081: PropertyFileConfiguration {
082: /**
083: * DefaultConstructor. Creates a new package configuration.
084: */
085: public PackageConfiguration() {
086: // nothing required
087: }
088:
089: /**
090: * The new configuartion will be inserted into the list of report configuration,
091: * so that this configuration has the given report configuration instance as parent.
092: *
093: * @param config the new report configuration.
094: */
095: public void insertConfiguration(
096: final HierarchicalConfiguration config) {
097: super .insertConfiguration(config);
098: }
099: }
100:
101: /**
102: * An internal constant declaring that the specified module was already loaded.
103: */
104: private static final int RETURN_MODULE_LOADED = 0;
105: /**
106: * An internal constant declaring that the specified module is not known.
107: */
108: private static final int RETURN_MODULE_UNKNOWN = 1;
109: /**
110: * An internal constant declaring that the specified module produced an error while loading.
111: */
112: private static final int RETURN_MODULE_ERROR = 2;
113:
114: /**
115: * The module configuration instance that should be used to store module
116: * properties. This separates the user defined properties from the implementation
117: * defined properties.
118: */
119: private final PackageConfiguration packageConfiguration;
120:
121: /**
122: * A list of all defined modules.
123: */
124: private final ArrayList modules;
125: /**
126: * A list of module name definitions.
127: */
128: private final ArrayList initSections;
129:
130: /** The boot implementation for which the modules are managed. */
131: private AbstractBoot booter;
132:
133: /** The instances of all modules for all booters. */
134: private static HashMap instances;
135:
136: /**
137: * Creates a package manager instance.
138: *
139: * @param booter the booter.
140: * @return A package manager.
141: */
142: public static PackageManager createInstance(
143: final AbstractBoot booter) {
144: PackageManager manager;
145: if (instances == null) {
146: instances = new HashMap();
147: manager = new PackageManager(booter);
148: instances.put(booter, manager);
149: return manager;
150: }
151: manager = (PackageManager) instances.get(booter);
152: if (manager == null) {
153: manager = new PackageManager(booter);
154: instances.put(booter, manager);
155: }
156: return manager;
157: }
158:
159: /**
160: * Creates a new package manager.
161: *
162: * @param booter the booter (<code>null</code> not permitted).
163: */
164: private PackageManager(final AbstractBoot booter) {
165: if (booter == null) {
166: throw new NullPointerException();
167: }
168: this .booter = booter;
169: this .packageConfiguration = new PackageConfiguration();
170: this .modules = new ArrayList();
171: this .initSections = new ArrayList();
172: }
173:
174: /**
175: * Checks, whether a certain module is available.
176: *
177: * @param moduleDescription the module description of the desired module.
178: * @return true, if the module is available and the version of the module
179: * is compatible, false otherwise.
180: */
181: public boolean isModuleAvailable(final ModuleInfo moduleDescription) {
182: final PackageState[] packageStates = (PackageState[]) this .modules
183: .toArray(new PackageState[this .modules.size()]);
184: for (int i = 0; i < packageStates.length; i++) {
185: final PackageState state = packageStates[i];
186: if (state.getModule().getModuleClass().equals(
187: moduleDescription.getModuleClass())) {
188: return (state.getState() == PackageState.STATE_INITIALIZED);
189: }
190: }
191: return false;
192: }
193:
194: /**
195: * Loads all modules mentioned in the report configuration starting with
196: * the given prefix. This method is used during the boot process of
197: * JFreeReport. You should never need to call this method directly.
198: *
199: * @param modulePrefix the module prefix.
200: */
201: public void load(final String modulePrefix) {
202: if (this .initSections.contains(modulePrefix)) {
203: return;
204: }
205: this .initSections.add(modulePrefix);
206:
207: final Configuration config = this .booter.getGlobalConfig();
208: final Iterator it = config.findPropertyKeys(modulePrefix);
209: int count = 0;
210: while (it.hasNext()) {
211: final String key = (String) it.next();
212: if (key.endsWith(".Module")) {
213: final String moduleClass = config
214: .getConfigProperty(key);
215: if (moduleClass != null && moduleClass.length() > 0) {
216: addModule(moduleClass);
217: count++;
218: }
219: }
220: }
221: Log.debug("Loaded a total of " + count
222: + " modules under prefix: " + modulePrefix);
223: }
224:
225: /**
226: * Initializes all previously uninitialized modules. Once a module is initialized,
227: * it is not re-initialized a second time.
228: */
229: public synchronized void initializeModules() {
230: // sort by subsystems and dependency
231: PackageSorter.sort(this .modules);
232:
233: for (int i = 0; i < this .modules.size(); i++) {
234: final PackageState mod = (PackageState) this .modules.get(i);
235: if (mod.configure(this .booter)) {
236: Log.debug(new Log.SimpleMessage("Conf: ",
237: new PadMessage(
238: mod.getModule().getModuleClass(), 70),
239: " [", mod.getModule().getSubSystem(), "]"));
240: }
241: }
242:
243: for (int i = 0; i < this .modules.size(); i++) {
244: final PackageState mod = (PackageState) this .modules.get(i);
245: if (mod.initialize(this .booter)) {
246: Log.debug(new Log.SimpleMessage("Init: ",
247: new PadMessage(
248: mod.getModule().getModuleClass(), 70),
249: " [", mod.getModule().getSubSystem(), "]"));
250: }
251: }
252: }
253:
254: /**
255: * Adds a module to the package manager.
256: * Once all modules are added, you have to call initializeModules()
257: * to configure and initialize the new modules.
258: *
259: * @param modClass the module class
260: */
261: public synchronized void addModule(final String modClass) {
262: final ArrayList loadModules = new ArrayList();
263: final ModuleInfo modInfo = new DefaultModuleInfo(modClass,
264: null, null, null);
265: if (loadModule(modInfo, new ArrayList(), loadModules, false)) {
266: for (int i = 0; i < loadModules.size(); i++) {
267: final Module mod = (Module) loadModules.get(i);
268: this .modules.add(new PackageState(mod));
269: }
270: }
271: }
272:
273: /**
274: * Checks, whether the given module is already loaded in either the given
275: * tempModules list or the global package registry. If tmpModules is null,
276: * only the previously installed modules are checked.
277: *
278: * @param tempModules a list of previously loaded modules.
279: * @param module the module specification that is checked.
280: * @return true, if the module is already loaded, false otherwise.
281: */
282: private int containsModule(final ArrayList tempModules,
283: final ModuleInfo module) {
284: if (tempModules != null) {
285: final ModuleInfo[] mods = (ModuleInfo[]) tempModules
286: .toArray(new ModuleInfo[tempModules.size()]);
287: for (int i = 0; i < mods.length; i++) {
288: if (mods[i].getModuleClass().equals(
289: module.getModuleClass())) {
290: return RETURN_MODULE_LOADED;
291: }
292: }
293: }
294:
295: final PackageState[] packageStates = (PackageState[]) this .modules
296: .toArray(new PackageState[this .modules.size()]);
297: for (int i = 0; i < packageStates.length; i++) {
298: if (packageStates[i].getModule().getModuleClass().equals(
299: module.getModuleClass())) {
300: if (packageStates[i].getState() == PackageState.STATE_ERROR) {
301: return RETURN_MODULE_ERROR;
302: } else {
303: return RETURN_MODULE_LOADED;
304: }
305: }
306: }
307: return RETURN_MODULE_UNKNOWN;
308: }
309:
310: /**
311: * A utility method that collects all failed modules. Such an module caused
312: * an error while being loaded, and is now cached in case it is referenced
313: * elsewhere.
314: *
315: * @param state the failed module.
316: */
317: private void dropFailedModule(final PackageState state) {
318: if (this .modules.contains(state) == false) {
319: this .modules.add(state);
320: }
321: }
322:
323: /**
324: * Tries to load a given module and all dependent modules. If the dependency check
325: * fails for that module (or for one of the dependent modules), the loaded modules
326: * are discarded and no action is taken.
327: *
328: * @param moduleInfo the module info of the module that should be loaded.
329: * @param incompleteModules a list of incompletly loaded modules. This are module
330: * specifications which depend on the current module and wait for the module to
331: * be completly loaded.
332: * @param modules the list of previously loaded modules for this module.
333: * @param fatal a flag that states, whether the failure of loading a module should
334: * be considered an error. Root-modules load errors are never fatal, as we try
335: * to load all known modules, regardless whether they are active or not.
336: * @return true, if the module was loaded successfully, false otherwise.
337: */
338: private boolean loadModule(final ModuleInfo moduleInfo,
339: final ArrayList incompleteModules, final ArrayList modules,
340: final boolean fatal) {
341: try {
342:
343: final Class c = ObjectUtilities.getClassLoader(getClass())
344: .loadClass(moduleInfo.getModuleClass());
345: final Module module = (Module) c.newInstance();
346:
347: if (acceptVersion(moduleInfo, module) == false) {
348: // module conflict!
349: Log.warn("Module " + module.getName()
350: + ": required version: " + moduleInfo
351: + ", but found Version: \n" + module);
352: final PackageState state = new PackageState(module,
353: PackageState.STATE_ERROR);
354: dropFailedModule(state);
355: return false;
356: }
357:
358: final int moduleContained = containsModule(modules, module);
359: if (moduleContained == RETURN_MODULE_ERROR) {
360: // the module caused harm before ...
361: Log.debug("Indicated failure for module: "
362: + module.getModuleClass());
363: final PackageState state = new PackageState(module,
364: PackageState.STATE_ERROR);
365: dropFailedModule(state);
366: return false;
367: } else if (moduleContained == RETURN_MODULE_UNKNOWN) {
368: if (incompleteModules.contains(module)) {
369: // we assume that loading will continue ...
370: Log
371: .error(new Log.SimpleMessage(
372: "Circular module reference: This module definition is invalid: ",
373: module.getClass()));
374: final PackageState state = new PackageState(module,
375: PackageState.STATE_ERROR);
376: dropFailedModule(state);
377: return false;
378: }
379: incompleteModules.add(module);
380: final ModuleInfo[] required = module
381: .getRequiredModules();
382: for (int i = 0; i < required.length; i++) {
383: if (loadModule(required[i], incompleteModules,
384: modules, true) == false) {
385: Log.debug("Indicated failure for module: "
386: + module.getModuleClass());
387: final PackageState state = new PackageState(
388: module, PackageState.STATE_ERROR);
389: dropFailedModule(state);
390: return false;
391: }
392: }
393:
394: final ModuleInfo[] optional = module
395: .getOptionalModules();
396: for (int i = 0; i < optional.length; i++) {
397: if (loadModule(optional[i], incompleteModules,
398: modules, true) == false) {
399: Log.debug(new Log.SimpleMessage(
400: "Optional module: ", optional[i]
401: .getModuleClass(),
402: " was not loaded."));
403: }
404: }
405: // maybe a dependent module defined the same base module ...
406: if (containsModule(modules, module) == RETURN_MODULE_UNKNOWN) {
407: modules.add(module);
408: }
409: incompleteModules.remove(module);
410: }
411: return true;
412: } catch (ClassNotFoundException cnfe) {
413: if (fatal) {
414: Log.warn(new Log.SimpleMessage(
415: "Unresolved dependency for package: ",
416: moduleInfo.getModuleClass()));
417: }
418: Log.debug(new Log.SimpleMessage("ClassNotFound: ", cnfe
419: .getMessage()));
420: return false;
421: } catch (Exception e) {
422: Log.warn(new Log.SimpleMessage(
423: "Exception while loading module: ", moduleInfo), e);
424: return false;
425: }
426: }
427:
428: /**
429: * Checks, whether the given module meets the requirements defined in the module
430: * information.
431: *
432: * @param moduleRequirement the required module specification.
433: * @param module the module that should be checked against the specification.
434: * @return true, if the module meets the given specifications, false otherwise.
435: */
436: private boolean acceptVersion(final ModuleInfo moduleRequirement,
437: final Module module) {
438: if (moduleRequirement.getMajorVersion() == null) {
439: return true;
440: }
441: if (module.getMajorVersion() == null) {
442: Log.warn("Module " + module.getName()
443: + " does not define a major version.");
444: } else {
445: final int compare = acceptVersion(moduleRequirement
446: .getMajorVersion(), module.getMajorVersion());
447: if (compare > 0) {
448: return false;
449: } else if (compare < 0) {
450: return true;
451: }
452: }
453:
454: if (moduleRequirement.getMinorVersion() == null) {
455: return true;
456: }
457: if (module.getMinorVersion() == null) {
458: Log.warn("Module " + module.getName()
459: + " does not define a minor version.");
460: } else {
461: final int compare = acceptVersion(moduleRequirement
462: .getMinorVersion(), module.getMinorVersion());
463: if (compare > 0) {
464: return false;
465: } else if (compare < 0) {
466: return true;
467: }
468: }
469:
470: if (moduleRequirement.getPatchLevel() == null) {
471: return true;
472: }
473: if (module.getPatchLevel() == null) {
474: Log.debug("Module " + module.getName()
475: + " does not define a patch level.");
476: } else {
477: if (acceptVersion(moduleRequirement.getPatchLevel(), module
478: .getPatchLevel()) > 0) {
479: Log.debug("Did not accept patchlevel: "
480: + moduleRequirement.getPatchLevel() + " - "
481: + module.getPatchLevel());
482: return false;
483: }
484: }
485: return true;
486:
487: }
488:
489: /**
490: * Compare the version strings. If the strings have a different length,
491: * the shorter string is padded with spaces to make them compareable.
492: *
493: * @param modVer the version string of the module
494: * @param depModVer the version string of the dependent or optional module
495: * @return 0, if the dependent module version is equal tothe module's required
496: * version, a negative number if the dependent module is newer or a positive
497: * number if the dependent module is older and does not fit.
498: */
499: private int acceptVersion(final String modVer,
500: final String depModVer) {
501: final int mLength = Math.max(modVer.length(), depModVer
502: .length());
503: final char[] modVerArray;
504: final char[] depVerArray;
505: if (modVer.length() > depModVer.length()) {
506: modVerArray = modVer.toCharArray();
507: depVerArray = new char[mLength];
508: final int delta = modVer.length() - depModVer.length();
509: Arrays.fill(depVerArray, 0, delta, ' ');
510: System.arraycopy(depVerArray, delta, depModVer
511: .toCharArray(), 0, depModVer.length());
512: } else if (modVer.length() < depModVer.length()) {
513: depVerArray = depModVer.toCharArray();
514: modVerArray = new char[mLength];
515: final char[] b1 = new char[mLength];
516: final int delta = depModVer.length() - modVer.length();
517: Arrays.fill(b1, 0, delta, ' ');
518: System.arraycopy(b1, delta, modVer.toCharArray(), 0, modVer
519: .length());
520: } else {
521: depVerArray = depModVer.toCharArray();
522: modVerArray = modVer.toCharArray();
523: }
524: return new String(modVerArray)
525: .compareTo(new String(depVerArray));
526: }
527:
528: /**
529: * Returns the default package configuration. Private report configuration
530: * instances may be inserted here. These inserted configuration can never override
531: * the settings from this package configuration.
532: *
533: * @return the package configuration.
534: */
535: public PackageConfiguration getPackageConfiguration() {
536: return this .packageConfiguration;
537: }
538:
539: /**
540: * Returns an array of the currently active modules. The module definition
541: * returned contain all known modules, including buggy and unconfigured
542: * instances.
543: *
544: * @return the modules.
545: */
546: public Module[] getAllModules() {
547: final Module[] mods = new Module[this .modules.size()];
548: for (int i = 0; i < this .modules.size(); i++) {
549: final PackageState state = (PackageState) this .modules
550: .get(i);
551: mods[i] = state.getModule();
552: }
553: return mods;
554: }
555:
556: /**
557: * Returns all active modules. This array does only contain modules
558: * which were successfully configured and initialized.
559: *
560: * @return the list of all active modules.
561: */
562: public Module[] getActiveModules() {
563: final ArrayList mods = new ArrayList();
564: for (int i = 0; i < this .modules.size(); i++) {
565: final PackageState state = (PackageState) this .modules
566: .get(i);
567: if (state.getState() == PackageState.STATE_INITIALIZED) {
568: mods.add(state.getModule());
569: }
570: }
571: return (Module[]) mods.toArray(new Module[mods.size()]);
572: }
573:
574: /**
575: * Prints the modules that are used.
576: *
577: * @param p the print stream.
578: */
579: public void printUsedModules(final PrintStream p) {
580: final Module[] allMods = getAllModules();
581: final ArrayList activeModules = new ArrayList();
582: final ArrayList failedModules = new ArrayList();
583:
584: for (int i = 0; i < allMods.length; i++) {
585: if (isModuleAvailable(allMods[i])) {
586: activeModules.add(allMods[i]);
587: } else {
588: failedModules.add(allMods[i]);
589: }
590: }
591:
592: p.print("Active modules: ");
593: p.println(activeModules.size());
594: p
595: .println("----------------------------------------------------------");
596: for (int i = 0; i < activeModules.size(); i++) {
597: final Module mod = (Module) activeModules.get(i);
598: p.print(new PadMessage(mod.getModuleClass(), 70));
599: p.print(" [");
600: p.print(mod.getSubSystem());
601: p.println("]");
602: p.print(" Version: ");
603: p.print(mod.getMajorVersion());
604: p.print("-");
605: p.print(mod.getMinorVersion());
606: p.print("-");
607: p.print(mod.getPatchLevel());
608: p.print(" Producer: ");
609: p.println(mod.getProducer());
610: p.print(" Description: ");
611: p.println(mod.getDescription());
612: }
613: }
614: }
|