From d82cf687f78865bc9a0f309d7e40f53c948c20ba Mon Sep 17 00:00:00 2001
From: Thomas Papke <web@thopap.de>
Date: Mon, 18 Oct 2021 20:56:53 +0200
Subject: [PATCH] #365 XDS ExtraMetadata slots might get duplicated if
 SubmitObjectsRequest's are unmarshalled and marshalled on the same instance

* Fix extra metadata slot duplication
---
 .../ihe/xds/core/XdsJaxbDataBinding.java      |   2 +
 .../transform/ebxml/Ebrs30MarshalingTest.java |  60 +++--
 ...mitObjectsRequest_ebrs3_extra_metadata.xml | 244 ++++++++++++++++++
 3 files changed, 286 insertions(+), 20 deletions(-)
 create mode 100644 commons/ihe/xds/src/test/resources/SubmitObjectsRequest_ebrs3_extra_metadata.xml

diff --git a/commons/ihe/xds/src/main/java/org/openehealth/ipf/commons/ihe/xds/core/XdsJaxbDataBinding.java b/commons/ihe/xds/src/main/java/org/openehealth/ipf/commons/ihe/xds/core/XdsJaxbDataBinding.java
index 14df8e3513..d168ffbadf 100644
--- a/commons/ihe/xds/src/main/java/org/openehealth/ipf/commons/ihe/xds/core/XdsJaxbDataBinding.java
+++ b/commons/ihe/xds/src/main/java/org/openehealth/ipf/commons/ihe/xds/core/XdsJaxbDataBinding.java
@@ -122,6 +122,8 @@ public void beforeMarshal(Object source) {
 
         private static void injectExtraMetadata(List<SlotType1> slots, ExtraMetadataHolder holder) {
             if (holder.getExtraMetadata() != null) {
+                slots.removeIf(slot -> isExtraMetadataSlotName(slot.getName())
+                        && holder.getExtraMetadata().containsKey(slot.getName()));
                 holder.getExtraMetadata().entrySet().stream()
                         .filter(entry -> isExtraMetadataSlotName(entry.getKey()))
                         .forEach(entry -> {
diff --git a/commons/ihe/xds/src/test/java/org/openehealth/ipf/commons/ihe/xds/core/transform/ebxml/Ebrs30MarshalingTest.java b/commons/ihe/xds/src/test/java/org/openehealth/ipf/commons/ihe/xds/core/transform/ebxml/Ebrs30MarshalingTest.java
index 8a1fcda49d..c114475dde 100644
--- a/commons/ihe/xds/src/test/java/org/openehealth/ipf/commons/ihe/xds/core/transform/ebxml/Ebrs30MarshalingTest.java
+++ b/commons/ihe/xds/src/test/java/org/openehealth/ipf/commons/ihe/xds/core/transform/ebxml/Ebrs30MarshalingTest.java
@@ -17,6 +17,7 @@
 
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
+import org.openehealth.ipf.commons.ihe.xds.core.XdsJaxbDataBinding;
 import org.openehealth.ipf.commons.ihe.xds.core.ebxml.EbXMLExtrinsicObject;
 import org.openehealth.ipf.commons.ihe.xds.core.ebxml.EbXMLObjectLibrary;
 import org.openehealth.ipf.commons.ihe.xds.core.ebxml.ebxml30.EbXMLAdhocQueryRequest30;
@@ -29,21 +30,26 @@
 import org.openehealth.ipf.commons.ihe.xds.core.stub.ebrs30.lcm.SubmitObjectsRequest;
 import org.openehealth.ipf.commons.ihe.xds.core.stub.ebrs30.query.AdhocQueryRequest;
 import org.openehealth.ipf.commons.ihe.xds.core.stub.ebrs30.rim.ExtrinsicObjectType;
-import org.openehealth.ipf.commons.ihe.xds.core.stub.ebrs30.rim.IdentifiableType;
+import org.openehealth.ipf.commons.ihe.xds.core.stub.ebrs30.rim.ObjectFactory;
 import org.openehealth.ipf.commons.ihe.xds.core.stub.ebrs30.rim.RegistryObjectListType;
 import org.openehealth.ipf.commons.ihe.xds.core.transform.requests.QueryParameter;
 import org.openehealth.ipf.commons.ihe.xds.core.transform.requests.RegisterDocumentSetTransformer;
 import org.openehealth.ipf.commons.ihe.xds.core.transform.requests.query.QuerySlotHelper;
 
 import javax.xml.bind.JAXBContext;
-import javax.xml.bind.JAXBElement;
 import javax.xml.bind.JAXBException;
-import javax.xml.namespace.QName;
+
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
+import java.io.StringReader;
+import java.io.StringWriter;
 
-import static org.junit.jupiter.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
 
 /**
  * Tests for marshaling objects created with our ebxml 2.1 classes.
@@ -54,14 +60,13 @@ public class Ebrs30MarshalingTest {
     private EbXMLExtrinsicObject docEntry;
     private EbXMLFactory30 factory;
     private EbXMLObjectLibrary objectLibrary;
-
-    private static final QName EXTRINSIC_OBJECT_QNAME = 
-        new QName("urn:oasis:names:tc:ebxml-regrep:xsd:rim:3.0", "ExtrinsicObject", "rim"); 
+    private JAXBContext context;
 
     @BeforeEach
-    public void setUp() {
+    public void setUp() throws JAXBException {
         factory = new EbXMLFactory30();
         objectLibrary = factory.createObjectLibrary();
+        context = JAXBContext.newInstance("org.openehealth.ipf.commons.ihe.xds.core.stub.ebrs30.rs");
         
         request = new SubmitObjectsRequest();
         var objListElement = new RegistryObjectListType();
@@ -70,12 +75,7 @@ public void setUp() {
 
         docEntry = factory.createExtrinsic("Document01", objectLibrary);
         docEntry.setObjectType(DocumentEntryType.STABLE.getUuid());
-        objList.add(getJaxbElement(EXTRINSIC_OBJECT_QNAME, ((EbXMLExtrinsicObject30)docEntry).getInternal()));
-    }
-    
-    @SuppressWarnings("unchecked")
-    private static JAXBElement<IdentifiableType> getJaxbElement(QName qname, IdentifiableType object) {
-        return new JAXBElement<>(qname, (Class)object.getClass(), object);
+        objList.add(new ObjectFactory().createExtrinsicObject(((EbXMLExtrinsicObject30)docEntry).getInternal()));
     }
     
     @Test
@@ -131,8 +131,7 @@ public void testAddSlot() throws Exception {
     @Test
     public void testFromRealEbXML() throws Exception {
         var file = new File(getClass().getClassLoader().getResource("SubmitObjectsRequest_ebrs30.xml").toURI());
-
-        var context = JAXBContext.newInstance("org.openehealth.ipf.commons.ihe.xds.core.stub.ebrs30.rs");
+        
         var unmarshaller = context.createUnmarshaller();
 
         var unmarshalled = unmarshaller.unmarshal(file);
@@ -158,8 +157,6 @@ public void testFromRealEbXML() throws Exception {
     @Test
     public void testPatientIdSlotRegexp() throws Exception {
         var file = new File(getClass().getClassLoader().getResource("iti18request.xml").toURI());
-
-        var context = JAXBContext.newInstance("org.openehealth.ipf.commons.ihe.xds.core.stub.ebrs30.rs");
         var unmarshaller = context.createUnmarshaller();
 
         var unmarshalled = unmarshaller.unmarshal(file);
@@ -174,7 +171,6 @@ public void testPatientIdSlotRegexp() throws Exception {
     public void testPatientIdMPQSlotRegexp() throws Exception {
         var file = new File(getClass().getClassLoader().getResource("iti51request.xml").toURI());
 
-        var context = JAXBContext.newInstance("org.openehealth.ipf.commons.ihe.xds.core.stub.ebrs30.rs");
         var unmarshaller = context.createUnmarshaller();
 
         var unmarshalled = unmarshaller.unmarshal(file);
@@ -184,9 +180,33 @@ public void testPatientIdMPQSlotRegexp() throws Exception {
         var patientIdList = slotHelper.toStringList(QueryParameter.DOC_ENTRY_PATIENT_ID);
         assertEquals(2, patientIdList.size());
     }
+    
+    @Test
+    public void verifyExtraMetadataWithJaxbBinding() throws Exception {
+        var file = new File(
+                getClass().getClassLoader().getResource("SubmitObjectsRequest_ebrs3_extra_metadata.xml").toURI());
+        var unmarshaller = context.createUnmarshaller();
+        unmarshaller.setListener(new XdsJaxbDataBinding().getUnmarshallerListener());
+
+        var unmarshalled = unmarshaller.unmarshal(file);
+        var original = (SubmitObjectsRequest) unmarshalled;
+        int numberOfSlotsInFirstDoc = new EbXMLSubmitObjectsRequest30(original).getExtrinsicObjects().get(0).getSlots()
+                .size();
+
+        var marshaller = context.createMarshaller();
+        marshaller.setListener(new XdsJaxbDataBinding().getMarshallerListener());
+        StringWriter writer = new StringWriter();
+        marshaller.marshal(original, writer);
+
+        var unmarshalledSecond = (SubmitObjectsRequest) unmarshaller.unmarshal(new StringReader(writer.toString()));
+        int numberOfSlotsInSecondDoc = new EbXMLSubmitObjectsRequest30(unmarshalledSecond).getExtrinsicObjects().get(0)
+                .getSlots().size();
+
+        assertEquals(numberOfSlotsInFirstDoc, numberOfSlotsInSecondDoc,
+                "Number of slots after Marshalling and Unmarsshalling does not match");
+    }
 
     private SubmitObjectsRequest send() throws JAXBException {
-        var context = JAXBContext.newInstance("org.openehealth.ipf.commons.ihe.xds.core.stub.ebrs30.rs");
         var marshaller = context.createMarshaller();
         var unmarshaller = context.createUnmarshaller();
         var outputStream = new ByteArrayOutputStream();
diff --git a/commons/ihe/xds/src/test/resources/SubmitObjectsRequest_ebrs3_extra_metadata.xml b/commons/ihe/xds/src/test/resources/SubmitObjectsRequest_ebrs3_extra_metadata.xml
new file mode 100644
index 0000000000..8e8dbd46a1
--- /dev/null
+++ b/commons/ihe/xds/src/test/resources/SubmitObjectsRequest_ebrs3_extra_metadata.xml
@@ -0,0 +1,244 @@
+<lcm:SubmitObjectsRequest xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="urn:oasis:names:tc:ebxml-regrep:xsd:lcm:3.0 ../schema/ebRS/lcm.xsd"
+    xmlns:lcm="urn:oasis:names:tc:ebxml-regrep:xsd:lcm:3.0" xmlns:rim="urn:oasis:names:tc:ebxml-regrep:xsd:rim:3.0"
+    xmlns:rs="urn:oasis:names:tc:ebxml-regrep:xsd:rs:3.0">
+    <rim:RegistryObjectList>
+        <rim:ExtrinsicObject id="Document01" mimeType="text/xml"
+            objectType="urn:uuid:7edca82f-054d-47f2-a032-9b2a5b5186c1">
+            <rim:Slot name="creationTime">
+                <rim:ValueList>
+                    <rim:Value>20051224</rim:Value>
+                </rim:ValueList>
+            </rim:Slot>
+            <rim:Slot name="languageCode">
+                <rim:ValueList>
+                    <rim:Value>en-us</rim:Value>
+                </rim:ValueList>
+            </rim:Slot>
+            <rim:Slot name="serviceStartTime">
+                <rim:ValueList>
+                    <rim:Value>200412230800</rim:Value>
+                </rim:ValueList>
+            </rim:Slot>
+            <rim:Slot name="serviceStopTime">
+                <rim:ValueList>
+                    <rim:Value>200412230801</rim:Value>
+                </rim:ValueList>
+            </rim:Slot>
+            <rim:Slot name="sourcePatientId">
+                <rim:ValueList>
+                    <rim:Value>ST-1000^^^&amp;1.3.6.1.4.1.21367.2003.3.9&amp;ISO</rim:Value>
+                </rim:ValueList>
+            </rim:Slot>
+            <rim:Slot name="sourcePatientInfo">
+                <rim:ValueList>
+                    <rim:Value>PID-3|ST-1000^^^&amp;1.3.6.1.4.1.21367.2003.3.9&amp;ISO</rim:Value>
+                    <rim:Value>PID-5|Doe^John^^^</rim:Value>
+                    <rim:Value>PID-7|19560527</rim:Value>
+                    <rim:Value>PID-8|M</rim:Value>
+                    <rim:Value>PID-11|100 Main St^^Metropolis^Il^44130^USA</rim:Value>
+                </rim:ValueList>
+            </rim:Slot>
+            <rim:Slot name="urn:my:company:slot">
+                <rim:ValueList>
+                    <rim:Value>somevalue</rim:Value>
+                </rim:ValueList>
+            </rim:Slot>
+            <rim:Name>
+                <rim:LocalizedString value="Physical"/>
+            </rim:Name>
+            <rim:Description/>
+            <rim:Classification id="cl01"
+                classificationScheme="urn:uuid:93606bcf-9494-43ec-9b4e-a7748d1a838d"
+                classifiedObject="Document01">
+                <rim:Slot name="authorPerson">
+                    <rim:ValueList>
+                        <rim:Value>Gerald Smitty</rim:Value>
+                    </rim:ValueList>
+                </rim:Slot>
+                <rim:Slot name="authorInstitution">
+                    <rim:ValueList>
+                        <rim:Value>Cleveland Clinic</rim:Value>
+                        <rim:Value>Parma Community</rim:Value>
+                    </rim:ValueList>
+                </rim:Slot>
+                <rim:Slot name="authorRole">
+                    <rim:ValueList>
+                        <rim:Value>Attending</rim:Value>
+                    </rim:ValueList>
+                </rim:Slot>
+                <rim:Slot name="authorSpeciality">
+                    <rim:ValueList>
+                        <rim:Value>Orthopedic</rim:Value>
+                    </rim:ValueList>
+                </rim:Slot>
+            </rim:Classification>
+            <rim:Classification id="cl02"
+                classificationScheme="urn:uuid:41a5887f-8865-4c09-adf7-e362475b143a"
+                classifiedObject="Document01" nodeRepresentation="History and Physical">
+                <rim:Slot name="codingScheme">
+                    <rim:ValueList>
+                        <rim:Value>Connect-a-thon classCodes</rim:Value>
+                    </rim:ValueList>
+                </rim:Slot>
+                <rim:Name>
+                    <rim:LocalizedString value="History and Physical"/>
+                </rim:Name>
+            </rim:Classification>
+            <rim:Classification id="cl03"
+                classificationScheme="urn:uuid:f4f85eac-e6cb-4883-b524-f2705394840f"
+                classifiedObject="Document01" nodeRepresentation="1.3.6.1.4.1.21367.2006.7.101">
+                <rim:Slot name="codingScheme">
+                    <rim:ValueList>
+                        <rim:Value>Connect-a-thon confidentialityCodes</rim:Value>
+                    </rim:ValueList>
+                </rim:Slot>
+                <rim:Name>
+                    <rim:LocalizedString value="Clinical-Staff"/>
+                </rim:Name>
+            </rim:Classification>
+            <rim:Classification id="cl04"
+                classificationScheme="urn:uuid:a09d5840-386c-46f2-b5ad-9c3699a4309d"
+                classifiedObject="Document01" nodeRepresentation="CDAR2/IHE 1.0">
+                <rim:Slot name="codingScheme"> 
+                    <rim:ValueList>
+                        <rim:Value>Connect-a-thon formatCodes</rim:Value>
+                    </rim:ValueList>
+                </rim:Slot>
+                <rim:Name>
+                    <rim:LocalizedString value="CDAR2/IHE 1.0"/>
+                </rim:Name>
+            </rim:Classification>
+            <rim:Classification id="cl05"
+                classificationScheme="urn:uuid:f33fb8ac-18af-42cc-ae0e-ed0b0bdb91e1"
+                classifiedObject="Document01" nodeRepresentation="Outpatient">
+                <rim:Slot name="codingScheme">
+                    <rim:ValueList>
+                        <rim:Value>Connect-a-thon healthcareFacilityTypeCodes</rim:Value>
+                    </rim:ValueList>
+                </rim:Slot>
+                <rim:Name>
+                    <rim:LocalizedString value="Outpatient"/>
+                </rim:Name>
+            </rim:Classification>
+            <rim:Classification id="cl06"
+                classificationScheme="urn:uuid:cccf5598-8b07-4b77-a05e-ae952c785ead"
+                classifiedObject="Document01" nodeRepresentation="General Medicine">
+                <rim:Slot name="codingScheme">
+                    <rim:ValueList>
+                        <rim:Value>Connect-a-thon practiceSettingCodes</rim:Value>
+                    </rim:ValueList>
+                </rim:Slot>
+                <rim:Name>
+                    <rim:LocalizedString value="General Medicine"/>
+                </rim:Name>
+            </rim:Classification>
+            <rim:Classification id="cl07"
+                classificationScheme="urn:uuid:f0306f51-975f-434e-a61c-c59651d33983"
+                classifiedObject="Document01" nodeRepresentation="34108-1">
+                <rim:Slot name="codingScheme">
+                    <rim:ValueList>
+                        <rim:Value>LOINC</rim:Value>
+                    </rim:ValueList>
+                </rim:Slot>
+                <rim:Name>
+                    <rim:LocalizedString value="Outpatient Evaluation And Management"/>
+                </rim:Name>
+            </rim:Classification>
+            <rim:ExternalIdentifier id="ei01" registryObject="Document01"
+                identificationScheme="urn:uuid:58a6f841-87b3-4a3e-92fd- a8ffeff98427"
+                value="SELF-5^^^&amp;1.3.6.1.4.1.21367.2005.3.7&amp;ISO">
+                <rim:Name>
+                    <rim:LocalizedString value="XDSDocumentEntry.patientId"/>
+                </rim:Name>
+            </rim:ExternalIdentifier>
+            <rim:ExternalIdentifier id="ei02" registryObject="Document01"
+                identificationScheme="urn:uuid:2e82c1f6-a085-4c72-9da3-8640a32e42ab"
+                value="1.3.6.1.4.1.21367.2005.3.9999.32">
+                <rim:Name>
+                    <rim:LocalizedString value="XDSDocumentEntry.uniqueId"/>
+                </rim:Name>
+            </rim:ExternalIdentifier>
+        </rim:ExtrinsicObject>
+        <rim:RegistryPackage id="SubmissionSet01">
+            <rim:Slot name="submissionTime">
+                <rim:ValueList>
+                    <rim:Value>20041225235050</rim:Value>
+                </rim:ValueList>
+            </rim:Slot>
+            <rim:Name>
+                <rim:LocalizedString value="Physical"/>
+            </rim:Name>
+            <rim:Description>
+                <rim:LocalizedString value="Annual physical"/>
+            </rim:Description>
+            <rim:Classification id="cl08"
+                classificationScheme="urn:uuid:a7058bb9-b4e4-4307-ba5b-e3f0ab85e12d"
+                classifiedObject="SubmissionSet01">
+                <rim:Slot name="authorPerson">
+                    <rim:ValueList>
+                        <rim:Value>Sherry Dopplemeyer</rim:Value>
+                    </rim:ValueList>
+                </rim:Slot>
+                <rim:Slot name="authorInstitution">
+                    <rim:ValueList>
+                        <rim:Value>Cleveland Clinic</rim:Value>
+                        <rim:Value>Berea Community</rim:Value>
+                    </rim:ValueList>
+                </rim:Slot>
+                <rim:Slot name="authorRole">
+                    <rim:ValueList>
+                        <rim:Value>Primary Surgon</rim:Value>
+                    </rim:ValueList>
+                </rim:Slot>
+                <rim:Slot name="authorSpeciality">
+                    <rim:ValueList>
+                        <rim:Value>Orthopedic</rim:Value>
+                    </rim:ValueList>
+                </rim:Slot>
+            </rim:Classification>
+            <rim:Classification id="cl09"
+                classificationScheme="urn:uuid:aa543740-bdda-424e-8c96- df4873be8500"
+                classifiedObject="SubmissionSet01" nodeRepresentation="History and Physical">
+                <rim:Slot name="codingScheme">
+                    <rim:ValueList>
+                        <rim:Value>Connect-a-thon contentTypeCodes</rim:Value>
+                    </rim:ValueList>
+                </rim:Slot>
+                <rim:Name>
+                    <rim:LocalizedString value="History and Physical"/>
+                </rim:Name>
+            </rim:Classification>
+            <rim:ExternalIdentifier id="ei03" registryObject="SubmissionSet01"
+                identificationScheme="urn:uuid:96fdda7c-d067-4183-912e- bf5ee74998a8"
+                value="1.3.6.1.4.1.21367.2005.3.9999.33">
+                <rim:Name>
+                    <rim:LocalizedString value="XDSSubmissionSet.uniqueId"/>
+                </rim:Name>
+            </rim:ExternalIdentifier>
+            <rim:ExternalIdentifier id="ei04" registryObject="SubmissionSet01"
+                identificationScheme="urn:uuid:554ac39e-e3fe-47fe-b233-965d2a147832" value="3670984664">
+                <rim:Name>
+                    <rim:LocalizedString value="XDSSubmissionSet.sourceId"/>
+                </rim:Name>
+            </rim:ExternalIdentifier>
+            <rim:ExternalIdentifier id="ei05" registryObject="SubmissionSet01"
+                identificationScheme="urn:uuid:6b5aea1a-874d-4603-a4bc- 96a0a7b38446"
+                value="SELF-5^^^&amp;1.3.6.1.4.1.21367.2005.3.7&amp;ISO">
+                <rim:Name>
+                    <rim:LocalizedString value="XDSSubmissionSet.patientId"/>
+                </rim:Name>
+            </rim:ExternalIdentifier>
+        </rim:RegistryPackage>
+        <rim:Classification id="cl10" classifiedObject="SubmissionSet01"
+            classificationNode="urn:uuid:a54d6aa5-d40d-43f9-88c5-b4633d873bdd"/>
+        <rim:Association id="as01" associationType="urn:oasis:names:tc:ebxml-regrep:AssociationType:HasMember" sourceObject="SubmissionSet01" targetObject="Document01">
+            <rim:Slot name="SubmissionSetStatus">
+                <rim:ValueList>
+                    <rim:Value>Original</rim:Value>
+                </rim:ValueList>
+            </rim:Slot>
+        </rim:Association>
+        <rim:Association id="as02" associationType="urn:ihe:iti:2010:AssociationType:IsSnapshotOf" sourceObject="Document01" targetObject="urn:uuid:e0985823-dc50-45a5-a6c8-a11a829893bd" />
+    </rim:RegistryObjectList>
+</lcm:SubmitObjectsRequest>