Skip to content

Commit

Permalink
#451: Fixed translation of FilterSets between DSMLv2 and JSON
Browse files Browse the repository at this point in the history
unixoid committed May 26, 2024
1 parent 4186a68 commit bb5c3d4
Showing 9 changed files with 377 additions and 21 deletions.
Original file line number Diff line number Diff line change
@@ -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> {
T toDsml()
//... and there must be a static method with the signature "static Filter2 fromDsml(T dsml)"
}

@CompileStatic
class And implements FilterIntermediary<FilterSet> {
List<FilterIntermediary> nested
FilterSet toDsml() { return FilterIntermediaryUtils.toFilterSet(nested) }
static FilterIntermediary fromDsml(FilterSet fs) { return new And(nested: FilterIntermediaryUtils.fromFilterSet(fs)) }
}

@CompileStatic
class Or implements FilterIntermediary<FilterSet> {
List<FilterIntermediary> nested
FilterSet toDsml() { return FilterIntermediaryUtils.toFilterSet(nested) }
static FilterIntermediary fromDsml(FilterSet fs) { return new Or(nested: FilterIntermediaryUtils.fromFilterSet(fs)) }
}

@CompileStatic
class Not implements FilterIntermediary<Filter> {
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> {
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> {
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> {
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> {
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> {
AttributeDescription toDsml() { return this }
static FilterIntermediary fromDsml(AttributeDescription ad) { return new Present(name: ad.name) }
}

@CompileStatic
class ApproxMatch extends AttributeValueAssertion implements FilterIntermediary<AttributeValueAssertion> {
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> {
MatchingRuleAssertion toDsml() { return this }
static FilterIntermediary fromDsml(MatchingRuleAssertion mra) { return new ExtensibleMatch(name: mra.name, value: mra.value, dnAttributes: mra.dnAttributes, matchingRule: mra.matchingRule) }
}
Original file line number Diff line number Diff line change
@@ -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<String> 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<FilterIntermediary> fromFilterSet(FilterSet fs) {
return fs.filterGroup.collect { jaxbElement -> fromDsml(jaxbElement.name.localPart, jaxbElement.value) }
}

static FilterSet toFilterSet(List<FilterIntermediary> fis) {
return new FilterSet(filterGroup: fis.collect { fi ->
OBJECT_FACTORY."${'createFilterSet' + fi.class.simpleName}"(fi.toDsml()) as JAXBElement
})
}

}
Original file line number Diff line number Diff line change
@@ -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<SearchRequestIntermediary> 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,
)
}

}
Original file line number Diff line number Diff line change
@@ -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<Control> controls
FilterIntermediary filter
List<String> 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,
)
}

}
Original file line number Diff line number Diff line change
@@ -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;
Original file line number Diff line number Diff line change
@@ -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;

Original file line number Diff line number Diff line change
@@ -13,14 +13,15 @@
* 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;
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 org.openehealth.ipf.commons.ihe.hpd.stub.dsmlv2.LDAPResult;

import javax.xml.bind.JAXBElement;
import java.io.IOException;
Original file line number Diff line number Diff line change
@@ -22,13 +22,17 @@ 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

@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
}

}
3 changes: 3 additions & 0 deletions src/site/changes.xml
Original file line number Diff line number Diff line change
@@ -24,6 +24,9 @@
</properties>
<body>
<release version="" description="" date="">
<action issue="451" dev="unixoid" type="fix">
Improved translation between DSMLv2 and JSON
</action>
<action issue="446" dev="Ivan1pl" type="fix">
Properly handle query payload when translating ATNA audit records to FHIR
</action>

0 comments on commit bb5c3d4

Please sign in to comment.