[6463] use version in canonical url of StructureDefinition to identify it (#6534)
* [6463] use version in canonical url of StructureDefinition to identify it * Add credit for #6534 --------- Co-authored-by: James Agnew <jamesagnew@gmail.com>
This commit is contained in:
parent
06580742d4
commit
f1318915fe
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
type: add
|
||||
issue: 6534
|
||||
title: "The JpaPersistedValidationSupport module which is used to fetch
|
||||
conformance resources from the JPA repository for validation purposes can
|
||||
now support versioned URLs. Thanks to Mangala Ekanayake for the contribution!"
|
|
@ -235,7 +235,12 @@ public class JpaPersistedResourceValidationSupport implements IValidationSupport
|
|||
}
|
||||
SearchParameterMap params = new SearchParameterMap();
|
||||
params.setLoadSynchronousUpTo(1);
|
||||
params.add(StructureDefinition.SP_URL, new UriParam(theUri));
|
||||
int versionSeparator = theUri.lastIndexOf('|');
|
||||
if (versionSeparator != -1) {params.add(StructureDefinition.SP_VERSION, new TokenParam(theUri.substring(versionSeparator + 1)));
|
||||
params.add(StructureDefinition.SP_URL, new UriParam(theUri.substring(0, versionSeparator)));
|
||||
} else {
|
||||
params.add(StructureDefinition.SP_URL, new UriParam(theUri));
|
||||
}
|
||||
search = myDaoRegistry.getResourceDao("StructureDefinition").search(params);
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,119 @@
|
|||
package ca.uhn.fhir.jpa.dao;
|
||||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR JPA Server
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2024 Smile CDR, Inc.
|
||||
* %%
|
||||
* 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.
|
||||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.support.IValidationSupport;
|
||||
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
||||
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
|
||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||
import ca.uhn.fhir.rest.param.TokenParam;
|
||||
import ca.uhn.fhir.rest.param.UriParam;
|
||||
import org.hl7.fhir.r4.model.StructureDefinition;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class JpaPersistedResourceValidationSupportTest {
|
||||
|
||||
private FhirContext theFhirContext = FhirContext.forR4();
|
||||
|
||||
@Nested
|
||||
class FetchStructureDefinitionTests {
|
||||
|
||||
@Mock
|
||||
private DaoRegistry myDaoRegistry;
|
||||
|
||||
@InjectMocks
|
||||
private final JpaPersistedResourceValidationSupport testClass = new JpaPersistedResourceValidationSupport(theFhirContext);
|
||||
|
||||
@Captor
|
||||
ArgumentCaptor<SearchParameterMap> searchParameterMapCaptor;
|
||||
|
||||
@Test
|
||||
@DisplayName("fetch StructureDefinition by version less url")
|
||||
void fetchStructureDefinitionForUrl() {
|
||||
final String profileUrl = "http://example.com/fhir/StructureDefinition/exampleProfile";
|
||||
IFhirResourceDao mockDao = mock(IFhirResourceDao.class);
|
||||
when(mockDao.search(any())).thenReturn(mock(IBundleProvider.class));
|
||||
when(myDaoRegistry.getResourceDao(anyString())).thenReturn(mockDao);
|
||||
|
||||
testClass.fetchResource(StructureDefinition.class, profileUrl);
|
||||
|
||||
verify(mockDao).search(searchParameterMapCaptor.capture());
|
||||
SearchParameterMap searchParams = searchParameterMapCaptor.getValue();
|
||||
String uriParam = searchParams.get(StructureDefinition.SP_URL)
|
||||
.get(0)
|
||||
.stream()
|
||||
.map(UriParam.class::cast)
|
||||
.map(UriParam::getValue)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
assertThat(uriParam).isEqualTo(profileUrl);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("fetch StructureDefinition by versioned url")
|
||||
void fetchStructureDefinitionForVersionedUrl() {
|
||||
final String profileUrl = "http://example.com/fhir/StructureDefinition/exampleProfile|1.1.0";
|
||||
IFhirResourceDao mockDao = mock(IFhirResourceDao.class);
|
||||
when(mockDao.search(any())).thenReturn(mock(IBundleProvider.class));
|
||||
when(myDaoRegistry.getResourceDao(anyString())).thenReturn(mockDao);
|
||||
|
||||
testClass.fetchResource(StructureDefinition.class, profileUrl);
|
||||
|
||||
verify(mockDao).search(searchParameterMapCaptor.capture());
|
||||
SearchParameterMap searchParams = searchParameterMapCaptor.getValue();
|
||||
String uriParam = searchParams.get(StructureDefinition.SP_URL)
|
||||
.get(0)
|
||||
.stream()
|
||||
.map(UriParam.class::cast)
|
||||
.map(UriParam::getValue)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
assertThat(uriParam).isEqualTo("http://example.com/fhir/StructureDefinition/exampleProfile");
|
||||
|
||||
String versionParam = searchParams.get(StructureDefinition.SP_VERSION)
|
||||
.get(0)
|
||||
.stream()
|
||||
.map(TokenParam.class::cast)
|
||||
.map(TokenParam::getValue)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
assertThat(versionParam).isEqualTo("1.1.0");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -123,8 +123,8 @@ public class RemoteTerminologyServiceJpaR4Test extends BaseJpaR4Test {
|
|||
// Verify 1
|
||||
Assertions.assertEquals(2, myCaptureQueriesListener.countGetConnections());
|
||||
assertThat(ourValueSetProvider.mySearchUrls).asList().containsExactlyInAnyOrder(
|
||||
"http://hl7.org/fhir/ValueSet/administrative-gender",
|
||||
"http://hl7.org/fhir/ValueSet/administrative-gender"
|
||||
"http://hl7.org/fhir/ValueSet/administrative-gender|4.0.1",
|
||||
"http://hl7.org/fhir/ValueSet/administrative-gender","http://hl7.org/fhir/ValueSet/administrative-gender"
|
||||
);
|
||||
assertThat(ourCodeSystemProvider.mySearchUrls).asList().containsExactlyInAnyOrder(
|
||||
"http://hl7.org/fhir/administrative-gender",
|
||||
|
@ -162,7 +162,7 @@ public class RemoteTerminologyServiceJpaR4Test extends BaseJpaR4Test {
|
|||
// Verify 1
|
||||
Assertions.assertEquals(0, myCaptureQueriesListener.countGetConnections());
|
||||
assertThat(ourValueSetProvider.mySearchUrls).asList().containsExactlyInAnyOrder(
|
||||
"http://hl7.org/fhir/ValueSet/administrative-gender",
|
||||
"http://hl7.org/fhir/ValueSet/administrative-gender|4.0.1",
|
||||
"http://hl7.org/fhir/ValueSet/administrative-gender"
|
||||
);
|
||||
assertThat(ourValueSetProvider.myValidatedCodes).asList().containsExactlyInAnyOrder(
|
||||
|
@ -215,14 +215,18 @@ public class RemoteTerminologyServiceJpaR4Test extends BaseJpaR4Test {
|
|||
// Verify 1
|
||||
Assertions.assertEquals(0, myCaptureQueriesListener.countGetConnections());
|
||||
assertThat(ourValueSetProvider.mySearchUrls).asList().containsExactlyInAnyOrder(
|
||||
"http://hl7.org/fhir/ValueSet/administrative-gender",
|
||||
"http://hl7.org/fhir/ValueSet/administrative-gender|4.0.1",
|
||||
"http://hl7.org/fhir/ValueSet/administrative-gender"
|
||||
);
|
||||
assertThat(ourValueSetProvider.myValidatedCodes).asList().containsExactlyInAnyOrder(
|
||||
"http://hl7.org/fhir/ValueSet/administrative-gender#null#female"
|
||||
"http://hl7.org/fhir/ValueSet/administrative-gender#http://hl7.org/fhir/administrative-gender#female"
|
||||
);
|
||||
assertThat(ourCodeSystemProvider.mySearchUrls).asList().containsExactlyInAnyOrder(
|
||||
"http://hl7.org/fhir/administrative-gender"
|
||||
);
|
||||
assertThat(ourCodeSystemProvider.myValidatedCodes).asList().containsExactlyInAnyOrder(
|
||||
"http://hl7.org/fhir/administrative-gender#female#null"
|
||||
);
|
||||
assertThat(ourCodeSystemProvider.mySearchUrls).asList().isEmpty();
|
||||
assertThat(ourCodeSystemProvider.myValidatedCodes).asList().isEmpty();
|
||||
|
||||
// Test 2 (should rely on caches)
|
||||
ourCodeSystemProvider.clearCalls();
|
||||
|
|
|
@ -94,7 +94,7 @@ public class ValidateWithRemoteTerminologyTest extends BaseResourceProviderR4Tes
|
|||
final String classSystem = "http://terminology.hl7.org/CodeSystem/v3-ActCode";
|
||||
final String identifierTypeSystem = "http://terminology.hl7.org/CodeSystem/v2-0203";
|
||||
|
||||
setupValueSetValidateCode("http://hl7.org/fhir/ValueSet/encounter-status", "http://hl7.org/fhir/encounter-status", statusCode, "validation/encounter/validateCode-ValueSet-encounter-status.json");
|
||||
setupValueSetValidateCode("http://hl7.org/fhir/ValueSet/encounter-status", "4.0.1","http://hl7.org/fhir/encounter-status", statusCode, "validation/encounter/validateCode-ValueSet-encounter-status.json");
|
||||
setupValueSetValidateCode("http://terminology.hl7.org/ValueSet/v3-ActEncounterCode", "http://terminology.hl7.org/CodeSystem/v3-ActCode", classCode, "validation/encounter/validateCode-ValueSet-v3-ActEncounterCode.json");
|
||||
setupValueSetValidateCode("http://hl7.org/fhir/ValueSet/identifier-type", "http://hl7.org/fhir/identifier-type", identifierTypeCode, "validation/encounter/validateCode-ValueSet-identifier-type.json");
|
||||
|
||||
|
@ -138,7 +138,7 @@ public class ValidateWithRemoteTerminologyTest extends BaseResourceProviderR4Tes
|
|||
final String loincSystem = "http://loinc.org";
|
||||
final String system = "http://fhir.infoway-inforoute.ca/io/psca/CodeSystem/ICD9CM";
|
||||
|
||||
setupValueSetValidateCode("http://hl7.org/fhir/ValueSet/observation-status", statusSystem, statusCode, "validation/observation/validateCode-ValueSet-observation-status.json");
|
||||
setupValueSetValidateCode("http://hl7.org/fhir/ValueSet/observation-status", "4.0.1", statusSystem, statusCode, "validation/observation/validateCode-ValueSet-observation-status.json");
|
||||
setupValueSetValidateCode("http://hl7.org/fhir/ValueSet/observation-codes", loincSystem, statusCode, "validation/observation/validateCode-ValueSet-codes.json");
|
||||
|
||||
setupCodeSystemValidateCode(statusSystem, statusCode, "validation/observation/validateCode-CodeSystem-observation-status.json");
|
||||
|
@ -171,7 +171,7 @@ public class ValidateWithRemoteTerminologyTest extends BaseResourceProviderR4Tes
|
|||
final String statusSystem = "http://hl7.org/fhir/event-status";
|
||||
final String snomedSystem = "http://snomed.info/sct";
|
||||
|
||||
setupValueSetValidateCode("http://hl7.org/fhir/ValueSet/event-status", statusSystem, statusCode, "validation/procedure/validateCode-ValueSet-event-status.json");
|
||||
setupValueSetValidateCode("http://hl7.org/fhir/ValueSet/event-status", "4.0.1", statusSystem, statusCode, "validation/procedure/validateCode-ValueSet-event-status.json");
|
||||
setupValueSetValidateCode("http://hl7.org/fhir/ValueSet/procedure-code", snomedSystem, procedureCode1, "validation/procedure/validateCode-ValueSet-procedure-code-valid.json");
|
||||
setupValueSetValidateCode("http://hl7.org/fhir/ValueSet/procedure-code", snomedSystem, procedureCode2, "validation/procedure/validateCode-ValueSet-procedure-code-invalid.json");
|
||||
|
||||
|
@ -213,7 +213,7 @@ public class ValidateWithRemoteTerminologyTest extends BaseResourceProviderR4Tes
|
|||
final String snomedSystem = "http://snomed.info/sct";
|
||||
final String absentUnknownSystem = "http://hl7.org/fhir/uv/ips/CodeSystem/absent-unknown-uv-ips";
|
||||
|
||||
setupValueSetValidateCode("http://hl7.org/fhir/ValueSet/event-status", statusSystem, statusCode, "validation/procedure/validateCode-ValueSet-event-status.json");
|
||||
setupValueSetValidateCode("http://hl7.org/fhir/ValueSet/event-status", "4.0.1", statusSystem, statusCode, "validation/procedure/validateCode-ValueSet-event-status.json");
|
||||
setupValueSetValidateCode("http://hl7.org/fhir/ValueSet/procedure-code", snomedSystem, procedureCode, "validation/procedure/validateCode-ValueSet-procedure-code-invalid-slice.json");
|
||||
setupValueSetValidateCode("http://hl7.org/fhir/uv/ips/ValueSet/absent-or-unknown-procedures-uv-ips", absentUnknownSystem, procedureCode, "validation/procedure/validateCode-ValueSet-absent-or-unknown-procedure.json");
|
||||
|
||||
|
@ -245,6 +245,13 @@ public class ValidateWithRemoteTerminologyTest extends BaseResourceProviderR4Tes
|
|||
// which also attempts a validateCode against the CodeSystem after the validateCode against the ValueSet
|
||||
}
|
||||
|
||||
private void setupValueSetValidateCode(String theUrl, String theVersion, String theSystem, String theCode, String theTerminologyResponseFile) {
|
||||
ValueSet valueSet = myValueSetProvider.addTerminologyResource(theUrl, theVersion);
|
||||
myValueSetProvider.addTerminologyResource(theSystem, theVersion);
|
||||
myValueSetProvider.addTerminologyResponse(OPERATION_VALIDATE_CODE, valueSet.getUrl(), theCode, ourCtx, theTerminologyResponseFile);
|
||||
|
||||
valueSet.getCompose().addInclude().setSystem(theSystem);
|
||||
}
|
||||
private void setupCodeSystemValidateCode(String theUrl, String theCode, String theTerminologyResponseFile) {
|
||||
CodeSystem codeSystem = myCodeSystemProvider.addTerminologyResource(theUrl);
|
||||
myCodeSystemProvider.addTerminologyResponse(OPERATION_VALIDATE_CODE, codeSystem.getUrl(), theCode, ourCtx, theTerminologyResponseFile);
|
||||
|
|
|
@ -285,7 +285,6 @@ public class ResourceProviderR5Test extends BaseResourceProviderR5Test {
|
|||
"source": "#384dd6bccaeafa6c"
|
||||
},
|
||||
"url": "https://health.gov.on.ca/idms/fhir/SearchParameter/MedicinalProductDefinition-SearchableString",
|
||||
"version": "1.0.0",
|
||||
"name": "MedicinalProductDefinitionSearchableString",
|
||||
"status": "active",
|
||||
"publisher": "MOH-IDMS",
|
||||
|
|
|
@ -85,8 +85,12 @@ public interface IValidationProviders {
|
|||
myTerminologyResourceMap.put(theUrl, theResource);
|
||||
}
|
||||
|
||||
protected void addVersionedTerminologyResource(String theUrl, String theVersion, T theResource) {
|
||||
myTerminologyResourceMap.put(theUrl + "|" + theVersion, theResource);
|
||||
}
|
||||
public abstract T addTerminologyResource(String theUrl);
|
||||
|
||||
public abstract T addTerminologyResource(String theUrl, String theVersion);
|
||||
protected IBaseParameters getTerminologyResponse(String theOperation, String theUrl, String theCode) throws Exception {
|
||||
String inputKey = getInputKey(theOperation, theUrl, theCode);
|
||||
if (myExceptionMap.containsKey(inputKey)) {
|
||||
|
|
|
@ -95,6 +95,10 @@ public interface IValidationProvidersDstu3 {
|
|||
addTerminologyResource(theUrl, codeSystem);
|
||||
return codeSystem;
|
||||
}
|
||||
@Override
|
||||
public CodeSystem addTerminologyResource(String theUrl, String theVersion) {
|
||||
return addTerminologyResource(theUrl);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
|
@ -133,5 +137,9 @@ public interface IValidationProvidersDstu3 {
|
|||
addTerminologyResource(theUrl, valueSet);
|
||||
return valueSet;
|
||||
}
|
||||
@Override
|
||||
public ValueSet addTerminologyResource(String theUrl, String theVersion) {
|
||||
return addTerminologyResource(theUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -99,6 +99,14 @@ public interface IValidationProvidersR4 {
|
|||
addTerminologyResource(theUrl, codeSystem);
|
||||
return codeSystem;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeSystem addTerminologyResource(String theUrl, String theVersion) {
|
||||
CodeSystem codeSystem = addTerminologyResource(theUrl);
|
||||
codeSystem.setVersion(theVersion);
|
||||
addVersionedTerminologyResource(theUrl, theVersion, codeSystem);
|
||||
return codeSystem;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
|
@ -138,5 +146,12 @@ public interface IValidationProvidersR4 {
|
|||
addTerminologyResource(theUrl, valueSet);
|
||||
return valueSet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValueSet addTerminologyResource(String theUrl, String theVersion) {
|
||||
ValueSet valueSet = addTerminologyResource(theUrl);
|
||||
addVersionedTerminologyResource(theUrl, theVersion, valueSet);
|
||||
return valueSet;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
|||
import ca.uhn.hapi.converters.canonical.VersionCanonicalizer;
|
||||
import jakarta.annotation.Nonnull;
|
||||
import jakarta.annotation.Nullable;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.apache.commons.lang3.builder.EqualsBuilder;
|
||||
import org.apache.commons.lang3.builder.HashCodeBuilder;
|
||||
|
@ -460,20 +461,13 @@ public class VersionSpecificWorkerContextWrapper extends I18nBase implements IWo
|
|||
return null;
|
||||
}
|
||||
|
||||
String uri = theUri;
|
||||
// handle profile version, if present
|
||||
if (theUri.contains("|")) {
|
||||
String[] parts = theUri.split("\\|");
|
||||
if (parts.length == 2) {
|
||||
uri = parts[0];
|
||||
} else {
|
||||
ourLog.warn("Unrecognized profile uri: {}", theUri);
|
||||
}
|
||||
if (StringUtils.countMatches(theUri, "|") > 1) {
|
||||
ourLog.warn("Unrecognized profile uri: {}", theUri);
|
||||
}
|
||||
|
||||
String resourceType = getResourceType(class_);
|
||||
@SuppressWarnings("unchecked")
|
||||
T retVal = (T) fetchResource(resourceType, uri);
|
||||
T retVal = (T) fetchResource(resourceType, theUri);
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue