From dd3c59b3d9abcc32360fbbdf75c476dc97805aaf Mon Sep 17 00:00:00 2001 From: Dmytro Rud Date: Thu, 13 Jul 2023 22:37:26 +0200 Subject: [PATCH] #422: Support JSON representation of the HPD DSMLv2 data model --- commons/ihe/hpd/pom.xml | 8 + .../ihe/hpd/stub/dsmlv2/BatchResponse.java | 3 + .../hpd/stub/dsmlv2/BatchResponseElement.java | 27 ++++ .../dsmlv2/BatchResponseIntermediary.java | 93 +++++++++++ .../ihe/hpd/stub/dsmlv2/DsmlMessage.java | 3 + .../ihe/hpd/stub/dsmlv2/ErrorResponse.java | 2 +- .../dsmlv2/JaxbElementListSerializer.java | 56 +++++++ .../ihe/hpd/stub/dsmlv2/LDAPResult.java | 20 ++- .../ihe/hpd/stub/dsmlv2/SearchResponse.java | 2 +- .../ihe/hpd/stub/dsmlv2/JsonTest.groovy | 148 ++++++++++++++++++ commons/ihe/xds/pom.xml | 8 +- src/site/changes.xml | 7 +- 12 files changed, 362 insertions(+), 15 deletions(-) create mode 100644 commons/ihe/hpd/src/main/java/org/openehealth/ipf/commons/ihe/hpd/stub/dsmlv2/BatchResponseElement.java create mode 100644 commons/ihe/hpd/src/main/java/org/openehealth/ipf/commons/ihe/hpd/stub/dsmlv2/BatchResponseIntermediary.java create mode 100644 commons/ihe/hpd/src/main/java/org/openehealth/ipf/commons/ihe/hpd/stub/dsmlv2/JaxbElementListSerializer.java create mode 100644 commons/ihe/hpd/src/test/groovy/org/openehealth/ipf/commons/ihe/hpd/stub/dsmlv2/JsonTest.groovy diff --git a/commons/ihe/hpd/pom.xml b/commons/ihe/hpd/pom.xml index 27fc7fea05..28d1b84331 100644 --- a/commons/ihe/hpd/pom.xml +++ b/commons/ihe/hpd/pom.xml @@ -46,6 +46,14 @@ org.springframework spring-beans + + com.fasterxml.jackson.module + jackson-module-jaxb-annotations + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + diff --git a/commons/ihe/hpd/src/main/java/org/openehealth/ipf/commons/ihe/hpd/stub/dsmlv2/BatchResponse.java b/commons/ihe/hpd/src/main/java/org/openehealth/ipf/commons/ihe/hpd/stub/dsmlv2/BatchResponse.java index 5242b8f136..99ef30ca36 100644 --- a/commons/ihe/hpd/src/main/java/org/openehealth/ipf/commons/ihe/hpd/stub/dsmlv2/BatchResponse.java +++ b/commons/ihe/hpd/src/main/java/org/openehealth/ipf/commons/ihe/hpd/stub/dsmlv2/BatchResponse.java @@ -15,6 +15,8 @@ */ package org.openehealth.ipf.commons.ihe.hpd.stub.dsmlv2; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + import java.util.ArrayList; import java.util.List; import javax.xml.bind.JAXBElement; @@ -59,6 +61,7 @@ public class BatchResponse { @XmlElementRef(name = "authResponse", namespace = "urn:oasis:names:tc:DSML:2:0:core", type = JAXBElement.class, required = false), @XmlElementRef(name = "addResponse", namespace = "urn:oasis:names:tc:DSML:2:0:core", type = JAXBElement.class, required = false) }) + @JsonSerialize(using = JaxbElementListSerializer.class) protected List> batchResponses; @XmlAttribute(name = "requestID") protected String requestID; diff --git a/commons/ihe/hpd/src/main/java/org/openehealth/ipf/commons/ihe/hpd/stub/dsmlv2/BatchResponseElement.java b/commons/ihe/hpd/src/main/java/org/openehealth/ipf/commons/ihe/hpd/stub/dsmlv2/BatchResponseElement.java new file mode 100644 index 0000000000..7ce44fcfb5 --- /dev/null +++ b/commons/ihe/hpd/src/main/java/org/openehealth/ipf/commons/ihe/hpd/stub/dsmlv2/BatchResponseElement.java @@ -0,0 +1,27 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openehealth.ipf.commons.ihe.hpd.stub.dsmlv2; + +import com.fasterxml.jackson.annotation.JsonTypeInfo; + +/** + * Marker interface for whatever can appear in {@link BatchResponse#batchResponses}. + * + * @author Dmytro Rud + */ +@JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS) +public interface BatchResponseElement { +} diff --git a/commons/ihe/hpd/src/main/java/org/openehealth/ipf/commons/ihe/hpd/stub/dsmlv2/BatchResponseIntermediary.java b/commons/ihe/hpd/src/main/java/org/openehealth/ipf/commons/ihe/hpd/stub/dsmlv2/BatchResponseIntermediary.java new file mode 100644 index 0000000000..54cc910c58 --- /dev/null +++ b/commons/ihe/hpd/src/main/java/org/openehealth/ipf/commons/ihe/hpd/stub/dsmlv2/BatchResponseIntermediary.java @@ -0,0 +1,93 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openehealth.ipf.commons.ihe.hpd.stub.dsmlv2; + +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +/** + * Intermediary representation of an HPD {@link BatchResponse} required because the JSON deserializer cannot handle lists of JAXBElements. + *

