-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge partial transfer files with base data (#2)
- Loading branch information
Showing
33 changed files
with
1,000 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
46 changes: 46 additions & 0 deletions
46
src/main/java/ch/geowerkstatt/interlis/testbed/runner/xtf/Basket.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package ch.geowerkstatt.interlis.testbed.runner.xtf; | ||
|
||
import org.w3c.dom.Element; | ||
import org.w3c.dom.Node; | ||
|
||
import java.util.Map; | ||
|
||
public record Basket(Element element, Map<String, Element> objects) { | ||
/** | ||
* Adds or replaces the child node with the given entry ID. | ||
* | ||
* @param entryId the entry ID | ||
* @param node the node to add or replace | ||
*/ | ||
public void addOrReplaceChildNode(String entryId, Node node) { | ||
var originalEntry = objects().get(entryId); | ||
if (originalEntry == null) { | ||
element().appendChild(node); | ||
} else { | ||
element().replaceChild(node, originalEntry); | ||
} | ||
} | ||
|
||
/** | ||
* Removes the basket node from the XML document. | ||
*/ | ||
public void removeBasketNode() { | ||
element().getParentNode().removeChild(element()); | ||
} | ||
|
||
/** | ||
* Removes the child node with the given entry ID. | ||
* | ||
* @param entryId the ID of the entry to remove | ||
* @return {@code true} if the entry was removed, {@code false} if the entry was not found | ||
*/ | ||
public boolean removeChildNode(String entryId) { | ||
var originalEntry = objects().get(entryId); | ||
if (originalEntry == null) { | ||
return false; | ||
} | ||
|
||
element().removeChild(originalEntry); | ||
return true; | ||
} | ||
} |
194 changes: 194 additions & 0 deletions
194
src/main/java/ch/geowerkstatt/interlis/testbed/runner/xtf/XtfFileMerger.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,194 @@ | ||
package ch.geowerkstatt.interlis.testbed.runner.xtf; | ||
|
||
import org.apache.logging.log4j.LogManager; | ||
import org.apache.logging.log4j.Logger; | ||
import org.w3c.dom.Document; | ||
import org.w3c.dom.Element; | ||
import org.w3c.dom.Node; | ||
|
||
import javax.xml.parsers.DocumentBuilder; | ||
import javax.xml.parsers.DocumentBuilderFactory; | ||
import javax.xml.parsers.ParserConfigurationException; | ||
import javax.xml.transform.TransformerException; | ||
import javax.xml.transform.TransformerFactory; | ||
import javax.xml.transform.dom.DOMSource; | ||
import javax.xml.transform.stream.StreamResult; | ||
import java.io.IOException; | ||
import java.nio.file.Files; | ||
import java.nio.file.Path; | ||
import java.util.Map; | ||
import java.util.Optional; | ||
import java.util.stream.Collectors; | ||
import java.util.stream.IntStream; | ||
import java.util.stream.Stream; | ||
|
||
public final class XtfFileMerger implements XtfMerger { | ||
private static final Logger LOGGER = LogManager.getLogger(); | ||
private static final String BASKET_ID = "BID"; | ||
private static final String OBJECT_ID = "TID"; | ||
private static final String DELETE_ATTRIBUTE = "DELETE"; | ||
private static final String DELETE_ATTRIBUTE_LOWERCASE = DELETE_ATTRIBUTE.toLowerCase(); | ||
private static final String INTERLIS24_NAMESPACE = "http://www.interlis.ch/xtf/2.4/INTERLIS"; | ||
|
||
private final DocumentBuilderFactory factory; | ||
|
||
/** | ||
* Creates a new instance of the XtfFileMerger class. | ||
*/ | ||
public XtfFileMerger() { | ||
factory = DocumentBuilderFactory.newInstance(); | ||
factory.setNamespaceAware(true); | ||
} | ||
|
||
@Override | ||
public boolean merge(Path baseFile, Path patchFile, Path outputFile) { | ||
try { | ||
LOGGER.info("Merging " + baseFile + " with " + patchFile + " into " + outputFile); | ||
var documentBuilder = createDocumentBuilder(); | ||
|
||
var baseDocument = documentBuilder.parse(baseFile.toFile()); | ||
var patchDocument = documentBuilder.parse(patchFile.toFile()); | ||
|
||
var baseBaskets = findBaskets(baseDocument); | ||
if (baseBaskets.isEmpty()) { | ||
LOGGER.error("No baskets found in base file " + baseFile + "."); | ||
return false; | ||
} | ||
|
||
var patchBaskets = findBaskets(patchDocument); | ||
if (patchBaskets.isEmpty()) { | ||
LOGGER.error("No baskets found in patch file " + patchFile + "."); | ||
return false; | ||
} | ||
|
||
if (!mergeBaskets(baseDocument, baseBaskets.get(), patchBaskets.get())) { | ||
return false; | ||
} | ||
|
||
writeMergedFile(baseDocument, outputFile); | ||
LOGGER.info("Successfully merged files into " + outputFile); | ||
return true; | ||
} catch (Exception e) { | ||
LOGGER.error("Failed to merge files.", e); | ||
return false; | ||
} | ||
} | ||
|
||
DocumentBuilder createDocumentBuilder() throws ParserConfigurationException { | ||
return factory.newDocumentBuilder(); | ||
} | ||
|
||
private static boolean mergeBaskets(Document document, Map<String, Basket> baseBaskets, Map<String, Basket> patchBaskets) { | ||
var isValid = true; | ||
|
||
for (var patchBasketEntry : patchBaskets.entrySet()) { | ||
var basketId = patchBasketEntry.getKey(); | ||
var patchBasket = patchBasketEntry.getValue(); | ||
|
||
var originalBasket = baseBaskets.get(basketId); | ||
if (originalBasket == null) { | ||
LOGGER.error("Basket " + basketId + " not found in base file."); | ||
isValid = false; | ||
continue; | ||
} | ||
|
||
if (hasDeleteAttribute(patchBasket.element())) { | ||
originalBasket.removeBasketNode(); | ||
continue; | ||
} | ||
|
||
for (var patchEntry : patchBasket.objects().entrySet()) { | ||
var entryId = patchEntry.getKey(); | ||
var element = patchEntry.getValue(); | ||
|
||
if (hasDeleteAttribute(element)) { | ||
if (!originalBasket.removeChildNode(entryId)) { | ||
LOGGER.error("Could not remove entry " + entryId + " from basket " + basketId + " as it does not exist."); | ||
isValid = false; | ||
} | ||
} else { | ||
var importedNode = document.importNode(element, true); | ||
originalBasket.addOrReplaceChildNode(entryId, importedNode); | ||
} | ||
} | ||
} | ||
|
||
return isValid; | ||
} | ||
|
||
private static void writeMergedFile(Document document, Path outputFile) throws IOException, TransformerException { | ||
Files.createDirectories(outputFile.getParent()); | ||
|
||
var transformerFactory = TransformerFactory.newInstance(); | ||
var transformer = transformerFactory.newTransformer(); | ||
var source = new DOMSource(document); | ||
var result = new StreamResult(outputFile.toFile()); | ||
transformer.transform(source, result); | ||
} | ||
|
||
static Optional<Map<String, Basket>> findBaskets(Document document) { | ||
var dataSection = findDataSection(document); | ||
if (dataSection.isEmpty()) { | ||
return Optional.empty(); | ||
} | ||
|
||
var baskets = streamChildElementNodes(dataSection.get()) | ||
.filter(e -> { | ||
var hasId = hasInterlisAttribute(e, BASKET_ID); | ||
if (!hasId) { | ||
LOGGER.warn("Basket without " + BASKET_ID + " found."); | ||
} | ||
return hasId; | ||
}) | ||
.collect(Collectors.toMap(e -> getInterlisAttribute(e, BASKET_ID), XtfFileMerger::collectBasket)); | ||
return Optional.of(baskets); | ||
} | ||
|
||
private static Basket collectBasket(Element basket) { | ||
var objects = streamChildElementNodes(basket) | ||
.filter(e -> { | ||
var hasId = hasInterlisAttribute(e, OBJECT_ID); | ||
if (!hasId) { | ||
LOGGER.warn("Entry without " + OBJECT_ID + " found in basket " + basket.getAttribute(BASKET_ID) + "."); | ||
} | ||
return hasId; | ||
}) | ||
.collect(Collectors.toMap(e -> getInterlisAttribute(e, OBJECT_ID), e -> e)); | ||
return new Basket(basket, objects); | ||
} | ||
|
||
private static boolean hasDeleteAttribute(Element element) { | ||
return element.hasAttribute(DELETE_ATTRIBUTE) || element.hasAttribute(DELETE_ATTRIBUTE_LOWERCASE); | ||
} | ||
|
||
private static boolean hasInterlisAttribute(Element element, String attributeName) { | ||
return getInterlisAttribute(element, attributeName) != null; | ||
} | ||
|
||
private static String getInterlisAttribute(Element element, String attributeName) { | ||
if (element.hasAttribute(attributeName)) { | ||
return element.getAttribute(attributeName); | ||
} | ||
|
||
var ili24Name = attributeName.toLowerCase(); | ||
if (element.hasAttributeNS(INTERLIS24_NAMESPACE, ili24Name)) { | ||
return element.getAttributeNS(INTERLIS24_NAMESPACE, ili24Name); | ||
} | ||
return null; | ||
} | ||
|
||
private static Optional<Element> findDataSection(Document document) { | ||
var transfer = document.getFirstChild(); | ||
return streamChildElementNodes(transfer) | ||
.filter(n -> n.getLocalName().equalsIgnoreCase("datasection")) | ||
.findFirst(); | ||
} | ||
|
||
private static Stream<Element> streamChildElementNodes(Node node) { | ||
var childNodes = node.getChildNodes(); | ||
return IntStream.range(0, childNodes.getLength()) | ||
.mapToObj(childNodes::item) | ||
.filter(n -> n instanceof Element) | ||
.map(n -> (Element) n); | ||
} | ||
} |
15 changes: 15 additions & 0 deletions
15
src/main/java/ch/geowerkstatt/interlis/testbed/runner/xtf/XtfMerger.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package ch.geowerkstatt.interlis.testbed.runner.xtf; | ||
|
||
import java.nio.file.Path; | ||
|
||
public interface XtfMerger { | ||
/** | ||
* Merges the patch file into the base file and writes the result to the output file. | ||
* | ||
* @param baseFile the base file | ||
* @param patchFile the patch file | ||
* @param outputFile the output file | ||
* @return {@code true} if the merge was successful, {@code false} otherwise. | ||
*/ | ||
boolean merge(Path baseFile, Path patchFile, Path outputFile); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<TRANSFER xmlns="http://www.interlis.ch/INTERLIS2.3"> | ||
<HEADERSECTION SENDER="interlis-testbed-runner" VERSION="2.3"> | ||
<MODELS> | ||
</MODELS> | ||
</HEADERSECTION> | ||
<DATASECTION> | ||
<ModelA.TopicA BID="B1"> | ||
<ModelA.TopicA.ClassA TID="A1_1"> | ||
<attr1>Some text</attr1> | ||
<attr2>Some more text</attr2> | ||
</ModelA.TopicA.ClassA> | ||
</ModelA.TopicA> | ||
|
||
<ModelA.TopicA BID="B2"> | ||
<ModelA.TopicA.ClassA TID="A2_1"> | ||
<attr1>Some text</attr1> | ||
<attr2>Some more text</attr2> | ||
</ModelA.TopicA.ClassA> | ||
</ModelA.TopicA> | ||
|
||
<ModelA.TopicA BID="B3"> | ||
</ModelA.TopicA> | ||
</DATASECTION> | ||
</TRANSFER> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
<?xml version="1.0" encoding="UTF-8" standalone="no"?> | ||
<TRANSFER xmlns="http://www.interlis.ch/INTERLIS2.3"> | ||
<HEADERSECTION SENDER="interlis-testbed-runner" VERSION="2.3"> | ||
<MODELS> | ||
</MODELS> | ||
</HEADERSECTION> | ||
<DATASECTION> | ||
<ModelA.TopicA BID="B1"> | ||
<ModelA.TopicA.ClassA TID="A1_1"> | ||
<attr1>Some text</attr1> | ||
<attr2>Some more text</attr2> | ||
</ModelA.TopicA.ClassA> | ||
<ModelA.TopicA.ClassA TID="A1_2"> | ||
<attr1>New entry</attr1> | ||
<attr2>Attr2</attr2> | ||
</ModelA.TopicA.ClassA> | ||
</ModelA.TopicA> | ||
|
||
<ModelA.TopicA BID="B2"> | ||
<ModelA.TopicA.ClassA TID="A2_1"> | ||
<attr1>Some text</attr1> | ||
<attr2>Some more text</attr2> | ||
</ModelA.TopicA.ClassA> | ||
</ModelA.TopicA> | ||
|
||
<ModelA.TopicA BID="B3"> | ||
<ModelA.TopicA.ClassA TID="A3_1"> | ||
<attr1>Attribute 1</attr1> | ||
<attr2>Attribute 2</attr2> | ||
</ModelA.TopicA.ClassA> | ||
</ModelA.TopicA> | ||
</DATASECTION> | ||
</TRANSFER> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<TRANSFER xmlns="http://www.interlis.ch/INTERLIS2.3"> | ||
<DATASECTION> | ||
<ModelA.TopicA BID="B1"> | ||
<ModelA.TopicA.ClassA TID="A1_2"> | ||
<attr1>New entry</attr1> | ||
<attr2>Attr2</attr2> | ||
</ModelA.TopicA.ClassA> | ||
</ModelA.TopicA> | ||
|
||
<ModelA.TopicA BID="B3"> | ||
<ModelA.TopicA.ClassA TID="A3_1"> | ||
<attr1>Attribute 1</attr1> | ||
<attr2>Attribute 2</attr2> | ||
</ModelA.TopicA.ClassA> | ||
</ModelA.TopicA> | ||
</DATASECTION> | ||
</TRANSFER> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<TRANSFER xmlns="http://www.interlis.ch/INTERLIS2.3"> | ||
<HEADERSECTION SENDER="interlis-testbed-runner" VERSION="2.3"> | ||
<MODELS> | ||
</MODELS> | ||
</HEADERSECTION> | ||
<DATASECTION> | ||
<ModelA.TopicA BID="B1"> | ||
<ModelA.TopicA.ClassA TID="A1_1"> | ||
<attr1>Some text</attr1> | ||
<attr2>Some more text</attr2> | ||
<line> | ||
<POLYLINE> | ||
<COORD> | ||
<C1>1.0</C1> | ||
<C2>2.0</C2> | ||
</COORD> | ||
<COORD> | ||
<C1>1.5</C1> | ||
<C2>2.5</C2> | ||
</COORD> | ||
</POLYLINE> | ||
</line> | ||
</ModelA.TopicA.ClassA> | ||
<ModelA.TopicA.ClassA TID="A1_2"> | ||
<attr1>Some text</attr1> | ||
<attr2>Some more text</attr2> | ||
</ModelA.TopicA.ClassA> | ||
</ModelA.TopicA> | ||
|
||
<ModelA.TopicA BID="B2"> | ||
<ModelA.TopicA.ClassA TID="A2_1"> | ||
<attr1>Some text</attr1> | ||
<attr2>Some more text</attr2> | ||
</ModelA.TopicA.ClassA> | ||
</ModelA.TopicA> | ||
</DATASECTION> | ||
</TRANSFER> |
Oops, something went wrong.