PackageManager.java 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586
  1. /* ========================================================================
  2. * JCommon : a free general purpose class library for the Java(tm) platform
  3. * ========================================================================
  4. *
  5. * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
  6. *
  7. * Project Info: http://www.jfree.org/jcommon/index.html
  8. *
  9. * This library is free software; you can redistribute it and/or modify it
  10. * under the terms of the GNU Lesser General Public License as published by
  11. * the Free Software Foundation; either version 2.1 of the License, or
  12. * (at your option) any later version.
  13. *
  14. * This library is distributed in the hope that it will be useful, but
  15. * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  16. * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
  17. * License for more details.
  18. *
  19. * You should have received a copy of the GNU Lesser General Public
  20. * License along with this library; if not, write to the Free Software
  21. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
  22. * USA.
  23. *
  24. * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
  25. * in the United States and other countries.]
  26. *
  27. * -------------------
  28. * PackageManager.java
  29. * -------------------
  30. * (C)opyright 2003, 2004, by Thomas Morgner and Contributors.
  31. *
  32. * Original Author: Thomas Morgner;
  33. * Contributor(s): David Gilbert (for Object Refinery Limited);
  34. *
  35. * $Id: PackageManager.java,v 1.10 2006/11/02 13:10:35 taqua Exp $
  36. *
  37. * Changes
  38. * -------
  39. * 26-Jun-2003 : Initial version
  40. * 07-Jun-2004 : Added JCommon header (DG);
  41. *
  42. */
  43. package org.jfree.base.modules;
  44. import java.io.PrintStream;
  45. import java.util.ArrayList;
  46. import java.util.Arrays;
  47. import java.util.HashMap;
  48. import java.util.Iterator;
  49. import org.jfree.base.AbstractBoot;
  50. import org.jfree.base.config.HierarchicalConfiguration;
  51. import org.jfree.base.config.PropertyFileConfiguration;
  52. import org.jfree.base.log.PadMessage;
  53. import org.jfree.util.Configuration;
  54. import org.jfree.util.Log;
  55. import org.jfree.util.ObjectUtilities;
  56. /**
  57. * The PackageManager is used to load and configure the modules of JFreeReport.
  58. * Modules are used to extend the basic capabilities of JFreeReport by providing
  59. * a simple plugin-interface.
  60. * <p>
  61. * Modules provide a simple capability to remove unneeded functionality from the
  62. * JFreeReport system and to reduce the overall code size. The modularisation provides
  63. * a very strict way of removing unnecessary dependencies between the various packages.</p>
  64. * <p>
  65. * The package manager can be used to add new modules to the system or to check
  66. * the existence and state of installed modules.</p>
  67. *
  68. * @author Thomas Morgner
  69. */
  70. public final class PackageManager {
  71. /**
  72. * The PackageConfiguration handles the module level configuration.
  73. *
  74. * @author Thomas Morgner
  75. */
  76. public static class PackageConfiguration extends PropertyFileConfiguration {
  77. /**
  78. * DefaultConstructor. Creates a new package configuration.
  79. */
  80. public PackageConfiguration() {
  81. // nothing required
  82. }
  83. /**
  84. * The new configuartion will be inserted into the list of report configuration,
  85. * so that this configuration has the given report configuration instance as parent.
  86. *
  87. * @param config the new report configuration.
  88. */
  89. public void insertConfiguration(final HierarchicalConfiguration config) {
  90. super.insertConfiguration(config);
  91. }
  92. }
  93. /**
  94. * An internal constant declaring that the specified module was already loaded.
  95. */
  96. private static final int RETURN_MODULE_LOADED = 0;
  97. /**
  98. * An internal constant declaring that the specified module is not known.
  99. */
  100. private static final int RETURN_MODULE_UNKNOWN = 1;
  101. /**
  102. * An internal constant declaring that the specified module produced an error while loading.
  103. */
  104. private static final int RETURN_MODULE_ERROR = 2;
  105. /**
  106. * The module configuration instance that should be used to store module
  107. * properties. This separates the user defined properties from the implementation
  108. * defined properties.
  109. */
  110. private final PackageConfiguration packageConfiguration;
  111. /**
  112. * A list of all defined modules.
  113. */
  114. private final ArrayList modules;
  115. /**
  116. * A list of module name definitions.
  117. */
  118. private final ArrayList initSections;
  119. /** The boot implementation for which the modules are managed. */
  120. private AbstractBoot booter;
  121. /** The instances of all modules for all booters. */
  122. private static HashMap instances;
  123. /**
  124. * Creates a package manager instance.
  125. *
  126. * @param booter the booter.
  127. * @return A package manager.
  128. */
  129. public static PackageManager createInstance(final AbstractBoot booter) {
  130. PackageManager manager;
  131. if (instances == null) {
  132. instances = new HashMap();
  133. manager = new PackageManager(booter);
  134. instances.put(booter, manager);
  135. return manager;
  136. }
  137. manager = (PackageManager) instances.get(booter);
  138. if (manager == null) {
  139. manager = new PackageManager(booter);
  140. instances.put(booter, manager);
  141. }
  142. return manager;
  143. }
  144. /**
  145. * Creates a new package manager.
  146. *
  147. * @param booter the booter (<code>null</code> not permitted).
  148. */
  149. private PackageManager(final AbstractBoot booter) {
  150. if (booter == null) {
  151. throw new NullPointerException();
  152. }
  153. this.booter = booter;
  154. this.packageConfiguration = new PackageConfiguration();
  155. this.modules = new ArrayList();
  156. this.initSections = new ArrayList();
  157. }
  158. /**
  159. * Checks, whether a certain module is available.
  160. *
  161. * @param moduleDescription the module description of the desired module.
  162. * @return true, if the module is available and the version of the module
  163. * is compatible, false otherwise.
  164. */
  165. public boolean isModuleAvailable(final ModuleInfo moduleDescription) {
  166. final PackageState[] packageStates =
  167. (PackageState[]) this.modules.toArray(new PackageState[this.modules.size()]);
  168. for (int i = 0; i < packageStates.length; i++) {
  169. final PackageState state = packageStates[i];
  170. if (state.getModule().getModuleClass().equals(moduleDescription.getModuleClass())) {
  171. return (state.getState() == PackageState.STATE_INITIALIZED);
  172. }
  173. }
  174. return false;
  175. }
  176. /**
  177. * Loads all modules mentioned in the report configuration starting with
  178. * the given prefix. This method is used during the boot process of
  179. * JFreeReport. You should never need to call this method directly.
  180. *
  181. * @param modulePrefix the module prefix.
  182. */
  183. public void load(final String modulePrefix) {
  184. if (this.initSections.contains(modulePrefix)) {
  185. return;
  186. }
  187. this.initSections.add(modulePrefix);
  188. final Configuration config = this.booter.getGlobalConfig();
  189. final Iterator it = config.findPropertyKeys(modulePrefix);
  190. int count = 0;
  191. while (it.hasNext()) {
  192. final String key = (String) it.next();
  193. if (key.endsWith(".Module")) {
  194. final String moduleClass = config.getConfigProperty(key);
  195. if (moduleClass != null && moduleClass.length() > 0) {
  196. addModule(moduleClass);
  197. count++;
  198. }
  199. }
  200. }
  201. Log.debug("Loaded a total of " + count + " modules under prefix: " + modulePrefix);
  202. }
  203. /**
  204. * Initializes all previously uninitialized modules. Once a module is initialized,
  205. * it is not re-initialized a second time.
  206. */
  207. public synchronized void initializeModules() {
  208. // sort by subsystems and dependency
  209. PackageSorter.sort(this.modules);
  210. for (int i = 0; i < this.modules.size(); i++) {
  211. final PackageState mod = (PackageState) this.modules.get(i);
  212. if (mod.configure(this.booter)) {
  213. Log.debug(new Log.SimpleMessage("Conf: ",
  214. new PadMessage(mod.getModule().getModuleClass(), 70),
  215. " [", mod.getModule().getSubSystem(), "]"));
  216. }
  217. }
  218. for (int i = 0; i < this.modules.size(); i++) {
  219. final PackageState mod = (PackageState) this.modules.get(i);
  220. if (mod.initialize(this.booter)) {
  221. Log.debug(new Log.SimpleMessage("Init: ",
  222. new PadMessage(mod.getModule().getModuleClass(), 70),
  223. " [", mod.getModule().getSubSystem(), "]"));
  224. }
  225. }
  226. }
  227. /**
  228. * Adds a module to the package manager.
  229. * Once all modules are added, you have to call initializeModules()
  230. * to configure and initialize the new modules.
  231. *
  232. * @param modClass the module class
  233. */
  234. public synchronized void addModule(final String modClass) {
  235. final ArrayList loadModules = new ArrayList();
  236. final ModuleInfo modInfo = new DefaultModuleInfo
  237. (modClass, null, null, null);
  238. if (loadModule(modInfo, new ArrayList(), loadModules, false)) {
  239. for (int i = 0; i < loadModules.size(); i++) {
  240. final Module mod = (Module) loadModules.get(i);
  241. this.modules.add(new PackageState(mod));
  242. }
  243. }
  244. }
  245. /**
  246. * Checks, whether the given module is already loaded in either the given
  247. * tempModules list or the global package registry. If tmpModules is null,
  248. * only the previously installed modules are checked.
  249. *
  250. * @param tempModules a list of previously loaded modules.
  251. * @param module the module specification that is checked.
  252. * @return true, if the module is already loaded, false otherwise.
  253. */
  254. private int containsModule(final ArrayList tempModules, final ModuleInfo module) {
  255. if (tempModules != null) {
  256. final ModuleInfo[] mods = (ModuleInfo[])
  257. tempModules.toArray(new ModuleInfo[tempModules.size()]);
  258. for (int i = 0; i < mods.length; i++) {
  259. if (mods[i].getModuleClass().equals(module.getModuleClass())) {
  260. return RETURN_MODULE_LOADED;
  261. }
  262. }
  263. }
  264. final PackageState[] packageStates =
  265. (PackageState[]) this.modules.toArray(new PackageState[this.modules.size()]);
  266. for (int i = 0; i < packageStates.length; i++) {
  267. if (packageStates[i].getModule().getModuleClass().equals(module.getModuleClass())) {
  268. if (packageStates[i].getState() == PackageState.STATE_ERROR) {
  269. return RETURN_MODULE_ERROR;
  270. }
  271. else {
  272. return RETURN_MODULE_LOADED;
  273. }
  274. }
  275. }
  276. return RETURN_MODULE_UNKNOWN;
  277. }
  278. /**
  279. * A utility method that collects all failed modules. Such an module caused
  280. * an error while being loaded, and is now cached in case it is referenced
  281. * elsewhere.
  282. *
  283. * @param state the failed module.
  284. */
  285. private void dropFailedModule(final PackageState state) {
  286. if (this.modules.contains(state) == false) {
  287. this.modules.add(state);
  288. }
  289. }
  290. /**
  291. * Tries to load a given module and all dependent modules. If the dependency check
  292. * fails for that module (or for one of the dependent modules), the loaded modules
  293. * are discarded and no action is taken.
  294. *
  295. * @param moduleInfo the module info of the module that should be loaded.
  296. * @param incompleteModules a list of incompletly loaded modules. This are module
  297. * specifications which depend on the current module and wait for the module to
  298. * be completly loaded.
  299. * @param modules the list of previously loaded modules for this module.
  300. * @param fatal a flag that states, whether the failure of loading a module should
  301. * be considered an error. Root-modules load errors are never fatal, as we try
  302. * to load all known modules, regardless whether they are active or not.
  303. * @return true, if the module was loaded successfully, false otherwise.
  304. */
  305. private boolean loadModule(final ModuleInfo moduleInfo, final ArrayList incompleteModules,
  306. final ArrayList modules, final boolean fatal) {
  307. try {
  308. final Class c = ObjectUtilities.getClassLoader(getClass()).loadClass(moduleInfo.getModuleClass());
  309. final Module module = (Module) c.newInstance();
  310. if (acceptVersion(moduleInfo, module) == false) {
  311. // module conflict!
  312. Log.warn("Module " + module.getName() + ": required version: "
  313. + moduleInfo + ", but found Version: \n" + module);
  314. final PackageState state = new PackageState(module, PackageState.STATE_ERROR);
  315. dropFailedModule(state);
  316. return false;
  317. }
  318. final int moduleContained = containsModule(modules, module);
  319. if (moduleContained == RETURN_MODULE_ERROR) {
  320. // the module caused harm before ...
  321. Log.debug("Indicated failure for module: " + module.getModuleClass());
  322. final PackageState state = new PackageState(module, PackageState.STATE_ERROR);
  323. dropFailedModule(state);
  324. return false;
  325. }
  326. else if (moduleContained == RETURN_MODULE_UNKNOWN) {
  327. if (incompleteModules.contains(module)) {
  328. // we assume that loading will continue ...
  329. Log.error(new Log.SimpleMessage
  330. ("Circular module reference: This module definition is invalid: ",
  331. module.getClass()));
  332. final PackageState state = new PackageState(module, PackageState.STATE_ERROR);
  333. dropFailedModule(state);
  334. return false;
  335. }
  336. incompleteModules.add(module);
  337. final ModuleInfo[] required = module.getRequiredModules();
  338. for (int i = 0; i < required.length; i++) {
  339. if (loadModule(required[i], incompleteModules, modules, true) == false) {
  340. Log.debug("Indicated failure for module: " + module.getModuleClass());
  341. final PackageState state = new PackageState(module, PackageState.STATE_ERROR);
  342. dropFailedModule(state);
  343. return false;
  344. }
  345. }
  346. final ModuleInfo[] optional = module.getOptionalModules();
  347. for (int i = 0; i < optional.length; i++) {
  348. if (loadModule(optional[i], incompleteModules, modules, true) == false) {
  349. Log.debug(new Log.SimpleMessage("Optional module: ",
  350. optional[i].getModuleClass(), " was not loaded."));
  351. }
  352. }
  353. // maybe a dependent module defined the same base module ...
  354. if (containsModule(modules, module) == RETURN_MODULE_UNKNOWN) {
  355. modules.add(module);
  356. }
  357. incompleteModules.remove(module);
  358. }
  359. return true;
  360. }
  361. catch (ClassNotFoundException cnfe) {
  362. if (fatal) {
  363. Log.warn(new Log.SimpleMessage
  364. ("Unresolved dependency for package: ", moduleInfo.getModuleClass()));
  365. }
  366. Log.debug(new Log.SimpleMessage("ClassNotFound: ", cnfe.getMessage()));
  367. return false;
  368. }
  369. catch (Exception e) {
  370. Log.warn(new Log.SimpleMessage("Exception while loading module: ", moduleInfo), e);
  371. return false;
  372. }
  373. }
  374. /**
  375. * Checks, whether the given module meets the requirements defined in the module
  376. * information.
  377. *
  378. * @param moduleRequirement the required module specification.
  379. * @param module the module that should be checked against the specification.
  380. * @return true, if the module meets the given specifications, false otherwise.
  381. */
  382. private boolean acceptVersion(final ModuleInfo moduleRequirement, final Module module) {
  383. if (moduleRequirement.getMajorVersion() == null) {
  384. return true;
  385. }
  386. if (module.getMajorVersion() == null) {
  387. Log.warn("Module " + module.getName() + " does not define a major version.");
  388. }
  389. else {
  390. final int compare = acceptVersion(moduleRequirement.getMajorVersion(),
  391. module.getMajorVersion());
  392. if (compare > 0) {
  393. return false;
  394. }
  395. else if (compare < 0) {
  396. return true;
  397. }
  398. }
  399. if (moduleRequirement.getMinorVersion() == null) {
  400. return true;
  401. }
  402. if (module.getMinorVersion() == null) {
  403. Log.warn("Module " + module.getName() + " does not define a minor version.");
  404. }
  405. else {
  406. final int compare = acceptVersion(moduleRequirement.getMinorVersion(),
  407. module.getMinorVersion());
  408. if (compare > 0) {
  409. return false;
  410. }
  411. else if (compare < 0) {
  412. return true;
  413. }
  414. }
  415. if (moduleRequirement.getPatchLevel() == null) {
  416. return true;
  417. }
  418. if (module.getPatchLevel() == null) {
  419. Log.debug("Module " + module.getName() + " does not define a patch level.");
  420. }
  421. else {
  422. if (acceptVersion(moduleRequirement.getPatchLevel(),
  423. module.getPatchLevel()) > 0) {
  424. Log.debug("Did not accept patchlevel: "
  425. + moduleRequirement.getPatchLevel() + " - "
  426. + module.getPatchLevel());
  427. return false;
  428. }
  429. }
  430. return true;
  431. }
  432. /**
  433. * Compare the version strings. If the strings have a different length,
  434. * the shorter string is padded with spaces to make them compareable.
  435. *
  436. * @param modVer the version string of the module
  437. * @param depModVer the version string of the dependent or optional module
  438. * @return 0, if the dependent module version is equal tothe module's required
  439. * version, a negative number if the dependent module is newer or a positive
  440. * number if the dependent module is older and does not fit.
  441. */
  442. private int acceptVersion(final String modVer, final String depModVer) {
  443. final int mLength = Math.max(modVer.length(), depModVer.length());
  444. final char[] modVerArray;
  445. final char[] depVerArray;
  446. if (modVer.length() > depModVer.length()) {
  447. modVerArray = modVer.toCharArray();
  448. depVerArray = new char[mLength];
  449. final int delta = modVer.length() - depModVer.length();
  450. Arrays.fill(depVerArray, 0, delta, ' ');
  451. System.arraycopy(depVerArray, delta, depModVer.toCharArray(), 0, depModVer.length());
  452. }
  453. else if (modVer.length() < depModVer.length()) {
  454. depVerArray = depModVer.toCharArray();
  455. modVerArray = new char[mLength];
  456. final char[] b1 = new char[mLength];
  457. final int delta = depModVer.length() - modVer.length();
  458. Arrays.fill(b1, 0, delta, ' ');
  459. System.arraycopy(b1, delta, modVer.toCharArray(), 0, modVer.length());
  460. }
  461. else {
  462. depVerArray = depModVer.toCharArray();
  463. modVerArray = modVer.toCharArray();
  464. }
  465. return new String(modVerArray).compareTo(new String(depVerArray));
  466. }
  467. /**
  468. * Returns the default package configuration. Private report configuration
  469. * instances may be inserted here. These inserted configuration can never override
  470. * the settings from this package configuration.
  471. *
  472. * @return the package configuration.
  473. */
  474. public PackageConfiguration getPackageConfiguration() {
  475. return this.packageConfiguration;
  476. }
  477. /**
  478. * Returns an array of the currently active modules. The module definition
  479. * returned contain all known modules, including buggy and unconfigured
  480. * instances.
  481. *
  482. * @return the modules.
  483. */
  484. public Module[] getAllModules() {
  485. final Module[] mods = new Module[this.modules.size()];
  486. for (int i = 0; i < this.modules.size(); i++) {
  487. final PackageState state = (PackageState) this.modules.get(i);
  488. mods[i] = state.getModule();
  489. }
  490. return mods;
  491. }
  492. /**
  493. * Returns all active modules. This array does only contain modules
  494. * which were successfully configured and initialized.
  495. *
  496. * @return the list of all active modules.
  497. */
  498. public Module[] getActiveModules() {
  499. final ArrayList mods = new ArrayList();
  500. for (int i = 0; i < this.modules.size(); i++) {
  501. final PackageState state = (PackageState) this.modules.get(i);
  502. if (state.getState() == PackageState.STATE_INITIALIZED) {
  503. mods.add(state.getModule());
  504. }
  505. }
  506. return (Module[]) mods.toArray(new Module[mods.size()]);
  507. }
  508. /**
  509. * Prints the modules that are used.
  510. *
  511. * @param p the print stream.
  512. */
  513. public void printUsedModules(final PrintStream p) {
  514. final Module[] allMods = getAllModules();
  515. final ArrayList activeModules = new ArrayList();
  516. final ArrayList failedModules = new ArrayList();
  517. for (int i = 0; i < allMods.length; i++) {
  518. if (isModuleAvailable(allMods[i])) {
  519. activeModules.add(allMods[i]);
  520. }
  521. else {
  522. failedModules.add(allMods[i]);
  523. }
  524. }
  525. p.print("Active modules: ");
  526. p.println(activeModules.size());
  527. p.println("----------------------------------------------------------");
  528. for (int i = 0; i < activeModules.size(); i++) {
  529. final Module mod = (Module) activeModules.get(i);
  530. p.print(new PadMessage(mod.getModuleClass(), 70));
  531. p.print(" [");
  532. p.print(mod.getSubSystem());
  533. p.println("]");
  534. p.print(" Version: ");
  535. p.print(mod.getMajorVersion());
  536. p.print("-");
  537. p.print(mod.getMinorVersion());
  538. p.print("-");
  539. p.print(mod.getPatchLevel());
  540. p.print(" Producer: ");
  541. p.println(mod.getProducer());
  542. p.print(" Description: ");
  543. p.println(mod.getDescription());
  544. }
  545. }
  546. }