diff --git a/commons/ihe/hpd/src/main/groovy/org/openehealth/ipf/commons/ihe/hpd/stub/json/FilterIntermediaries.groovy b/commons/ihe/hpd/src/main/groovy/org/openehealth/ipf/commons/ihe/hpd/stub/json/FilterIntermediaries.groovy new file mode 100644 index 0000000000..ee91aec826 --- /dev/null +++ b/commons/ihe/hpd/src/main/groovy/org/openehealth/ipf/commons/ihe/hpd/stub/json/FilterIntermediaries.groovy @@ -0,0 +1,93 @@ +/* + * Copyright 2024 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.json + +import com.fasterxml.jackson.annotation.JsonTypeInfo +import groovy.transform.CompileStatic +import org.openehealth.ipf.commons.ihe.hpd.stub.dsmlv2.* + +/** + * @author Dmytro Rud + */ +@CompileStatic +@JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS) +interface FilterIntermediary { + T toDsml() + //... and there must be a static method with the signature "static Filter2 fromDsml(T dsml)" +} + +@CompileStatic +class And implements FilterIntermediary { + List nested + FilterSet toDsml() { return FilterIntermediaryUtils.toFilterSet(nested) } + static FilterIntermediary fromDsml(FilterSet fs) { return new And(nested: FilterIntermediaryUtils.fromFilterSet(fs)) } +} + +@CompileStatic +class Or implements FilterIntermediary { + List nested + FilterSet toDsml() { return FilterIntermediaryUtils.toFilterSet(nested) } + static FilterIntermediary fromDsml(FilterSet fs) { return new Or(nested: FilterIntermediaryUtils.fromFilterSet(fs)) } +} + +@CompileStatic +class Not implements FilterIntermediary { + FilterIntermediary nested + Filter toDsml() { return FilterIntermediaryUtils.toFilter(nested) } + static FilterIntermediary fromDsml(Filter f) { return new Not(nested: FilterIntermediaryUtils.fromFilter(f)) } +} + +@CompileStatic +class EqualityMatch extends AttributeValueAssertion implements FilterIntermediary { + AttributeValueAssertion toDsml() { return this } + static FilterIntermediary fromDsml(AttributeValueAssertion ava) { return new EqualityMatch(name: ava.name, value: ava.value) } +} + +@CompileStatic +class Substrings extends SubstringFilter implements FilterIntermediary { + SubstringFilter toDsml() { return this } + static FilterIntermediary fromDsml(SubstringFilter sf) { return new Substrings(name: sf.name, initial: sf.initial, any: sf.any, final: sf.final) } +} + +@CompileStatic +class GreaterOrEqual extends AttributeValueAssertion implements FilterIntermediary { + AttributeValueAssertion toDsml() { return this } + static FilterIntermediary fromDsml(AttributeValueAssertion ava) { return new GreaterOrEqual(name: ava.name, value: ava.value) } +} + +@CompileStatic +class LessOrEqual extends AttributeValueAssertion implements FilterIntermediary { + AttributeValueAssertion toDsml() { return this } + static FilterIntermediary fromDsml(AttributeValueAssertion ava) { return new LessOrEqual(name: ava.name, value: ava.value) } +} + +@CompileStatic +class Present extends AttributeDescription implements FilterIntermediary { + AttributeDescription toDsml() { return this } + static FilterIntermediary fromDsml(AttributeDescription ad) { return new Present(name: ad.name) } +} + +@CompileStatic +class ApproxMatch extends AttributeValueAssertion implements FilterIntermediary { + AttributeValueAssertion toDsml() { return this } + static FilterIntermediary fromDsml(AttributeValueAssertion ava) { return new ApproxMatch(name: ava.name, value: ava.value) } +} + +@CompileStatic +class ExtensibleMatch extends MatchingRuleAssertion implements FilterIntermediary { + MatchingRuleAssertion toDsml() { return this } + static FilterIntermediary fromDsml(MatchingRuleAssertion mra) { return new ExtensibleMatch(name: mra.name, value: mra.value, dnAttributes: mra.dnAttributes, matchingRule: mra.matchingRule) } +} diff --git a/commons/ihe/hpd/src/main/groovy/org/openehealth/ipf/commons/ihe/hpd/stub/json/FilterIntermediaryUtils.groovy b/commons/ihe/hpd/src/main/groovy/org/openehealth/ipf/commons/ihe/hpd/stub/json/FilterIntermediaryUtils.groovy new file mode 100644 index 0000000000..b30cb480af --- /dev/null +++ b/commons/ihe/hpd/src/main/groovy/org/openehealth/ipf/commons/ihe/hpd/stub/json/FilterIntermediaryUtils.groovy @@ -0,0 +1,61 @@ +/* + * Copyright 2024 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.json + +import org.openehealth.ipf.commons.ihe.hpd.stub.dsmlv2.Filter +import org.openehealth.ipf.commons.ihe.hpd.stub.dsmlv2.FilterSet +import org.openehealth.ipf.commons.ihe.hpd.stub.dsmlv2.ObjectFactory + +import javax.xml.bind.JAXBElement + +/** + * @author Dmytro Rud + */ +class FilterIntermediaryUtils { + + static final ObjectFactory OBJECT_FACTORY = new ObjectFactory() + + static final List FILTER_TYPES = ['and', 'or', 'not', 'equalityMatch', 'substring', 'greaterOrEqual', 'lessOrEqual', 'present', 'approxMatch', 'extensibleMatch'] + + static FilterIntermediary fromDsml(String filterType, Object dsml) { + return Class.forName("${FilterIntermediaryUtils.packageName + '.' + filterType.capitalize()}").declaredMethods.find { it.name == 'fromDsml' }.invoke(null, dsml) as FilterIntermediary + } + + static FilterIntermediary fromFilter(Filter f) { + for (fieldName in FILTER_TYPES) { + def fieldValue = f."${fieldName}" + if (fieldValue) { + return fromDsml(fieldName, fieldValue) + } + } + return null + } + + static Filter toFilter(FilterIntermediary fi) { + return new Filter("${fi.class.simpleName.uncapitalize()}": fi.toDsml()) + } + + static List fromFilterSet(FilterSet fs) { + return fs.filterGroup.collect { jaxbElement -> fromDsml(jaxbElement.name.localPart, jaxbElement.value) } + } + + static FilterSet toFilterSet(List fis) { + return new FilterSet(filterGroup: fis.collect { fi -> + OBJECT_FACTORY."${'createFilterSet' + fi.class.simpleName}"(fi.toDsml()) as JAXBElement + }) + } + +} diff --git a/commons/ihe/hpd/src/main/groovy/org/openehealth/ipf/commons/ihe/hpd/stub/json/SearchBatchRequestIntermediary.groovy b/commons/ihe/hpd/src/main/groovy/org/openehealth/ipf/commons/ihe/hpd/stub/json/SearchBatchRequestIntermediary.groovy new file mode 100644 index 0000000000..f3b8260450 --- /dev/null +++ b/commons/ihe/hpd/src/main/groovy/org/openehealth/ipf/commons/ihe/hpd/stub/json/SearchBatchRequestIntermediary.groovy @@ -0,0 +1,56 @@ +/* + * Copyright 2024 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.json + +import groovy.transform.CompileStatic +import org.openehealth.ipf.commons.ihe.hpd.stub.dsmlv2.* + +/** + * @author Dmytro Rud + */ +@CompileStatic +class SearchBatchRequestIntermediary { + + String requestId + AuthRequest authRequest + List searchRequests + BatchRequest.RequestProcessingType processing + BatchRequest.RequestResponseOrder responseOrder + BatchRequest.RequestErrorHandlingType onError + + static SearchBatchRequestIntermediary fromBatchRequest(BatchRequest batchRequest) { + return new SearchBatchRequestIntermediary( + requestId: batchRequest.requestID, + authRequest: batchRequest.authRequest, + searchRequests: batchRequest.batchRequests.collect { SearchRequestIntermediary.fromSearchRequest(it as SearchRequest) }, + processing: batchRequest.processing, + responseOrder: batchRequest.responseOrder, + onError: batchRequest.onError, + ) + } + + BatchRequest toBatchRequest() { + return new BatchRequest( + requestID: requestId, + authRequest: authRequest, + batchRequests: searchRequests.collect { it.toSearchRequest() as DsmlMessage }, + processing: processing, + responseOrder: responseOrder, + onError: onError, + ) + } + +} diff --git a/commons/ihe/hpd/src/main/groovy/org/openehealth/ipf/commons/ihe/hpd/stub/json/SearchRequestIntermediary.groovy b/commons/ihe/hpd/src/main/groovy/org/openehealth/ipf/commons/ihe/hpd/stub/json/SearchRequestIntermediary.groovy new file mode 100644 index 0000000000..066c5ba391 --- /dev/null +++ b/commons/ihe/hpd/src/main/groovy/org/openehealth/ipf/commons/ihe/hpd/stub/json/SearchRequestIntermediary.groovy @@ -0,0 +1,68 @@ +/* + * Copyright 2024 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.json + +import groovy.transform.CompileStatic +import org.openehealth.ipf.commons.ihe.hpd.stub.dsmlv2.* + +/** + * @author Dmytro Rud + */ +@CompileStatic +class SearchRequestIntermediary { + + String requestId + List controls + FilterIntermediary filter + List attributes + String dn + SearchRequest.SearchScope scope + SearchRequest.DerefAliasesType derefAliases + long sizeLimit + long timeLimit + boolean typesOnly + + static SearchRequestIntermediary fromSearchRequest(SearchRequest searchRequest) { + return new SearchRequestIntermediary( + requestId: searchRequest.requestID, + controls: searchRequest.control, + filter: FilterIntermediaryUtils.fromFilter(searchRequest.filter), + attributes: searchRequest.attributes.attribute.collect { it.name }, + dn: searchRequest.dn, + scope: searchRequest.scope, + derefAliases: searchRequest.derefAliases, + sizeLimit: searchRequest.sizeLimit, + timeLimit: searchRequest.timeLimit, + typesOnly: searchRequest.typesOnly, + ) + } + + SearchRequest toSearchRequest() { + return new SearchRequest( + requestID: requestId, + control: controls, + filter: FilterIntermediaryUtils.toFilter(filter), + attributes: new AttributeDescriptions(attribute: attributes.collect { new AttributeDescription(name: it) }), + dn: dn, + scope: scope, + derefAliases: derefAliases, + sizeLimit: sizeLimit, + timeLimit: timeLimit, + typesOnly: typesOnly, + ) + } + +} 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 99ef30ca36..76e0d7466b 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 @@ -16,6 +16,7 @@ package org.openehealth.ipf.commons.ihe.hpd.stub.dsmlv2; import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import org.openehealth.ipf.commons.ihe.hpd.stub.json.JaxbElementListSerializer; import java.util.ArrayList; import java.util.List; 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/json/BatchResponseIntermediary.java similarity index 97% rename from commons/ihe/hpd/src/main/java/org/openehealth/ipf/commons/ihe/hpd/stub/dsmlv2/BatchResponseIntermediary.java rename to commons/ihe/hpd/src/main/java/org/openehealth/ipf/commons/ihe/hpd/stub/json/BatchResponseIntermediary.java index 54cc910c58..f0673e4670 100644 --- 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/json/BatchResponseIntermediary.java @@ -13,10 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.openehealth.ipf.commons.ihe.hpd.stub.dsmlv2; +package org.openehealth.ipf.commons.ihe.hpd.stub.json; import lombok.Getter; import lombok.Setter; +import org.openehealth.ipf.commons.ihe.hpd.stub.dsmlv2.*; import java.util.List; 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/json/JaxbElementListSerializer.java similarity index 94% rename from commons/ihe/hpd/src/main/java/org/openehealth/ipf/commons/ihe/hpd/stub/dsmlv2/JaxbElementListSerializer.java rename to commons/ihe/hpd/src/main/java/org/openehealth/ipf/commons/ihe/hpd/stub/json/JaxbElementListSerializer.java index 44e062130e..f019359936 100644 --- 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/json/JaxbElementListSerializer.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.openehealth.ipf.commons.ihe.hpd.stub.dsmlv2; +package org.openehealth.ipf.commons.ihe.hpd.stub.json; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.JsonSerializer; @@ -21,6 +21,7 @@ import com.fasterxml.jackson.databind.jsontype.TypeSerializer; import com.fasterxml.jackson.databind.ser.std.StdSerializer; import com.fasterxml.jackson.databind.type.SimpleType; +import org.openehealth.ipf.commons.ihe.hpd.stub.dsmlv2.LDAPResult; import javax.xml.bind.JAXBElement; import java.io.IOException; 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 index 5fb2e41ce7..0c017c21c7 100644 --- 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 @@ -22,6 +22,8 @@ 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.ihe.hpd.stub.json.BatchResponseIntermediary +import org.openehealth.ipf.commons.ihe.hpd.stub.json.SearchBatchRequestIntermediary import org.openehealth.ipf.commons.xml.XmlUtils import javax.xml.bind.JAXBElement @@ -29,6 +31,8 @@ import javax.xml.bind.JAXBElement @Slf4j class JsonTest { + static final ObjectFactory OBJECT_FACTORY = new ObjectFactory() + static ObjectMapper mapper @BeforeAll @@ -43,25 +47,25 @@ class JsonTest { @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'), - ], - ), - ), + 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) @@ -145,4 +149,72 @@ class JsonTest { assert json1 == json2 } -} + @Test + void testSearchBatchRequest() { + def batchRequest1 = new BatchRequest( + requestID: '123', + processing: BatchRequest.RequestProcessingType.SEQUENTIAL, + responseOrder: BatchRequest.RequestResponseOrder.SEQUENTIAL, + onError: BatchRequest.RequestErrorHandlingType.EXIT, + batchRequests: [ + new SearchRequest( + requestID: '124', + dn: 'ou=HCProfessional,foo=bar', + scope: SearchRequest.SearchScope.WHOLE_SUBTREE, + derefAliases: SearchRequest.DerefAliasesType.NEVER_DEREF_ALIASES, + sizeLimit: 100L, + timeLimit: 200L, + typesOnly: false, + filter: new Filter( + and: new FilterSet( + filterGroup: [ + OBJECT_FACTORY.createFilterSetOr(new FilterSet( + filterGroup: [ + OBJECT_FACTORY.createFilterSetSubstrings(new SubstringFilter(name: 'hcIdentifier', initial: 'RefData:', any: ['123', '456'], _final: ':ACTIVE')), + OBJECT_FACTORY.createFilterSetEqualityMatch(new AttributeValueAssertion(name: 'sn', value: 'Schmidt')), + OBJECT_FACTORY.createFilterSetGreaterOrEqual(new AttributeValueAssertion(name: 'creationTimestamp', value: '20240525101112')), + OBJECT_FACTORY.createFilterSetLessOrEqual(new AttributeValueAssertion(name: 'creationTimestamp', value: '20240525101112')), + ], + )), + OBJECT_FACTORY.createFilterSetNot(new Filter( + or: new FilterSet( + filterGroup: [ + OBJECT_FACTORY.createFilterSetPresent(new AttributeDescription(name: 'hcProfession')), + OBJECT_FACTORY.createFilterSetApproxMatch(new AttributeValueAssertion(name: 'telephoneNumber', value: '+4176...')), + OBJECT_FACTORY.createFilterSetExtensibleMatch(new MatchingRuleAssertion(name: 'cn', value: 'Mouse, Mickey, comm1:123', dnAttributes: Boolean.TRUE, matchingRule: 'rule123')), + ], + ), + )), + ]), + ), + attributes: new AttributeDescriptions( + attribute: [ + new AttributeDescription(name: 'hpdServiceAddress'), + new AttributeDescription(name: 'boundInstitution'), + ], + ), + ), + ], + ) + def xml1 = XmlUtils.renderJaxb(HpdValidator.JAXB_CONTEXT, batchRequest1, false) + log.debug('XML 1:\n{}', xml1) + + def batchRequestIntermediary1 = SearchBatchRequestIntermediary.fromBatchRequest(batchRequest1) + def json1 = mapper.writeValueAsString(batchRequestIntermediary1) + log.debug('JSON 1:\n{}', json1) + + def batchRequestIntermediary2 = mapper.readValue(json1, SearchBatchRequestIntermediary.class) + def batchRequest2 = batchRequestIntermediary2.toBatchRequest() + def xml2 = XmlUtils.renderJaxb(HpdValidator.JAXB_CONTEXT, batchRequest2, false) + log.debug('XML 2:\n{}', xml2) + + assert xml1 == xml2 + + def batchRequestIntermediary3 = SearchBatchRequestIntermediary.fromBatchRequest(batchRequest2) + def json3 = mapper.writeValueAsString(batchRequestIntermediary3) + log.debug('JSON 3:\n{}', json3) + + assert json1 == json3 + } + +} \ No newline at end of file diff --git a/src/site/changes.xml b/src/site/changes.xml index e3a83e48fe..5125a1adad 100644 --- a/src/site/changes.xml +++ b/src/site/changes.xml @@ -24,6 +24,9 @@ + + Improved translation between DSMLv2 and JSON + Properly handle query payload when translating ATNA audit records to FHIR