diff --git a/ccm-core/src/main/java/org/libreccm/modules/CcmModule.java b/ccm-core/src/main/java/org/libreccm/modules/CcmModule.java index 38a5f08b1..d6ca777fd 100644 --- a/ccm-core/src/main/java/org/libreccm/modules/CcmModule.java +++ b/ccm-core/src/main/java/org/libreccm/modules/CcmModule.java @@ -41,14 +41,33 @@ public interface CcmModule { /** * An implementation of this method is called after the module is installed. + * + * This method can be used to create initial data. * - * @param event + * @param event @see InstallEvent */ void install(InstallEvent event); + /** + * Called each time the application is restarted. + * + * @param event @see InitEvent + */ void init(InitEvent event); + /** + * Called each time the application is shutdown. + * + * @param event @see ShutdownEvent + */ void shutdown(ShutdownEvent event); + /** + * Called if the module is uninstalled. The implementation of this + * method should remove all data created by the {@link #install(InstallEvent)} + * method. + * + * @param event @see UnInstallEvent + */ void uninstall(UnInstallEvent event); } diff --git a/ccm-core/src/main/java/org/libreccm/modules/CcmModuleContextListener.java b/ccm-core/src/main/java/org/libreccm/modules/CcmModuleContextListener.java index 63eead7be..d3f09d326 100644 --- a/ccm-core/src/main/java/org/libreccm/modules/CcmModuleContextListener.java +++ b/ccm-core/src/main/java/org/libreccm/modules/CcmModuleContextListener.java @@ -25,10 +25,14 @@ import javax.servlet.annotation.WebListener; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.libreccm.modules.ModuleManager; /** - * + * An implementation of the {@link ServletContextListener} interface for + * initialising the modules. + * + * The real work is done by the {@link ModuleManager}. This class only delegates + * to the {@code ModuleManager}. + * * @author Jens Pelzetter */ @WebListener diff --git a/ccm-core/src/main/java/org/libreccm/modules/DependencyException.java b/ccm-core/src/main/java/org/libreccm/modules/DependencyException.java index a10716a23..c33ffeb52 100644 --- a/ccm-core/src/main/java/org/libreccm/modules/DependencyException.java +++ b/ccm-core/src/main/java/org/libreccm/modules/DependencyException.java @@ -19,7 +19,9 @@ package org.libreccm.modules; /** - * + * Thrown by the {@link DependencyTreeManager} if something is wrong with the + * dependency tree. + * * @author Jens Pelzetter */ public class DependencyException extends Exception { @@ -32,7 +34,7 @@ public class DependencyException extends Exception { */ public DependencyException() { super(); - //Nothin more + //Nothing more } /** diff --git a/ccm-core/src/main/java/org/libreccm/modules/DependencyTreeManager.java b/ccm-core/src/main/java/org/libreccm/modules/DependencyTreeManager.java index 489feade6..e7008efb1 100644 --- a/ccm-core/src/main/java/org/libreccm/modules/DependencyTreeManager.java +++ b/ccm-core/src/main/java/org/libreccm/modules/DependencyTreeManager.java @@ -36,6 +36,8 @@ import org.apache.maven.artifact.versioning.ComparableVersion; * returned list of nodes call the the {@link #orderModules(java.util.List)} * method. The list returned by {@link #orderModules(java.util.List)} contains * all modules in order. + * + * This class is not part of the public API. * * More information about topological sorting: * https://en.wikipedia.org/wiki/Topological_sorting @@ -47,21 +49,34 @@ final class DependencyTreeManager { private static final Logger LOGGER = LogManager.getLogger( DependencyTreeManager.class); + /** + * Initialises the tree with the provided list of modules. + * + * @param modules The module for which a dependency tree is generated. + * @return An ordered list of tree nodes. + * + * @throws DependencyException If something is wrong with the dependency + * tree. For example if a module on which another module depends is missing + * or if a cycle is detected in the dependency tree. + */ public List generateTree(final Iterable modules) throws DependencyException { LOGGER.info("Starting to generate dependency tree..."); + //Create the tree nodes. A HashMap is used to avoid duplicates and + //the lookup the nodes based on their name. final Map nodes = new HashMap<>(); - for (final CcmModule module : modules) { createTreeNode(module, nodes); } + //Add the dependency relations to the nodes for (final CcmModule module : modules) { addDependencyRelations(module, nodes); } + //Generate the node list final List nodeList = new ArrayList<>(); for (final Map.Entry entry : nodes.entrySet()) { nodeList.add(entry.getValue()); @@ -72,15 +87,29 @@ final class DependencyTreeManager { return nodeList; } + /** + * Generates an ordered list of the tree nodes which can be used to + * initialise the modules in correct order. + * + * In this method the topological sorting happens. + * + * @param dependencyTree The list of tree nodes of the dependency tree. + * @return A ordered list of the tree nodes. + * @throws DependencyException If something is wrong with the dependency + * tree. + */ public List orderModules(final List dependencyTree) throws DependencyException { LOGGER.info("Creating an ordered list from the dependency tree..."); + //List for the ordered and resolved nodes/modules. final List orderedModules = new ArrayList<>(); final List resolvedModules = new ArrayList<>(); LOGGER.info("Looking for modules which do not depend on any other " + "modules..."); + //Find all modules which do not depend on any other module. These + //modules are used as starting point for the sorting. for (final TreeNode node : dependencyTree) { if (node.getDependsOn().isEmpty()) { LOGGER.info( @@ -93,21 +122,28 @@ final class DependencyTreeManager { LOGGER.info("Ordering remaining nodes..."); while (!resolvedModules.isEmpty()) { + //Remove the first node from the resolved modules list final TreeNode current = resolvedModules.remove(0); LOGGER.info("\tProcessing node for module \"{}\"...", current.getModuleInfo().getModuleName()); - + + //Add the node to the ordered modules list. orderedModules.add(current); + //Remove the edges to the current node. for (final TreeNode dependent : current.getDependentModules()) { dependent.removeDependsOn(current); + //If the dependent node has node more dependsOn relations put + //the node into the resolved modules list. if (dependent.getDependsOn().isEmpty()) { resolvedModules.add(dependent); } } } + //Check if all nodes have been ordered. If not the tree has at least on + //on cycle and can't be processed. if (orderedModules.size() == dependencyTree.size()) { LOGGER.info("Dependency graph proceessed successfully. " + "Modules in order:"); @@ -123,6 +159,8 @@ final class DependencyTreeManager { } } + //Helper method for creating a tree node for a module and putting the + //node into the map of tree nodes. private void createTreeNode(final CcmModule module, final Map nodes) { final TreeNode node = new TreeNode(module); @@ -132,17 +170,29 @@ final class DependencyTreeManager { nodes.put(node.getModuleInfo().getModuleName(), node); } + /** + * Helper method for adding the dependency relations for a module to the + * nodes. + * + * @param module The module. + * @param nodes The map of nodes. + * + * @throws DependencyException If something goes wrong. + */ private void addDependencyRelations(final CcmModule module, final Map nodes) throws DependencyException { + //Load the module info for the current module final ModuleInfo moduleInfo = new ModuleInfo(); moduleInfo.load(module); LOGGER.info("Adding dependency relations for module \"{}\"...", moduleInfo.getModuleName()); + //Get the name of the module from the module info. final String moduleName = moduleInfo.getModuleName(); + //Check if the nodes map has an entry for the module. if (!nodes.containsKey(moduleName)) { LOGGER.fatal("Modules nodes map does contain an entry for \"{}\". " + "That should not happen.", @@ -153,62 +203,33 @@ final class DependencyTreeManager { moduleName)); } + //Get the node from the map final TreeNode node = nodes.get(moduleName); LOGGER.info("Processing required modules for module \"{}\"...", node.getModuleInfo().getModuleName()); + //Process the modules required by the current module and add the + //dependency relations. for (final RequiredModule requiredModule : node.getModuleInfo(). getRequiredModules()) { -// final ModuleInfo requiredModuleInfo = new ModuleInfo(); -// requiredModuleInfo.load(requiredModule.module()); -// -// LOGGER.info("\tModule \"{}\" requires module \"{}\".", -// node.getModuleInfo().getModuleName(), -// requiredModuleInfo.getModuleName()); -// -// if (!nodes.containsKey(requiredModuleInfo.getModuleName())) { -// -// LOGGER.fatal("Required module \"{}\" no found.", -// requiredModuleInfo.getModuleName()); -// -// throw new DependencyException(String.format( -// "Module \"%s\" depends on module \"%s\" but the dependency " -// + "tree does contain an entry for module \"%s\".", -// moduleInfo.getModuleName(), -// requiredModuleInfo.getModuleName(), -// requiredModuleInfo.getModuleName())); -// } -// -// final TreeNode dependencyNode = nodes.get(requiredModuleInfo. -// getModuleName()); -// -// //Check version -// if (!validateVersion(dependencyNode.getModuleInfo(). -// getModuleVersion(), -// requiredModule.minVersion(), -// requiredModule.maxVersion())) { -// throw new DependencyException(String.format( -// "The required module is avialable but in the correct " -// + "version. " -// + "Available version: \"%s\"; " -// + "minimal required version: \"%s\"; " -// + "maximum required version: \"%s\"", -// dependencyNode.getModuleInfo().getModuleVersion(), -// requiredModule.minVersion(), -// requiredModule.maxVersion())); -// } -// -// node.addDependsOn(dependencyNode); -// dependencyNode.addDependentModule(node); addDependencyRelation(nodes, node, requiredModule); } } + /** + * Helper method for adding a single dependency relation. + * + * @param nodes The map of tree nodes. + * @param node The node to which the dependency relations are added. + * @param requiredModule The module required by the current module/node. + * @throws DependencyException + */ private void addDependencyRelation(final Map nodes, final TreeNode node, final RequiredModule requiredModule) throws DependencyException { + //Get the module info for the required module final ModuleInfo requiredInfo = new ModuleInfo(); requiredInfo.load(requiredModule.module()); @@ -216,6 +237,7 @@ final class DependencyTreeManager { node.getModuleInfo().getModuleName(), requiredInfo.getModuleName()); + //Check if the nodes list has an entry for the required module. if (!nodes.containsKey(requiredInfo.getModuleName())) { LOGGER.fatal("Required module \"{}\" no found.", requiredInfo.getModuleName()); @@ -227,6 +249,7 @@ final class DependencyTreeManager { requiredInfo.getModuleName())); } + //Validate the version of the required module. final TreeNode dependencyNode = nodes.get(requiredInfo. getModuleName()); if (!validateVersion(dependencyNode.getModuleInfo(). @@ -244,6 +267,7 @@ final class DependencyTreeManager { requiredModule.maxVersion())); } + //Create the dependencies relations. node.addDependsOn(dependencyNode); dependencyNode.addDependentModule(node); } diff --git a/ccm-core/src/main/java/org/libreccm/modules/InitEvent.java b/ccm-core/src/main/java/org/libreccm/modules/InitEvent.java index 9b645bed5..aedb07b1b 100644 --- a/ccm-core/src/main/java/org/libreccm/modules/InitEvent.java +++ b/ccm-core/src/main/java/org/libreccm/modules/InitEvent.java @@ -19,7 +19,11 @@ package org.libreccm.modules; /** - * + * Event object for the module initialisation. + * + * @see ModuleEvent + * + * * @author Jens Pelzetter */ public class InitEvent extends ModuleEvent { diff --git a/ccm-core/src/main/java/org/libreccm/modules/InstallEvent.java b/ccm-core/src/main/java/org/libreccm/modules/InstallEvent.java index d7ec7aa18..0931643f4 100644 --- a/ccm-core/src/main/java/org/libreccm/modules/InstallEvent.java +++ b/ccm-core/src/main/java/org/libreccm/modules/InstallEvent.java @@ -19,7 +19,10 @@ package org.libreccm.modules; /** - * + * Event object for the module initialisation. + * + * @see ModuleEvent + * * @author Jens Pelzetter */ public class InstallEvent extends ModuleEvent { diff --git a/ccm-core/src/main/java/org/libreccm/modules/InstalledModule.java b/ccm-core/src/main/java/org/libreccm/modules/InstalledModule.java index a6d0c3c39..e024fcabc 100644 --- a/ccm-core/src/main/java/org/libreccm/modules/InstalledModule.java +++ b/ccm-core/src/main/java/org/libreccm/modules/InstalledModule.java @@ -30,7 +30,9 @@ import javax.persistence.Table; import static org.libreccm.core.CoreConstants.DB_SCHEMA; /** - * + * A JPA entity bean for for installed modules table for use in the LibreCCM + * administration UI. + * * @author Jens Pelzetter */ @Entity @@ -49,9 +51,15 @@ public class InstalledModule implements Serializable { @Column(name = "MODULE_ID") private int moduleId; + /** + * The fully qualified name of the module class. + */ @Column(name = "MODULE_CLASS_NAME", length = 2048, unique = true) private String moduleClassName; + /** + * The status of the module. + */ @Column(name = "STATUS") @Enumerated(EnumType.STRING) private ModuleStatus status; diff --git a/ccm-core/src/main/java/org/libreccm/modules/IntegrationException.java b/ccm-core/src/main/java/org/libreccm/modules/IntegrationException.java index 8aca60f46..b1929536b 100644 --- a/ccm-core/src/main/java/org/libreccm/modules/IntegrationException.java +++ b/ccm-core/src/main/java/org/libreccm/modules/IntegrationException.java @@ -19,7 +19,8 @@ package org.libreccm.modules; /** - * + * Thrown if an error occurs in the {@link CcmIntegrator}. + * * @author Jens Pelzetter */ public class IntegrationException extends RuntimeException { diff --git a/ccm-core/src/main/java/org/libreccm/modules/Module.java b/ccm-core/src/main/java/org/libreccm/modules/Module.java index e5383871f..c73394f1c 100644 --- a/ccm-core/src/main/java/org/libreccm/modules/Module.java +++ b/ccm-core/src/main/java/org/libreccm/modules/Module.java @@ -26,7 +26,7 @@ import static java.lang.annotation.ElementType.*; import java.lang.annotation.Target; /** - * Annotation for describing some meta of a module. + * Annotation for describing some meta data of a module. * * @author Jens Pelzetter */ diff --git a/ccm-core/src/main/java/org/libreccm/modules/ModuleEvent.java b/ccm-core/src/main/java/org/libreccm/modules/ModuleEvent.java index 9428d73fd..96eb0d38a 100644 --- a/ccm-core/src/main/java/org/libreccm/modules/ModuleEvent.java +++ b/ccm-core/src/main/java/org/libreccm/modules/ModuleEvent.java @@ -21,7 +21,9 @@ package org.libreccm.modules; import javax.persistence.EntityManager; /** - * + * Base class for the module lifecycle events. Provides access to the JPA + * {@code EntityManager}. + * * @author Jens Pelzetter */ class ModuleEvent { diff --git a/ccm-core/src/main/java/org/libreccm/modules/ModuleInfo.java b/ccm-core/src/main/java/org/libreccm/modules/ModuleInfo.java index 033fec90c..c78a0b8be 100644 --- a/ccm-core/src/main/java/org/libreccm/modules/ModuleInfo.java +++ b/ccm-core/src/main/java/org/libreccm/modules/ModuleInfo.java @@ -32,21 +32,47 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; /** + * A representation of the module metadata combining the data from the modules + * info file and the annotations on the module class. * * @author Jens Pelzetter */ public class ModuleInfo { + /** + * Constant for the artifactId property in the module info file. + */ private static final String ARTIFACT_ID = "artifactId"; + /** + * Constant for the groupId property in the module info file. + */ private static final String GROUP_ID = "groupId"; + /** + * Constant for the version property in the module info file. + */ private static final String VERSION = "version"; private static final Logger LOGGER = LogManager.getLogger(ModuleInfo.class); + /** + * The name of the module (artifact id). + */ private transient String moduleName; + /** + * The data package of the module (group id). + */ private transient String moduleDataPackage; + /** + * The entities provided by the module. + */ private transient Class[] moduleEntities; + /** + * The version of the module. + */ private transient String moduleVersion; + /** + * The modules required by the described module. + */ private transient RequiredModule[] requiredModules; public ModuleInfo() { @@ -61,36 +87,47 @@ public class ModuleInfo { return moduleDataPackage; } - public String getDbSchemaName() { - return moduleName.toLowerCase(Locale.ROOT).replace("-", "_"); - } - - public String getDbScriptsLocation(final Connection connection) - throws SQLException { - final StringBuffer buffer - = new StringBuffer("classpath:/db/migrations/"); - buffer.append(moduleDataPackage); - switch (connection.getMetaData().getDatabaseProductName()) { - case "H2": { - buffer.append("/h2"); - break; - } - case "MySQL": - buffer.append("/mysql"); - break; - case "PostgreSQL": - buffer.append("/postgresql"); - break; - default: - throw new IntegrationException(String.format( - "Integration failed. Database \"%s\" is not supported yet.", - connection.getMetaData(). - getDatabaseProductName())); - } - - return buffer.toString(); - } - +// /** +// * Gets the database schema name for the module. That is the +// * {@link moduleName} with all hyphens replaced by underscore (hyphens can +// * be nasty in SQL). +// * +// * @return The name of the database schema of the module. +// */ +// public String getDbSchemaName() { +// return moduleName.toLowerCase(Locale.ROOT).replace("-", "_"); +// } +// /** +// * +// * @param connection +// * @return +// * @throws SQLException +// */ +// public String getDbScriptsLocation(final Connection connection) +// throws SQLException { +// final StringBuffer buffer +// = new StringBuffer("classpath:/db/migrations/"); +// buffer.append(moduleDataPackage); +// switch (connection.getMetaData().getDatabaseProductName()) { +// case "H2": { +// buffer.append("/h2"); +// break; +// } +// case "MySQL": +// buffer.append("/mysql"); +// break; +// case "PostgreSQL": +// buffer.append("/postgresql"); +// break; +// default: +// throw new IntegrationException(String.format( +// "Integration failed. Database \"%s\" is not supported yet.", +// connection.getMetaData(). +// getDatabaseProductName())); +// } +// +// return buffer.toString(); +// } public String getModuleVersion() { return moduleVersion; } @@ -107,85 +144,29 @@ public class ModuleInfo { load(module.getClass()); } + /** + * Loads the module info data. + * + * @param moduleClass The module class for which the module data is loaded. + */ public void load(final Class moduleClass) { LOGGER.info("Reading module info for {}...", moduleClass.getName()); final Module annotation = moduleClass.getAnnotation(Module.class); final Properties properties = loadModuleInfoFile(moduleClass); -// final Properties properties = new Properties(); -// -// final String path = String.format("/module-info/%s.properties", -// moduleClass.getName()); -// LOGGER.info("Trying to retrieve module info for module {} from {}...", -// moduleClass.getName(), -// path); -// final InputStream stream = moduleClass.getResourceAsStream(path); -// if (stream == null) { -// LOGGER.warn("No module info for {} found at {}", -// moduleClass.getName(), -// path); -// } else { -// try { -// properties.load(stream); -// } catch (IOException ex) { -// LOGGER.error("Failed to read module info for {} at {}.", -// moduleClass.getName(), -// path); -// LOGGER.error("Cause: ", ex); -// } -// } LOGGER.info("Reading module name..."); -// if (annotation.name() != null && !annotation.name().isEmpty()) { -// moduleName = annotation.name(); -// } else if (properties.getProperty(ARTIFACT_ID) != null -// && !properties.getProperty(ARTIFACT_ID).isEmpty()) { -// moduleName = properties.getProperty(ARTIFACT_ID); -// } else { -// LOGGER.warn( -// "The module was not specificied by the module annotation " -// + "or by the module info file. Creating name from " -// + "simple name of the module class."); -// moduleName = moduleClass.getSimpleName().toLowerCase(); -// } LOGGER.info("Reading module name..."); moduleName = readModuleName(moduleClass, annotation, properties); LOGGER.info("Module name is \"{}\".", moduleName); LOGGER.info("Reading module package name..."); -// if (annotation.packageName() != null -// && !annotation.packageName().isEmpty()) { -// moduleDataPackage = annotation.packageName(); -// } else if (properties.getProperty(GROUP_ID) != null -// && !properties.getProperty(GROUP_ID).isEmpty()) { -// moduleDataPackage = String.format("%s/%s", -// properties.getProperty(GROUP_ID), -// properties. -// getProperty(ARTIFACT_ID).replace( -// "-", "_")); -// } else { -// LOGGER.warn("The module data package was specified by the module " -// + "annotation nore was an group id found in the module info" -// + "file. Creating data package name from the name of the " -// + "module class."); -// moduleDataPackage = moduleClass.getName().toLowerCase(); -// } moduleDataPackage = readModulePackageName(moduleClass, annotation, properties); LOGGER.info("Module data package is \"{}\".", moduleDataPackage); LOGGER.info("Reading module version..."); -// if (annotation.version() != null && !annotation.version().isEmpty()) { -// moduleVersion = annotation.version(); -// } else if (properties.getProperty(VERSION) != null -// && !properties.getProperty(VERSION).isEmpty()) { -// moduleVersion = properties.getProperty(VERSION); -// } else { -// LOGGER.warn("Module version is not specified by the module " -// + "annotation or in the module info file. Module version is " -// + "undefinied. This can lead to all sorts of strange errors!"); -// } moduleVersion = readModuleVersion(annotation, properties); LOGGER.info("Module version is \"{}.\"", moduleVersion); @@ -193,8 +174,16 @@ public class ModuleInfo { moduleEntities = annotation.entities(); } + /** + * Load the module info properties file. + * + * @param moduleClass The class for which the module info properties file is + * loaded. + * + * @return The properties from the module info properties file. + */ private Properties loadModuleInfoFile( - final Class moduleClass) { + final Class moduleClass) { final Properties moduleInfo = new Properties(); final String path = String.format("/module-info/%s.properties", @@ -220,19 +209,30 @@ public class ModuleInfo { return moduleInfo; } + /** + * Reads the module name. If the module annotation of the module class has a + * value for {@code name} that value is used. Otherwise the name from the + * module info file is used. If this name is also empty the simple name of + * the module class is used. + * + * @param moduleClass The module class. + * @param annotation The module annotation of the module class. + * @param moduleInfo The module info properties. + * @return The name of the module. + */ private String readModuleName(final Class moduleClass, final Module annotation, final Properties moduleInfo) { @SuppressWarnings( - "PMD.LongVariable") + "PMD.LongVariable") final boolean annotationHasModuleName = annotation.name() != null - && !annotation.name() - .isEmpty(); + && !annotation.name() + .isEmpty(); @SuppressWarnings("PMD.LongVariable") final boolean moduleInfoHasModuleName = moduleInfo.getProperty( - ARTIFACT_ID) - != null && !moduleInfo - .getProperty(ARTIFACT_ID).isEmpty(); + ARTIFACT_ID) + != null && !moduleInfo + .getProperty(ARTIFACT_ID).isEmpty(); if (annotationHasModuleName) { return annotation.name(); @@ -240,29 +240,40 @@ public class ModuleInfo { return moduleInfo.getProperty(ARTIFACT_ID); } else { LOGGER.warn( - "The module was not specificied by the module annotation " - + "or by the module info file. Creating name from " - + "simple name of the module class."); + "The module was not specificied by the module annotation " + + "or by the module info file. Creating name from " + + "simple name of the module class."); return moduleClass.getSimpleName().toLowerCase(); } } + /** + * Reads the module name. If the module annotation of the module class has a + * value for {@code packageName} that value is used. Otherwise the groupId + * from the module info file is used. If this name is also empty the package + * name of the module class is used. + * + * @param moduleClass The module class. + * @param annotation The module annotation of the module class. + * @param moduleInfo The module info properties. + * @return The name of the module. + */ private String readModulePackageName( - final Class moduleClass, - final Module annotation, - final Properties moduleInfo) { + final Class moduleClass, + final Module annotation, + final Properties moduleInfo) { @SuppressWarnings( - "PMD.LongVariable") + "PMD.LongVariable") final boolean annotationHasPackageName = annotation.packageName() - != null - && !annotation - .packageName() - .isEmpty(); + != null + && !annotation + .packageName() + .isEmpty(); @SuppressWarnings("PMD.LongVariable") final boolean moduleInfoHasPackageName = moduleInfo - .getProperty(GROUP_ID) != null && !moduleInfo.getProperty( - GROUP_ID).isEmpty(); + .getProperty(GROUP_ID) != null && !moduleInfo.getProperty( + GROUP_ID).isEmpty(); if (annotationHasPackageName) { return annotation.packageName(); } else if (moduleInfoHasPackageName) { @@ -273,27 +284,39 @@ public class ModuleInfo { "_")); } else { LOGGER.warn("The module data package was specified by the module " - + "annotation nore was an group id found in the module info" + + "annotation nore was an group id found in the module info" + "file. Creating data package name from the name of the " + "module class."); return moduleClass.getName().toLowerCase(); } } + /** + * Reads the module version. If the module annotation on the module class + * specifies a value for the version that value is used. Otherwise the value + * from the module info properties is used. If the properties do not specify + * a version the version is undefined. That should be avoided because it can + * lead to strange errors. + * + * @param annotation The module annotation of the module. + * @param moduleInfo The module info properties. + * @return The version of the module or {@code null} if the version is + * unspecified. + */ private String readModuleVersion( - final Module annotation, - final Properties moduleInfo) { + final Module annotation, + final Properties moduleInfo) { @SuppressWarnings("PMD.LongVariable") final boolean annotationHasVersion = annotation.version() != null - && !annotation.version() - .isEmpty(); + && !annotation.version() + .isEmpty(); @SuppressWarnings("PMD.LongVariable") final boolean moduleInfoHasVersion = moduleInfo.getProperty(VERSION) - != null - && !moduleInfo.getProperty( - VERSION) - .isEmpty(); + != null + && !moduleInfo.getProperty( + VERSION) + .isEmpty(); if (annotationHasVersion) { return annotation.version(); @@ -301,7 +324,7 @@ public class ModuleInfo { return moduleInfo.getProperty(VERSION); } else { LOGGER.warn("Module version is not specified by the module " - + "annotation or in the module info file. Module version is " + + "annotation or in the module info file. Module version is " + "undefinied. This can lead to all sorts of strange errors!"); return ""; } diff --git a/ccm-core/src/main/java/org/libreccm/modules/ModuleManager.java b/ccm-core/src/main/java/org/libreccm/modules/ModuleManager.java index bf4d63a7c..6fcfa9250 100644 --- a/ccm-core/src/main/java/org/libreccm/modules/ModuleManager.java +++ b/ccm-core/src/main/java/org/libreccm/modules/ModuleManager.java @@ -32,6 +32,10 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; /** + * The {@code ModuleManager} manages the lifecycle of all modules. + * + * Please note: While this class is public it is not part of the public API and + * should not be called by other classes. * * @author Jens Pelzetter */ @@ -39,33 +43,45 @@ import org.apache.logging.log4j.Logger; public class ModuleManager { private static final Logger LOGGER = LogManager.getLogger( - ModuleManager.class + ModuleManager.class ); + /** + * {@code EntityManager} for interacting with the database. + */ @PersistenceContext(name = "LibreCCM") private EntityManager entityManager; + /** + * Dependency tree nodes. + */ private List moduleNodes; + /** + * Method for initialising the dependency tree (is put into + * {@link #moduleNodes}. The method is annotated with {@link PostConstruct} + * which causes the CDI container to call this method after an instance of + * this class is generated. + */ @PostConstruct public void initDependencyTree() { + + //Find all modules using the service loader. LOGGER.info("Finding modules"); final ServiceLoader modules = ServiceLoader.load( - CcmModule.class); + CcmModule.class); LOGGER.info("Creating dependency tree these modules:"); for (final CcmModule module : modules) { final ModuleInfo moduleInfo = new ModuleInfo(); moduleInfo.load(module); LOGGER.info("\t{} {}", - // ModuleUtil.getModuleName(module), - // ModuleUtil.getVersion(module)); moduleInfo.getModuleName(), moduleInfo.getModuleVersion()); } + //Create the dependency tree final DependencyTreeManager treeManager = new DependencyTreeManager(); - try { final List tree = treeManager.generateTree(modules); moduleNodes = treeManager.orderModules(tree); @@ -75,23 +91,33 @@ public class ModuleManager { } + /** + * Initialises all modules. + */ @Transactional(Transactional.TxType.REQUIRED) public void initModules() { LOGGER.info("Initalising modules..."); + //Initialise all modules in the correct order for (final TreeNode node : moduleNodes) { + + //Create an install event instance. final InstallEvent installEvent = new InstallEvent(); installEvent.setEntityManager(entityManager); + //Check if the module is a new module. If it is a new module + //call the install method of the module and set the module status + //to installed after the install method has run sucessfully. final InstalledModule installedModule = entityManager.find( - InstalledModule.class, - node.getModule().getClass().getName().hashCode()); + InstalledModule.class, + node.getModule().getClass().getName().hashCode()); if (installedModule != null - && installedModule.getStatus() == ModuleStatus.NEW) { + && installedModule.getStatus() == ModuleStatus.NEW) { node.getModule().install(installEvent); installedModule.setStatus(ModuleStatus.INSTALLED); entityManager.merge(installedModule); } + //Create an init event instance and call the init method. final InitEvent initEvent = new InitEvent(); initEvent.setEntityManager(entityManager); node.getModule().init(initEvent); @@ -100,8 +126,8 @@ public class ModuleManager { node.getModule().getClass().getName()); final Properties moduleInfo = getModuleInfo(node.getModule()); LOGGER - .info("Module group id: {}", moduleInfo.getProperty( - "groupId")); + .info("Module group id: {}", moduleInfo.getProperty( + "groupId")); LOGGER.info("Module artifact id: {}", moduleInfo.getProperty( "artifactId")); LOGGER.info("Module version: {}", moduleInfo.getProperty("version")); @@ -111,16 +137,21 @@ public class ModuleManager { } } + /** + * Helper method for retrieving the module info for a module. + * + * @param module + * @return + */ private Properties getModuleInfo(final CcmModule module) { final Properties moduleInfo = new Properties(); -// try { final String moduleInfoPath = String.format( - "/module-info/%s.properties", - module.getClass().getName()); + "/module-info/%s.properties", + module.getClass().getName()); LOGGER.info("Path for module info: {}", moduleInfoPath); try (final InputStream stream = module.getClass().getResourceAsStream( - moduleInfoPath)) { + moduleInfoPath)) { if (stream == null) { LOGGER.warn("No module info found."); } else { @@ -134,11 +165,17 @@ public class ModuleManager { return moduleInfo; } + /** + * Called to shutdown all modules. + * + */ @Transactional(Transactional.TxType.REQUIRED) public void shutdownModules() { LOGGER.info("Shutting down modules..."); System.out.println("Shutting down modules..."); for (final TreeNode node : moduleNodes) { + //Create a shutdown event instance and call the shutdown method of + //the module. final ShutdownEvent shutdownEvent = new ShutdownEvent(); shutdownEvent.setEntityManager(entityManager); node.getModule().shutdown(shutdownEvent); @@ -147,12 +184,13 @@ public class ModuleManager { System.out.println("Modules shut down."); System.out.println("Checking for modules to uninstall..."); + //Check for modules to uninstall for (final TreeNode node : moduleNodes) { System.out.printf("Checking status of module %s%n", node.getModule().getClass().getName()); final InstalledModule installedModule = entityManager.find( - InstalledModule.class, node. - getModule().getClass().getName().hashCode()); + InstalledModule.class, node. + getModule().getClass().getName().hashCode()); LOGGER.info("Status of module {} ({}): {}", node.getModuleInfo().getModuleName(), node.getModule().getClass().getName(), @@ -165,20 +203,27 @@ public class ModuleManager { node.getModule().getClass().getName()); if (ModuleStatus.UNINSTALL.equals(installedModule.getStatus())) { + //If the current module is marked for uninstall check if there + //modules which depend on the module. If there are now module + //depending on the module create an uninstall event instance + //and call the uninstall method of the module. System.out.printf("Module %s is scheduled for uninstall...%n", node.getModuleInfo().getModuleName()); if (node.getDependentModules().isEmpty()) { System.out. - printf("Calling uninstall method of module %s...%n", - node.getModuleInfo().getModuleName()); + printf("Calling uninstall method of module %s...%n", + node.getModuleInfo().getModuleName()); final UnInstallEvent unInstallEvent = new UnInstallEvent(); unInstallEvent.setEntityManager(entityManager); node.getModule().uninstall(null); } else { + //If there are module depending on the module marked for + //uninstall reset the status of the module to installed. + //Normally this should never happen but just in case... System.out.printf("There are other modules depending on " - + "module %s. Module can't be " - + "uninstalled. Depending modules:%n", + + "module %s. Module can't be " + + "uninstalled. Depending modules:%n", node.getModuleInfo().getModuleName()); for (final TreeNode dependent : node.getDependentModules()) { System.out.printf("\t%s%n", @@ -190,8 +235,8 @@ public class ModuleManager { } } else { System.out.printf( - "Module %s is *not* scheduled for uninstall.%n", - node.getModuleInfo().getModuleName()); + "Module %s is *not* scheduled for uninstall.%n", + node.getModuleInfo().getModuleName()); } } } diff --git a/ccm-core/src/main/java/org/libreccm/modules/ModuleManagerException.java b/ccm-core/src/main/java/org/libreccm/modules/ModuleManagerException.java index ea76080c7..1209f5707 100644 --- a/ccm-core/src/main/java/org/libreccm/modules/ModuleManagerException.java +++ b/ccm-core/src/main/java/org/libreccm/modules/ModuleManagerException.java @@ -19,16 +19,20 @@ package org.libreccm.modules; /** - * + * Thrown by the {@link ModuleManager} if something goes wrong. + * * @author Jens Pelzetter */ public class ModuleManagerException extends RuntimeException { + private static final long serialVersionUID = 1426939919890655697L; + /** * Creates a new instance of ModuleManagerException without * detail message. */ public ModuleManagerException() { + super(); //Nothing } diff --git a/ccm-core/src/main/java/org/libreccm/modules/RequiredModule.java b/ccm-core/src/main/java/org/libreccm/modules/RequiredModule.java index 216ac9eb3..268131e10 100644 --- a/ccm-core/src/main/java/org/libreccm/modules/RequiredModule.java +++ b/ccm-core/src/main/java/org/libreccm/modules/RequiredModule.java @@ -19,15 +19,31 @@ package org.libreccm.modules; /** - * + * Annotation for describing a dependency relation between a module and another + * module. + * * @author Jens Pelzetter */ public @interface RequiredModule { + /** + * The module class required by the module. + * + * @return + */ Class module(); + /** + * The minimal version required by the module. + * @return + */ String minVersion() default ""; + /** + * The maximum version required by the module. + * + * @return + */ String maxVersion() default ""; } diff --git a/ccm-core/src/main/java/org/libreccm/modules/ShutdownEvent.java b/ccm-core/src/main/java/org/libreccm/modules/ShutdownEvent.java index b7d5f082d..5771eb5db 100644 --- a/ccm-core/src/main/java/org/libreccm/modules/ShutdownEvent.java +++ b/ccm-core/src/main/java/org/libreccm/modules/ShutdownEvent.java @@ -19,7 +19,9 @@ package org.libreccm.modules; /** - * + * Event object for the module shutdown. + * + * @see ModuleEvent * @author Jens Pelzetter */ public class ShutdownEvent extends ModuleEvent { diff --git a/ccm-core/src/main/java/org/libreccm/modules/TreeNode.java b/ccm-core/src/main/java/org/libreccm/modules/TreeNode.java index 80418658b..7b7bcc1db 100644 --- a/ccm-core/src/main/java/org/libreccm/modules/TreeNode.java +++ b/ccm-core/src/main/java/org/libreccm/modules/TreeNode.java @@ -24,7 +24,6 @@ import java.util.List; import java.util.Objects; /** - * * Represents a node in the dependency tree. This class is not * part of the public API. * @@ -32,11 +31,26 @@ import java.util.Objects; */ final class TreeNode { + /** + * The module class of the module represented by this node. + */ private CcmModule module; + /** + * The module info for the module. + */ private ModuleInfo moduleInfo; + /** + * The modules depending on the module represented by this tree node. + */ private List dependentModules; + /** + * The modules the module represented by this tree node depends on. + */ private List dependsOn; + /** + * Creates a new empty tree node. + */ public TreeNode() { super(); @@ -44,6 +58,12 @@ final class TreeNode { dependsOn = new ArrayList<>(); } + /** + * Creates a tree node for the provided module. Them module info for the + * module is loaded automatically. + * + * @param module + */ public TreeNode(final CcmModule module) { this(); diff --git a/ccm-core/src/main/java/org/libreccm/modules/UnInstallEvent.java b/ccm-core/src/main/java/org/libreccm/modules/UnInstallEvent.java index 342506d2c..194f0c253 100644 --- a/ccm-core/src/main/java/org/libreccm/modules/UnInstallEvent.java +++ b/ccm-core/src/main/java/org/libreccm/modules/UnInstallEvent.java @@ -19,7 +19,9 @@ package org.libreccm.modules; /** - * + * Event object for the module deinstallation. + * + * @see ModuleEvent * @author Jens Pelzetter */ public class UnInstallEvent extends ModuleEvent {