CCM NG: Revised Import/Export system
git-svn-id: https://svn.libreccm.org/ccm/ccm_ng@5698 8810af33-2d31-482b-a856-94f89814c4dfpull/2/head
parent
1bd1c69ed4
commit
b44b0b726f
|
|
@ -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 <strong>not</strong> not part of the public API.
|
||||
*
|
||||
** More information about topological sorting:
|
||||
* <a href="https://en.wikipedia.org/wiki/Topological_sorting">https://en.wikipedia.org/wiki/Topological_sorting</a>
|
||||
*
|
||||
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
|
||||
*/
|
||||
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<EntityImExporterTreeNode> generateTree(
|
||||
final List<EntityImExporter<?>> 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<String, EntityImExporterTreeNode> 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<EntityImExporterTreeNode> nodeList = new ArrayList<>();
|
||||
for (final Map.Entry<String, EntityImExporterTreeNode> entry
|
||||
: nodes.entrySet()) {
|
||||
|
||||
for (final Map.Entry<String, EntityImExporterTreeNode> 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<EntityImExporterTreeNode> orderImExporters(
|
||||
final List<EntityImExporterTreeNode> nodes)
|
||||
throws DependencyException {
|
||||
|
||||
LOGGER.info("Creating an ordered list from the dependency tree...");
|
||||
|
||||
//List for the ordered and resolved nodes.
|
||||
final List<EntityImExporterTreeNode> orderedNodes = new ArrayList<>();
|
||||
final List<EntityImExporterTreeNode> 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<String, EntityImExporterTreeNode> 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<String, EntityImExporterTreeNode> 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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<EntityImExporter<?>> 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<EntityImExporterTreeNode> tree = treeManager
|
||||
.generateTree(
|
||||
imExportersList);
|
||||
final List<EntityImExporterTreeNode> 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<String> 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<String> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 <strong>not</strong> 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<TreeNode> generateTree(final Iterable<CcmModule> 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<TreeNode> orderModules(final List<TreeNode> 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<String, TreeNode> 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<String, TreeNode> 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(
|
||||
|
|
|
|||
Loading…
Reference in New Issue