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 extends CcmModule> 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 extends CcmModule> moduleClass) {
+ final Class extends CcmModule> 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 extends CcmModule> 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 extends CcmModule> moduleClass,
- final Module annotation,
- final Properties moduleInfo) {
+ final Class extends CcmModule> 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 extends CcmModule> 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 {