diff --git a/ccm-core/src/main/java/org/libreccm/imexport/EntityImExporterTreeManager.java b/ccm-core/src/main/java/org/libreccm/imexport/EntityImExporterTreeManager.java
index 23445348c..26f6529bc 100644
--- a/ccm-core/src/main/java/org/libreccm/imexport/EntityImExporterTreeManager.java
+++ b/ccm-core/src/main/java/org/libreccm/imexport/EntityImExporterTreeManager.java
@@ -18,21 +18,57 @@
*/
package org.libreccm.imexport;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
+ * This class implements topological sorting to determine the order in which
+ * entities are imported.
+ *
+ * The class is used by creating an instance with the parameterless constructor.
+ * To create the tree/graph call the null {@link #generateTree(java.util.List)}
+ * method. With the returned list of nodes call the
+ * {@link #orderImExporters(java.util.List)} method. The list returned by
+ * {@link #orderImExporters(java.util.List)} contains all
+ * {@link EntityImExporter} in the order.
+ *
+ * This class is not not part of the public API.
+ *
+ ** More information about topological sorting:
+ * https://en.wikipedia.org/wiki/Topological_sorting
*
* @author Jens Pelzetter
*/
final class EntityImExporterTreeManager {
+ private static final Logger LOGGER = LogManager
+ .getLogger(EntityImExporterTreeManager.class);
+
+ /**
+ * Initialises the tree with the provided list of {@link EntityImExporter}s.
+ *
+ * @param imExporters The available {@link EntityImExporter}s.
+ *
+ * @return An ordered list of the 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 List> imExporters)
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 = imExporters
.stream()
.map(EntityImExporterTreeNode::new)
@@ -46,32 +82,139 @@ final class EntityImExporterTreeManager {
.getName(),
node -> node));
+ //Add the dependency relations to the nodes
for (final EntityImExporter> imExporter : imExporters) {
addDependencyRelations(imExporter, nodes);
}
-
+
+ //Generate the node list
final List nodeList = new ArrayList<>();
- for (final Map.Entry entry
- : nodes.entrySet()) {
-
+ for (final Map.Entry entry
+ : nodes.entrySet()) {
+
nodeList.add(entry.getValue());
}
-
+
+ LOGGER.info("Dependency tree generated.");
+
return nodeList;
}
+ /**
+ * Generates an ordered list of the tree nodes which can be used to process
+ * the imports in the correct order.
+ *
+ * In this method the topological sorting happens.
+ *
+ * @param nodes The nodes of the dependency tree.
+ *
+ * @return A ordered list of the tree nodes.
+ *
+ * @throws DependencyException If something is wrong with dependency graph.
+ */
+ public List orderImExporters(
+ final List nodes)
+ throws DependencyException {
+
+ LOGGER.info("Creating an ordered list from the dependency tree...");
+
+ //List for the ordered and resolved nodes.
+ final List orderedNodes = new ArrayList<>();
+ final List resolvedNodes = new ArrayList<>();
+
+ LOGGER.info("Looking for EntityImExporters which do not depend on any "
+ + "other EntityImExporters.");
+ //Find all nodes which do not depend on any other nodes. These
+ //nodes are used as starting point for the sorting.
+ for (final EntityImExporterTreeNode node : nodes) {
+
+ if (node.getDependsOn().isEmpty()) {
+ LOGGER.info(
+ "\tNode \"{}\" does not depend on any other module",
+ node.getEntityImExporter().getClass().getName());
+ resolvedNodes.add(node);
+ }
+ }
+
+ LOGGER.info("Ordering remaining nodes...");
+ while (!resolvedNodes.isEmpty()) {
+
+ //Remove the first node from the resolved nodes list
+ final EntityImExporterTreeNode current = resolvedNodes.remove(0);
+ LOGGER.info("\tProcessing node for EntityImExporter \"{}\"...",
+ current.getEntityImExporter().getClass().getName());
+
+ //Add the node to the ordered modules list.
+ orderedNodes.add(current);
+
+ //Remove the edges to the current node.
+ for (final EntityImExporterTreeNode dependent
+ : current.getDependentImExporters()) {
+
+ dependent.removeDependsOn(current);
+
+ //If the dependent node has no more dependsOn relations put
+ //the node into the resolved modules list.
+ if (dependent.getDependsOn().isEmpty()) {
+ resolvedNodes.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 (orderedNodes.size() == nodes.size()) {
+
+ LOGGER.info("EntityImExporter dependency graph processed "
+ + "successfully. EntityImExporters in order:");
+ for (final EntityImExporterTreeNode node : orderedNodes) {
+ LOGGER.info("\t{}",
+ node.getEntityImExporter().getClass().getName());
+ }
+
+ return orderedNodes;
+
+ } else {
+
+ LOGGER.fatal("The EntityImExporter dependency graph has at least "
+ + "one cycle.");
+ throw new DependencyException("The EntityImExporter dependency "
+ + "graph has at least one cycle.");
+ }
+
+ }
+
+ /**
+ * Helper method for adding the dependency relations for an
+ * {@link EntityImExporter} to the nodes.
+ *
+ * @param imExporter The current {@link EntityImExporter}.
+ * @param nodes The map of nodes.
+ *
+ * @throws DependencyException If something goes wrong.
+ */
private void addDependencyRelations(
final EntityImExporter> imExporter,
final Map nodes)
throws DependencyException {
+ //Get the dependencies of the current EntityImExporter
final Processes processes = imExporter
.getClass()
.getAnnotation(Processes.class);
- final String className = imExporter.getClass().getName();
+ //Get the name of the module from the module info.
+ final String className = imExporter.getClass().getName();
+ LOGGER
+ .info("Adding dependency relations for EntityImExporter \"{}\"...",
+ className);
+
+ //Check if the nodes map has an entry for the EntityImExporter.
if (!nodes.containsKey(className)) {
+ LOGGER.fatal("EntityImExporter nodes map does contain an entry for "
+ + "\"{}\". That should not happen.",
+ className);
throw new IllegalArgumentException(String.format(
"The nodes map does not contain a node for "
+ "EntityImExporter \"%s\"."
@@ -79,34 +222,59 @@ final class EntityImExporterTreeManager {
className));
}
+ //Get the node from the map
final EntityImExporterTreeNode node = nodes.get(className);
-
+ LOGGER
+ .info("Processing required modules for EntityImExporter \"{}\"...",
+ className);
+ //Process the EntityImExporter required by the current module and add
+ //the dependency relations.
for (final Class extends Exportable> clazz : processes.dependsOn()) {
addDependencyRelation(nodes, node, clazz);
}
}
+ /**
+ * 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 requiredClass The type which is required by the current
+ * module/node.
+ *
+ * @throws DependencyException
+ */
private void addDependencyRelation(
final Map nodes,
EntityImExporterTreeNode node,
- Class extends Exportable> clazz)
+ Class extends Exportable> requiredClass)
throws DependencyException {
- if (!nodes.containsKey(clazz.getName())) {
+ LOGGER.info("\tEntityImExporter for \"{}\" requires "
+ + "EntityImExporter for \"{}\".",
+ node.getEntityImExporter().getClass().getName(),
+ requiredClass.getName());
+ //Check if the nodes list has an entry for the required module.
+ if (!nodes.containsKey(requiredClass.getName())) {
+
+ LOGGER.fatal("Required EntityImExporter for \"{}\" no found.",
+ requiredClass.getName());
throw new DependencyException(String.format(
"EntityImExporter for type \"%s\" depends on type \"%s\" "
+ "but no EntityImExporter for type \"%s\" is available.",
node.getEntityImExporter().getClass().getAnnotation(
Processes.class).type().getName(),
- clazz.getName(),
- clazz.getName()));
+ requiredClass.getName(),
+ requiredClass.getName()));
}
final EntityImExporterTreeNode dependencyNode = nodes
- .get(clazz.getName());
+ .get(requiredClass.getName());
+ //Create the dependencies relations.
node.addDependsOn(dependencyNode);
dependencyNode.addDependentImExporter(node);
}
diff --git a/ccm-core/src/main/java/org/libreccm/imexport/ImportExport.java b/ccm-core/src/main/java/org/libreccm/imexport/ImportExport.java
index 816cd5f03..3c81f202e 100644
--- a/ccm-core/src/main/java/org/libreccm/imexport/ImportExport.java
+++ b/ccm-core/src/main/java/org/libreccm/imexport/ImportExport.java
@@ -102,26 +102,30 @@ public class ImportExport {
importName));
}
} catch (FileAccessException
- | FileDoesNotExistException
- | InsufficientPermissionsException ex) {
+ | FileDoesNotExistException
+ | InsufficientPermissionsException ex) {
throw new UnexpectedErrorException(ex);
}
- final String manifestPath = String.format("%s/ccm-export.json",
- importsPath);
- try (final InputStream manifestInputStream = ccmFiles
- .createInputStream(importsPath)) {
+ final List> imExportersList = new ArrayList<>();
+ imExporters.forEach(imExporter -> imExportersList.add(imExporter));
- final JsonReader manifestReader = Json
- .createReader(manifestInputStream);
- final JsonObject manifest = manifestReader.readObject();
+ try {
+ final EntityImExporterTreeManager treeManager
+ = new EntityImExporterTreeManager();
+ final List tree = treeManager
+ .generateTree(
+ imExportersList);
+ final List orderedNodes = treeManager
+ .orderImExporters(tree);
- } catch (IOException
- | FileDoesNotExistException
- | FileAccessException
- | InsufficientPermissionsException ex) {
+ final ImportManifest manifest = createImportManifest(importName);
+ //ToDo
+
+ } catch (DependencyException ex) {
+ throw new UnexpectedErrorException(ex);
}
throw new UnsupportedOperationException();
@@ -134,8 +138,8 @@ public class ImportExport {
try {
importArchivePaths = ccmFiles.listFiles("imports");
} catch (FileAccessException
- | FileDoesNotExistException
- | InsufficientPermissionsException ex) {
+ | FileDoesNotExistException
+ | InsufficientPermissionsException ex) {
throw new UnexpectedErrorException(ex);
}
@@ -168,52 +172,53 @@ public class ImportExport {
final String manifestPath = String.format("imports/%s/ccm-export.json",
path);
- final InputStream inputStream;
- try {
- inputStream = ccmFiles.createInputStream(manifestPath);
- } catch (FileAccessException
- | FileDoesNotExistException
- | InsufficientPermissionsException ex) {
+ try (final InputStream inputStream = ccmFiles
+ .createInputStream(manifestPath)) {
+
+ final JsonReader reader = Json.createReader(inputStream);
+ final JsonObject manifestJson = reader.readObject();
+
+ if (!manifestJson.containsKey("created")) {
+ throw new IllegalArgumentException(String.format(
+ "The manifest file \"%s\" is malformed. "
+ + "Key \"created\" is missing.",
+ manifestPath));
+ }
+
+ if (!manifestJson.containsKey("onServer")) {
+ throw new IllegalArgumentException(String.format(
+ "The manifest file \"%s\" is malformed. "
+ + "Key \"onServer\" is missing.",
+ manifestPath));
+ }
+
+ if (!manifestJson.containsKey("types")) {
+ throw new IllegalArgumentException(String.format(
+ "The manifest file \"%s\" is malformed. "
+ + "Key \"types\" is missing.",
+ manifestPath));
+ }
+
+ final LocalDateTime created = LocalDateTime
+ .parse(manifestJson.getString("created"));
+ final String onServer = manifestJson.getString("onServer");
+ final List types = manifestJson.getJsonArray("types")
+ .stream()
+ .map(value -> value.toString())
+ .collect(Collectors.toList());
+
+ return new ImportManifest(
+ Date.from(created.atZone(ZoneId.of("UTC")).toInstant()),
+ onServer,
+ types);
+ } catch (IOException
+ | FileAccessException
+ | FileDoesNotExistException
+ | InsufficientPermissionsException ex) {
throw new UnexpectedErrorException(ex);
}
- final JsonReader reader = Json.createReader(inputStream);
- final JsonObject manifestJson = reader.readObject();
-
- if (!manifestJson.containsKey("created")) {
- throw new IllegalArgumentException(String.format(
- "The manifest file \"%s\" is malformed. "
- + "Key \"created\" is missing.",
- manifestPath));
- }
-
- if (!manifestJson.containsKey("onServer")) {
- throw new IllegalArgumentException(String.format(
- "The manifest file \"%s\" is malformed. "
- + "Key \"onServer\" is missing.",
- manifestPath));
- }
-
- if (!manifestJson.containsKey("types")) {
- throw new IllegalArgumentException(String.format(
- "The manifest file \"%s\" is malformed. "
- + "Key \"types\" is missing.",
- manifestPath));
- }
-
- final LocalDateTime created = LocalDateTime
- .parse(manifestJson.getString("created"));
- final String onServer = manifestJson.getString("onServer");
- final List types = manifestJson.getJsonArray("types")
- .stream()
- .map(value -> value.toString())
- .collect(Collectors.toList());
-
- return new ImportManifest(
- Date.from(created.atZone(ZoneId.of("UTC")).toInstant()),
- onServer,
- types);
}
}
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 3da274121..3dce0d056 100644
--- a/ccm-core/src/main/java/org/libreccm/modules/DependencyTreeManager.java
+++ b/ccm-core/src/main/java/org/libreccm/modules/DependencyTreeManager.java
@@ -34,10 +34,10 @@ import java.util.Map;
* The class is used by creating an instance with the parameterless constructor.
* To create the tree/graph call the
* {@link #generateTree(javax.enterprise.inject.Instance)} method. With the
- * returned list of nodes call the the {@link #orderModules(java.util.List)}
+ * returned list of nodes call 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:
@@ -52,13 +52,15 @@ final class DependencyTreeManager {
/**
* 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.
+ *
+ * @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 {
@@ -89,15 +91,17 @@ final class DependencyTreeManager {
}
/**
- * Generates an ordered list of the tree nodes which can be used to
+ * 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.
+ *
+ * @throws DependencyException If something is wrong with the dependency
+ * tree.
*/
public List orderModules(final List dependencyTree)
throws DependencyException {
@@ -127,7 +131,7 @@ final class DependencyTreeManager {
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);
@@ -135,7 +139,7 @@ final class DependencyTreeManager {
for (final TreeNode dependent : current.getDependentModules()) {
dependent.removeDependsOn(current);
- //If the dependent node has node more dependsOn relations put
+ //If the dependent node has no more dependsOn relations put
//the node into the resolved modules list.
if (dependent.getDependsOn().isEmpty()) {
resolvedModules.add(dependent);
@@ -172,13 +176,13 @@ final class DependencyTreeManager {
}
/**
- * Helper method for adding the dependency relations for a module to the
+ * 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.
+ * @param nodes The map of nodes.
+ *
+ * @throws DependencyException If something goes wrong.
*/
private void addDependencyRelations(final CcmModule module,
final Map nodes)
@@ -220,11 +224,13 @@ final class DependencyTreeManager {
/**
* 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 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
+ *
+ * @throws DependencyException
*/
private void addDependencyRelation(final Map nodes,
final TreeNode node,
@@ -301,11 +307,11 @@ final class DependencyTreeManager {
if ((minRequiredVersion == null || minRequiredVersion.isEmpty())
&& (maxRequiredVersion == null || maxRequiredVersion.
- isEmpty())) {
+ isEmpty())) {
return true;
} else if ((minRequiredVersion != null && !minRequiredVersion.isEmpty())
&& (maxRequiredVersion == null || maxRequiredVersion
- .isEmpty())) {
+ .isEmpty())) {
final ComparableVersion minVersion = new ComparableVersion(
minRequiredVersion);
final ComparableVersion version = new ComparableVersion(
@@ -314,7 +320,7 @@ final class DependencyTreeManager {
return minVersion.compareTo(version) <= 0;
} else if ((minRequiredVersion == null || minRequiredVersion.isEmpty())
&& (maxRequiredVersion != null && !maxRequiredVersion
- .isEmpty())) {
+ .isEmpty())) {
final ComparableVersion maxVersion = new ComparableVersion(
maxRequiredVersion);
final ComparableVersion version = new ComparableVersion(