Skip to content

Commit

Permalink
oehf#448: further work on iti-119
Browse files Browse the repository at this point in the history
  • Loading branch information
Christian Ohr committed Oct 29, 2024
1 parent e5d7277 commit 7c29cf5
Show file tree
Hide file tree
Showing 15 changed files with 200 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
import jakarta.servlet.Filter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.nio.charset.StandardCharsets;


Expand Down Expand Up @@ -152,6 +153,16 @@ public IpfFhirServlet fhirServlet(
if (narrativeGenerator != null) {
fhirServlet.setNarrativeGenerator(narrativeGenerator);
}
// Register server interceptor for ITI-119 if on classpath
try {
var clazz = Class.forName("org.openehealth.ipf.commons.ihe.fhir.iti119.MatchGradeEnumInterceptor");
fhirServlet.registerInterceptor(clazz.getConstructor().newInstance());
} catch (ClassNotFoundException e) {
// ok
} catch (Exception e) {
// should never happen
throw new RuntimeException(e);
}
return fhirServlet;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,13 @@ public void validateRequest(Object payload, Map<String, Object> parameters) {
}
}

@Override
public void validateResponse(Object payload, Map<String, Object> parameters) {
var resource = (IBaseResource) payload;
var validationResult = validator.validateWithResult(resource);
if (!validationResult.isSuccessful()) {
var operationOutcome = validationResult.toOperationOutcome();
throw FhirUtils.exception(UnprocessableEntityException::new, operationOutcome, "Validation Failed");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,11 @@
package org.openehealth.ipf.commons.ihe.fhir.iti119;

import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
import ca.uhn.fhir.model.primitive.DecimalDt;
import ca.uhn.fhir.model.valueset.BundleEntryTransactionMethodEnum;
import org.hl7.fhir.r4.model.codesystems.MatchGrade;

public abstract class AdditionalResourceMetadataKeyEnum {

public static final ResourceMetadataKeyEnum<BundleEntryTransactionMethodEnum> SEARCH_SCORE =
new ResourceMetadataKeyEnum<>("SEARCH_SCORE", DecimalDt.class) {};
public static final ResourceMetadataKeyEnum<MatchGrade> ENTRY_MATCH_GRADE =
new ResourceMetadataKeyEnum<>("ENTRY_MATCH_GRADE", MatchGrade.class) {};

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
*/
package org.openehealth.ipf.commons.ihe.fhir.iti119;

import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Parameters;
import org.openehealth.ipf.commons.audit.AuditContext;
import org.openehealth.ipf.commons.audit.model.AuditMessage;
Expand Down Expand Up @@ -54,7 +53,7 @@ public AuditMessage[] makeAuditMessage(AuditContext auditContext, FhirQueryAudit
@Override
public FhirQueryAuditDataset enrichAuditDatasetFromRequest(FhirQueryAuditDataset auditDataset, Object request, Map<String, Object> parameters) {
var dataset = super.enrichAuditDatasetFromRequest(auditDataset, request, parameters);
if (request instanceof Parameters p) {
if (request instanceof Parameters p && auditDataset.getFhirContext() != null) {
dataset.setQueryString(auditDataset.getFhirContext().newJsonParser().encodeToString(p));
}
return dataset;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package org.openehealth.ipf.commons.ihe.fhir.iti119;

import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
import ca.uhn.fhir.rest.annotation.IncludeParam;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
Expand All @@ -26,7 +27,10 @@
import ca.uhn.fhir.rest.api.server.RequestDetails;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.hl7.fhir.r4.model.*;
import org.hl7.fhir.r4.model.BooleanType;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.PositiveIntType;
import org.hl7.fhir.r4.model.ResourceType;
import org.openehealth.ipf.commons.ihe.fhir.AbstractPlainProvider;
import org.openehealth.ipf.commons.ihe.fhir.iti78.PdqPatient;

Expand Down Expand Up @@ -57,24 +61,19 @@ public class Iti119ResourceProvider extends AbstractPlainProvider {
* @return {@link IBundleProvider} instance that manages retrieving patients
*/
@SuppressWarnings("unused")
@Operation(name = PDQM_MATCH_OPERATION_NAME, type = PdqPatient.class)
@Operation(name = PDQM_MATCH_OPERATION_NAME, type = PdqPatient.class, bundleType = BundleTypeEnum.SEARCHSET)
public IBundleProvider pdqmMatch(
@OperationParam(name = RESOURCE, type = Patient.class) Patient resource,
@OperationParam(name = ONLY_CERTAIN_MATCHES, type = BooleanType.class) Boolean onlyCertainMatches,
@OperationParam(name = COUNT, type = IntegerType.class) Integer count,
@OperationParam(name = ONLY_CERTAIN_MATCHES, type = BooleanType.class) BooleanType onlyCertainMatches,
@OperationParam(name = COUNT, type = PositiveIntType.class) PositiveIntType count,
@Sort SortSpec sortSpec,
@IncludeParam Set<Include> includeSpec,
RequestDetails requestDetails,
HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse) {

var inParams = new Parameters();
inParams.addParameter().setResource(resource);
inParams.addParameter().setName(ONLY_CERTAIN_MATCHES).setValue(new BooleanType(onlyCertainMatches));
inParams.addParameter().setName(COUNT).setValue(new IntegerType(count));

// Run down the route
return requestBundleProvider(inParams, null, ResourceType.Patient.name(),
return requestBundleProvider(requestDetails.getResource(), null, ResourceType.Patient.name(),
httpServletRequest, httpServletResponse, requestDetails);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public Iti119TransactionConfiguration() {
new Iti119ClientAuditStrategy(),
new Iti119ServerAuditStrategy(),
FhirVersionEnum.R4,
new Iti119ResourceProvider(), // Consumer side. accept patient searches
new Iti119ResourceProvider(), // Consumer side. Accept patient searches
new Iti119ClientRequestFactory(),
FhirTransactionValidator.NO_VALIDATION);
setSupportsLazyLoading(true);
Expand Down
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.fhir.iti119;

import ca.uhn.fhir.interceptor.api.Hook;
import ca.uhn.fhir.interceptor.api.Interceptor;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Coding;

import java.util.Optional;

import static org.openehealth.ipf.commons.ihe.fhir.iti119.AdditionalResourceMetadataKeyEnum.ENTRY_MATCH_GRADE;

/**
* There is no universal mechanism to attach any kind of search attribute to a response bundle. As ITI-119 asks
* for a custom attribute that cannot been easily be handled within the BundleFactory, an interceptor is required.
* An instance of this interceptor must be registered with the {@link ca.uhn.fhir.rest.server.RestfulServer RestfulServer} or
* the {@link org.openehealth.ipf.commons.ihe.fhir.IpfFhirServlet IpfFhirServlet} subclass. The Spring Boot starter does
* this automatically.
*/
@Interceptor
public class MatchGradeEnumInterceptor {

public static final String MATCH_GRADE_EXTENSION_URL = "http://hl7.org/fhir/StructureDefinition/match-grade";

@Hook(Pointcut.SERVER_OUTGOING_RESPONSE)
@SuppressWarnings("unused")
public void insertMatchGrades(RequestDetails requestDetails, IBaseResource resource) {
if (resource instanceof Bundle bundle) {
bundle.getEntry().stream()
.filter(Bundle.BundleEntryComponent::hasResource)
.filter(Bundle.BundleEntryComponent::hasSearch)
.filter(bc -> Bundle.SearchEntryMode.MATCH.equals(bc.getSearch().getMode()))
.forEach(entry ->
Optional.ofNullable(ENTRY_MATCH_GRADE.get(entry.getResource())).ifPresent(matchGrade ->
entry.getSearch().addExtension(
MATCH_GRADE_EXTENSION_URL,
new Coding()
.setSystem(matchGrade.getSystem())
.setCode(matchGrade.toCode()))
)
);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Copyright 2015 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.fhir.iti119;

import ca.uhn.fhir.model.api.annotation.Extension;
import ca.uhn.fhir.model.api.annotation.*;
import ca.uhn.fhir.rest.gclient.DateClientParam;
import ca.uhn.fhir.rest.gclient.StringClientParam;
import ca.uhn.fhir.rest.gclient.TokenClientParam;
import ca.uhn.fhir.util.ElementUtil;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.r4.model.*;

import java.util.ArrayList;
import java.util.List;

/**
* Patient as defined by the PDQm specification
*
* @author Christian Ohr
* @since 5.0
*/
@ResourceDef(name = "Patient", id = "pdqm", profile = "https://profiles.ihe.net/ITI/PDQm/StructureDefinition/IHE.PDQm.MatchInputPatient")
public class PdqmMatchInputPatient extends Patient {


@Child(name = "mothersMaidenName")
@Extension(url = "http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName", definedLocally = false)
@Description(shortDefinition = "Mother's maiden name of a patient")
private HumanName mothersMaidenName;

@Override
public boolean isEmpty() {
return super.isEmpty() && ElementUtil.isEmpty(mothersMaidenName);
}

public HumanName getMothersMaidenName() {
if (mothersMaidenName == null) {
mothersMaidenName = new HumanName();
}
return mothersMaidenName;
}

public void setMothersMaidenName(HumanName mothersMaidenName) {
this.mothersMaidenName = mothersMaidenName;
}

public boolean hasMothersMaidenName() {
return this.mothersMaidenName != null && !this.mothersMaidenName.isEmpty();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,9 @@ public FhirQueryAuditDataset enrichAuditDatasetFromRequest(FhirQueryAuditDataset
.map(Parameters.ParametersParameterComponent::getValue)
.findFirst().orElseThrow(() -> new RuntimeException("No sourceIdentifier in PIX query"));

if (sourceIdentifier instanceof Identifier) {
var identifier = (Identifier) sourceIdentifier;
if (sourceIdentifier instanceof Identifier identifier) {
dataset.getPatientIds().add(String.format("%s|%s", identifier.getSystem(), identifier.getValue()));
} else if (sourceIdentifier instanceof StringType) {
var identifier = (StringType) sourceIdentifier;
} else if (sourceIdentifier instanceof StringType identifier) {
dataset.getPatientIds().add(identifier.getValue());
} else {
dataset.getPatientIds().add(sourceIdentifier.toString());
Expand Down
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.hl7.fhir.r4.model.Patient;
import org.openehealth.ipf.commons.ihe.fhir.IpfFhirServlet;
import org.openehealth.ipf.commons.ihe.fhir.iti119.Iti119Constants;
import org.openehealth.ipf.commons.ihe.fhir.iti119.MatchGradeEnumInterceptor;
import org.openehealth.ipf.platform.camel.ihe.fhir.test.FhirTestContainer;

/**
Expand All @@ -31,6 +32,7 @@ abstract class AbstractTestIti119 extends FhirTestContainer {

public static void startServer(String contextDescriptor, boolean secure) {
var servlet = new IpfFhirServlet(FhirVersionEnum.R4);
servlet.registerInterceptor(new MatchGradeEnumInterceptor());
startServer(servlet, contextDescriptor, secure, FhirTestContainer.DEMO_APP_PORT, "FhirServlet");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,30 @@
*/
package org.openehealth.ipf.platform.camel.ihe.fhir.iti119;

import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
import ca.uhn.fhir.model.valueset.BundleEntrySearchModeEnum;
import org.hl7.fhir.r4.model.HumanName;
import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.codesystems.MatchGrade;

import java.math.BigDecimal;
import java.util.List;

import static ca.uhn.fhir.model.api.ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE;
import static ca.uhn.fhir.model.api.ResourceMetadataKeyEnum.ENTRY_SEARCH_SCORE;
import static ca.uhn.fhir.model.valueset.BundleEntrySearchModeEnum.MATCH;
import static org.openehealth.ipf.commons.ihe.fhir.iti119.AdditionalResourceMetadataKeyEnum.ENTRY_MATCH_GRADE;

public enum ResponseCase {

OK;

public List<Patient> populateResponse(Parameters request) {
var patient = new Patient()
.addName(new HumanName().setFamily("Test"));
ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.put(patient, BundleEntrySearchModeEnum.MATCH);
patient.setId("Patient/4711");
ENTRY_SEARCH_MODE.put(patient, MATCH);
ENTRY_SEARCH_SCORE.put(patient, new BigDecimal(String.valueOf(0.95d)));
ENTRY_MATCH_GRADE.put(patient, MatchGrade.PROBABLE);
return List.of(patient);
}
}
Loading

0 comments on commit 7c29cf5

Please sign in to comment.