CanonicalDiagramExportService.java
package com.taxonomy.export.service;
import com.taxonomy.archimate.ArchiMateModel;
import com.taxonomy.archimate.ArchiMateExportMetadata;
import com.taxonomy.diagram.DiagramEdge;
import com.taxonomy.diagram.DiagramModel;
import com.taxonomy.diagram.DiagramNode;
import com.taxonomy.export.ArchiMateDiagramService;
import com.taxonomy.archimate.exchange.ArchiMateXmlExporter;
import com.taxonomy.export.VisioDiagramService;
import com.taxonomy.export.VisioPackageBuilder;
import com.taxonomy.visio.VisioDocument;
import com.taxonomy.visio.VisioExportMetadata;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
/**
* Serializes an already selected canonical diagram without analysis, selection
* or mutable preference lookups.
*/
@Service
public class CanonicalDiagramExportService {
private final VisioDiagramService visioDiagramService;
private final VisioPackageBuilder visioPackageBuilder;
private final ArchiMateDiagramService archiMateDiagramService;
private final ArchiMateXmlExporter archiMateXmlExporter;
public CanonicalDiagramExportService(
VisioDiagramService visioDiagramService,
VisioPackageBuilder visioPackageBuilder,
ArchiMateDiagramService archiMateDiagramService,
ArchiMateXmlExporter archiMateXmlExporter) {
this.visioDiagramService = visioDiagramService;
this.visioPackageBuilder = visioPackageBuilder;
this.archiMateDiagramService = archiMateDiagramService;
this.archiMateXmlExporter = archiMateXmlExporter;
}
public byte[] exportAsArchiMate(DiagramModel canonicalDiagram) {
DiagramModel diagram = requireCanonicalDiagram(canonicalDiagram);
ArchiMateModel model = archiMateDiagramService.convert(diagram);
return archiMateXmlExporter.export(model);
}
public ArchiMateModel prepareArchiMate(DiagramModel canonicalDiagram, ArchiMateExportMetadata metadata) {
return metadata.apply(archiMateDiagramService.convert(requireCanonicalDiagram(canonicalDiagram)));
}
public byte[] exportAsArchiMate(DiagramModel canonicalDiagram, ArchiMateExportMetadata metadata) {
return serializeArchiMate(prepareArchiMate(canonicalDiagram, metadata));
}
/** Serialize and validate an already prepared model once, including bundle exports. */
public byte[] serializeArchiMate(ArchiMateModel model) {
return archiMateXmlExporter.export(model);
}
public byte[] exportAsVisio(DiagramModel canonicalDiagram) {
return exportAsVisio(canonicalDiagram, VisioExportMetadata.unbound());
}
public byte[] exportAsVisio(DiagramModel canonicalDiagram, VisioExportMetadata metadata) {
return serializeVisio(canonicalDiagram, metadata, false);
}
public byte[] exportAsVisioBundle(DiagramModel canonicalDiagram, VisioExportMetadata metadata) {
return serializeVisio(canonicalDiagram, metadata, true);
}
private byte[] serializeVisio(DiagramModel canonicalDiagram, VisioExportMetadata metadata, boolean bundle) {
DiagramModel diagram = requireCanonicalDiagram(canonicalDiagram);
VisioDocument document = visioDiagramService.convert(diagram, metadata);
try {
return bundle ? visioPackageBuilder.buildBundle(document) : visioPackageBuilder.build(document);
} catch (IOException exception) {
throw new UncheckedIOException(
"Could not serialize the canonical diagram as VSDX",
exception);
}
}
private static DiagramModel requireCanonicalDiagram(DiagramModel diagram) {
Objects.requireNonNull(diagram, "canonicalDiagram");
Objects.requireNonNull(diagram.nodes(), "canonicalDiagram.nodes");
Objects.requireNonNull(diagram.edges(), "canonicalDiagram.edges");
Objects.requireNonNull(diagram.layout(), "canonicalDiagram.layout");
requireNonBlank(
diagram.layout().direction(),
"canonicalDiagram.layout.direction");
Set<String> nodeIds = new HashSet<>();
for (int index = 0; index < diagram.nodes().size(); index++) {
DiagramNode node = diagram.nodes().get(index);
if (node == null) {
throw invalid("Canonical node " + index + " must not be null");
}
String id = requireNonBlank(
node.id(), "Canonical node " + index + " identifier");
if (!nodeIds.add(id)) {
throw invalid("Duplicate canonical node identifier " + id);
}
requireNonBlank(node.label(), "Canonical node " + id + " label");
requireNonBlank(node.type(), "Canonical node " + id + " type");
requireUnitInterval(
node.relevance(), "Canonical node " + id + " relevance");
}
for (DiagramNode node : diagram.nodes()) {
String parentId = node.parentId();
if (parentId == null) {
continue;
}
parentId = requireNonBlank(
parentId, "Canonical node " + node.id() + " parent");
if (!nodeIds.contains(parentId)) {
throw invalid("Canonical node " + node.id()
+ " parent does not resolve: " + parentId);
}
if (node.id().equals(parentId)) {
throw invalid("Canonical node " + node.id()
+ " must not be its own parent");
}
}
Set<String> edgeIds = new HashSet<>();
for (int index = 0; index < diagram.edges().size(); index++) {
DiagramEdge edge = diagram.edges().get(index);
if (edge == null) {
throw invalid("Canonical relation " + index + " must not be null");
}
String id = requireNonBlank(
edge.id(), "Canonical relation " + index + " identifier");
if (!edgeIds.add(id)) {
throw invalid("Duplicate canonical relation identifier " + id);
}
String sourceId = requireNonBlank(
edge.sourceId(), "Canonical relation " + id + " source");
String targetId = requireNonBlank(
edge.targetId(), "Canonical relation " + id + " target");
if (!nodeIds.contains(sourceId)) {
throw invalid("Canonical relation " + id
+ " source does not resolve: " + sourceId);
}
if (!nodeIds.contains(targetId)) {
throw invalid("Canonical relation " + id
+ " target does not resolve: " + targetId);
}
requireNonBlank(
edge.relationType(),
"Canonical relation " + id + " type");
requireUnitInterval(
edge.relevance(),
"Canonical relation " + id + " relevance");
}
return diagram;
}
private static String requireNonBlank(String value, String field) {
if (value == null || value.isBlank()) {
throw invalid(field + " must not be blank");
}
return value;
}
private static void requireUnitInterval(double value, String field) {
if (!Double.isFinite(value) || value < 0.0 || value > 1.0) {
throw invalid(field
+ " must be a finite value between 0.0 and 1.0, but was "
+ value);
}
}
private static IllegalArgumentException invalid(String message) {
return new IllegalArgumentException(message);
}
}