+ * Usage: + *

+ *     ObjectMapper mapper = new ObjectMapper();
+ *     BatchResponseIntermediary intermediary = mapper.readValue(json1, BatchResponseIntermediary.class);
+ *     BatchResponse batchResponse = batchResponseIntermediary.toBatchResponse();
+ * 
+ * + * @author Dmytro Rud + */ +public class BatchResponseIntermediary { + + private static final ObjectFactory OBJECT_FACTORY = new ObjectFactory(); + + @Getter + @Setter + private List batchResponses; + + @Getter + @Setter + private String requestID; + + /** + * Transforms this intermediary representation into a proper JAXB POJO. + */ + public BatchResponse toBatchResponse() { + BatchResponse batchResponse = new BatchResponse(); + batchResponse.setRequestID(requestID); + if (batchResponses != null) { + for (Object response : batchResponses) { + if (response instanceof ExtendedResponse) { + batchResponse.getBatchResponses().add(OBJECT_FACTORY.createBatchResponseExtendedResponse((ExtendedResponse) response)); + } else if (response instanceof SearchResponse) { + batchResponse.getBatchResponses().add(OBJECT_FACTORY.createBatchResponseSearchResponse((SearchResponse) response)); + } else if (response instanceof ErrorResponse) { + batchResponse.getBatchResponses().add(OBJECT_FACTORY.createBatchResponseErrorResponse((ErrorResponse) response)); + } else if (response instanceof LDAPResult) { + LDAPResult ldapResult = (LDAPResult) response; + switch (ldapResult.getElementName()) { + case "modDNResponse": + batchResponse.getBatchResponses().add(OBJECT_FACTORY.createBatchResponseModDNResponse(ldapResult)); + break; + case "compareResponse": + batchResponse.getBatchResponses().add(OBJECT_FACTORY.createBatchResponseCompareResponse(ldapResult)); + break; + case "delResponse": + batchResponse.getBatchResponses().add(OBJECT_FACTORY.createBatchResponseDelResponse(ldapResult)); + break; + case "modifyResponse": + batchResponse.getBatchResponses().add(OBJECT_FACTORY.createBatchResponseModifyResponse(ldapResult)); + break; + case "authResponse": + batchResponse.getBatchResponses().add(OBJECT_FACTORY.createBatchResponseAuthResponse(ldapResult)); + break; + case "addResponse": + batchResponse.getBatchResponses().add(OBJECT_FACTORY.createBatchResponseAddResponse(ldapResult)); + break; + default: + throw new IllegalStateException("Cannot handle LDAPResult element name " + ldapResult.getElementName()); + } + } else { + throw new IllegalStateException("Cannot handle class " + response.getClass().getName()); + } + } + } + return batchResponse; + } + +} diff --git a/commons/ihe/hpd/src/main/java/org/openehealth/ipf/commons/ihe/hpd/stub/dsmlv2/DsmlMessage.java b/commons/ihe/hpd/src/main/java/org/openehealth/ipf/commons/ihe/hpd/stub/dsmlv2/DsmlMessage.java index 18b9b94e55..6a43d5904d 100644 --- a/commons/ihe/hpd/src/main/java/org/openehealth/ipf/commons/ihe/hpd/stub/dsmlv2/DsmlMessage.java +++ b/commons/ihe/hpd/src/main/java/org/openehealth/ipf/commons/ihe/hpd/stub/dsmlv2/DsmlMessage.java @@ -15,6 +15,8 @@ */ package org.openehealth.ipf.commons.ihe.hpd.stub.dsmlv2; +import com.fasterxml.jackson.annotation.JsonTypeInfo; + import java.util.ArrayList; import java.util.List; import javax.xml.bind.annotation.XmlAccessType; @@ -62,6 +64,7 @@ ExtendedRequest.class, SearchResultEntry.class }) +@JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS) public class DsmlMessage { protected List control; diff --git a/commons/ihe/hpd/src/main/java/org/openehealth/ipf/commons/ihe/hpd/stub/dsmlv2/ErrorResponse.java b/commons/ihe/hpd/src/main/java/org/openehealth/ipf/commons/ihe/hpd/stub/dsmlv2/ErrorResponse.java index 9abd880c98..9d8973cb5b 100644 --- a/commons/ihe/hpd/src/main/java/org/openehealth/ipf/commons/ihe/hpd/stub/dsmlv2/ErrorResponse.java +++ b/commons/ihe/hpd/src/main/java/org/openehealth/ipf/commons/ihe/hpd/stub/dsmlv2/ErrorResponse.java @@ -74,7 +74,7 @@ "message", "detail" }) -public class ErrorResponse { +public class ErrorResponse implements BatchResponseElement { protected String message; protected Detail detail; diff --git a/commons/ihe/hpd/src/main/java/org/openehealth/ipf/commons/ihe/hpd/stub/dsmlv2/JaxbElementListSerializer.java b/commons/ihe/hpd/src/main/java/org/openehealth/ipf/commons/ihe/hpd/stub/dsmlv2/JaxbElementListSerializer.java new file mode 100644 index 0000000000..44e062130e --- /dev/null +++ b/commons/ihe/hpd/src/main/java/org/openehealth/ipf/commons/ihe/hpd/stub/dsmlv2/JaxbElementListSerializer.java @@ -0,0 +1,56 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openehealth.ipf.commons.ihe.hpd.stub.dsmlv2; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.jsontype.TypeSerializer; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import com.fasterxml.jackson.databind.type.SimpleType; + +import javax.xml.bind.JAXBElement; +import java.io.IOException; +import java.util.List; + +/** + * JSON serializer for {@link List}<{@link JAXBElement}>. + * + * @author Dmytro Rud + */ +public class JaxbElementListSerializer extends StdSerializer { + + public JaxbElementListSerializer() { + super(List.class); + } + + @Override + public void serialize(List list, JsonGenerator gen, SerializerProvider provider) throws IOException { + gen.writeStartArray(list); + for (Object object : list) { + JAXBElement jaxbElement = (JAXBElement) object; + Object value = jaxbElement.getValue(); + if (value instanceof LDAPResult) { + ((LDAPResult) value).setElementName(jaxbElement.getName().getLocalPart()); + } + JsonSerializer valueSerializer = provider.findValueSerializer(value.getClass()); + TypeSerializer typeSerializer = provider.findTypeSerializer(SimpleType.constructUnsafe(value.getClass())); + valueSerializer.serializeWithType(value, gen, provider, typeSerializer); + } + gen.writeEndArray(); + } + +} diff --git a/commons/ihe/hpd/src/main/java/org/openehealth/ipf/commons/ihe/hpd/stub/dsmlv2/LDAPResult.java b/commons/ihe/hpd/src/main/java/org/openehealth/ipf/commons/ihe/hpd/stub/dsmlv2/LDAPResult.java index a400cf09d0..f245373620 100644 --- a/commons/ihe/hpd/src/main/java/org/openehealth/ipf/commons/ihe/hpd/stub/dsmlv2/LDAPResult.java +++ b/commons/ihe/hpd/src/main/java/org/openehealth/ipf/commons/ihe/hpd/stub/dsmlv2/LDAPResult.java @@ -15,15 +15,13 @@ */ package org.openehealth.ipf.commons.ihe.hpd.stub.dsmlv2; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.Setter; + import java.util.ArrayList; import java.util.List; -import javax.xml.bind.annotation.XmlAccessType; -import javax.xml.bind.annotation.XmlAccessorType; -import javax.xml.bind.annotation.XmlAttribute; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlSchemaType; -import javax.xml.bind.annotation.XmlSeeAlso; -import javax.xml.bind.annotation.XmlType; +import javax.xml.bind.annotation.*; /** @@ -58,9 +56,15 @@ ExtendedResponse.class }) public class LDAPResult - extends DsmlMessage + extends DsmlMessage implements BatchResponseElement { + // this attribute is required only in JSON serialization to hold the actual element name + @XmlTransient + @JsonProperty("@e") + @Getter @Setter + private String elementName; + @XmlElement(required = true) protected ResultCode resultCode; protected String errorMessage; diff --git a/commons/ihe/hpd/src/main/java/org/openehealth/ipf/commons/ihe/hpd/stub/dsmlv2/SearchResponse.java b/commons/ihe/hpd/src/main/java/org/openehealth/ipf/commons/ihe/hpd/stub/dsmlv2/SearchResponse.java index 321330b93a..7d7dfa2dd9 100644 --- a/commons/ihe/hpd/src/main/java/org/openehealth/ipf/commons/ihe/hpd/stub/dsmlv2/SearchResponse.java +++ b/commons/ihe/hpd/src/main/java/org/openehealth/ipf/commons/ihe/hpd/stub/dsmlv2/SearchResponse.java @@ -52,7 +52,7 @@ "searchResultReference", "searchResultDone" }) -public class SearchResponse { +public class SearchResponse implements BatchResponseElement { protected List searchResultEntry; protected List searchResultReference; diff --git a/commons/ihe/hpd/src/test/groovy/org/openehealth/ipf/commons/ihe/hpd/stub/dsmlv2/JsonTest.groovy b/commons/ihe/hpd/src/test/groovy/org/openehealth/ipf/commons/ihe/hpd/stub/dsmlv2/JsonTest.groovy new file mode 100644 index 0000000000..5fb2e41ce7 --- /dev/null +++ b/commons/ihe/hpd/src/test/groovy/org/openehealth/ipf/commons/ihe/hpd/stub/dsmlv2/JsonTest.groovy @@ -0,0 +1,148 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openehealth.ipf.commons.ihe.hpd.stub.dsmlv2 + +import com.fasterxml.jackson.annotation.JsonInclude +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.SerializationFeature +import groovy.util.logging.Slf4j +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.Test +import org.openehealth.ipf.commons.ihe.hpd.HpdValidator +import org.openehealth.ipf.commons.xml.XmlUtils + +import javax.xml.bind.JAXBElement + +@Slf4j +class JsonTest { + + static ObjectMapper mapper + + @BeforeAll + static void beforeAll() { + mapper = new ObjectMapper() + mapper.enable(SerializationFeature.INDENT_OUTPUT) + mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL) + mapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY) + mapper.setSerializationInclusion(JsonInclude.Include.NON_DEFAULT) + } + + @Test + void testBatchRequest() { + def batchRequest1 = new BatchRequest( + requestID: '123', + batchRequests: [ + new SearchRequest( + requestID: '124', + dn: 'ou=HPDElectronicService', + scope: SearchRequest.SearchScope.WHOLE_SUBTREE, + derefAliases: SearchRequest.DerefAliasesType.NEVER_DEREF_ALIASES, + filter: new Filter( + equalityMatch: new AttributeValueAssertion(name: 'hcIdentifier', value: '1234567') + ), + attributes: new AttributeDescriptions( + attribute: [ + new AttributeDescription(name: 'hpdServiceAddress'), + new AttributeDescription(name: 'boundInstitution'), + ], + ), + ), + + ], + ) + + def json1 = mapper.writeValueAsString(batchRequest1) + log.debug('JSON 1:\n{}', json1) + + def batchRequest2 = mapper.readValue(json1, BatchRequest.class) + def json2 = mapper.writeValueAsString(batchRequest2) + log.debug('JSON 2:\n{}', json2) + + assert json1 == json2 + } + + @Test + void testBatchResponse() { + def batchResponseString1 = ''' + + + + + + + + + + 765 256 64 60 + + + + + 754 354 00 14 $ Personal + + + hpdServiceId=31a0b48e-b409-4468-9add-341969e003d7:2.16.756.5.30.1.166.0.1,ou=HPDElectronicService,dc=HPD,o=comm1,c=CH + + + status=PRIMARY $ addr=Kantonsspital Graubünden 7000 Chur CH $ city=Chur $ country=CH $ postalCode=7000 $ streetName=Kantonsspital Graubünden $ boundInstitution=None + + + comm1:102245 + + + comm1:GPSID:102245:ACTIVE + SIVC:SIVC-ID:995067:ACTIVE + + + medappl@comm1.ch $ Personal + + + cn=relKSGR,ou=Relationship,dc=HPD,o=comm1,c=CH + + + KSGR Radiologie + + + HCProfessional + HPDProvider + inetOrgPerson + naturalPerson + organizationalPerson + person + top + + + + + + + + +''' + + JAXBElement jaxbElement1 = HpdValidator.JAXB_CONTEXT.createUnmarshaller().unmarshal(XmlUtils.source(batchResponseString1)) + def json1 = mapper.writeValueAsString(jaxbElement1.value) + log.debug('JSON 1:\n{}', json1) + + def batchResponseIntermediary2 = mapper.readValue(json1, BatchResponseIntermediary.class) + def batchResponse2 = batchResponseIntermediary2.toBatchResponse() + def json2 = mapper.writeValueAsString(batchResponse2) + log.debug('JSON 2:\n{}', json2) + + assert json1 == json2 + } + +} diff --git a/commons/ihe/xds/pom.xml b/commons/ihe/xds/pom.xml index d06646e0ad..0986c04a4c 100644 --- a/commons/ihe/xds/pom.xml +++ b/commons/ihe/xds/pom.xml @@ -35,6 +35,10 @@ com.fasterxml.jackson.module jackson-module-jaxb-annotations + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + @@ -61,10 +65,6 @@ mockito-core test - - com.fasterxml.jackson.datatype - jackson-datatype-jsr310 - diff --git a/src/site/changes.xml b/src/site/changes.xml index cbc548f6de..0e0e8cfda4 100644 --- a/src/site/changes.xml +++ b/src/site/changes.xml @@ -23,7 +23,12 @@ Christian Ohr - + + + Support JSON representation of the HPD DSMLv2 data model + + + Properly populate PurposeOfUse attribute in audit records of some HL7v3-based transactions