(#5442) fetch should also resolve canonical URL references (#5443)

* (#5442) fetch should also resolve canonical URL references

* (#5442) - added test

* (#5442) - cleanup?

* (#5442) - changelog and reorganize test

* (#5442) PR feedback

* (#5442) PR feedback

* (#5442) cleared ValidatorResourceFetcher linter warnings

* (#5442) - new error code

* (#5442) caught additional error

* (#5442) spotless apply

* (#5442) spotless apply

---------

Co-authored-by: taha.attari@smilecdr.com <taha.attari@smilecdr.com>
This commit is contained in:
Taha 2023-11-27 20:12:35 -08:00 committed by GitHub
parent 83bfa817ae
commit 63eed3936b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 1516 additions and 22 deletions

View File

@ -0,0 +1,4 @@
---
type: add
issue: 5442
title: "The ValidatorResourceFetcher will now resolve canonical URL references as well as simple local references."

View File

@ -327,18 +327,29 @@ public interface IFhirResourceDao<T extends IBaseResource> extends IDao {
/**
* @deprecated Use {@link #search(SearchParameterMap, RequestDetails)} instead
* @throws InvalidRequestException If a SearchParameter is not known to the server
*/
IBundleProvider search(SearchParameterMap theParams);
IBundleProvider search(SearchParameterMap theParams) throws InvalidRequestException;
IBundleProvider search(SearchParameterMap theParams, RequestDetails theRequestDetails);
/**
* *
* @throws InvalidRequestException If a SearchParameter is not known to the server
*/
IBundleProvider search(SearchParameterMap theParams, RequestDetails theRequestDetails)
throws InvalidRequestException;
/**
* *
* @throws InvalidRequestException If a SearchParameter is not known to the server
*/
IBundleProvider search(
SearchParameterMap theParams, RequestDetails theRequestDetails, HttpServletResponse theServletResponse);
SearchParameterMap theParams, RequestDetails theRequestDetails, HttpServletResponse theServletResponse)
throws InvalidRequestException;
/**
* Search for IDs for processing a match URLs, etc.
*/
default <T extends IResourcePersistentId> List<T> searchForIds(
default <PT extends IResourcePersistentId> List<PT> searchForIds(
SearchParameterMap theParams, RequestDetails theRequest) {
return searchForIds(theParams, theRequest, null);
}
@ -350,7 +361,7 @@ public interface IFhirResourceDao<T extends IBaseResource> extends IDao {
* create/update, this is the resource being searched for
* @since 5.5.0
*/
default <T extends IResourcePersistentId> List<T> searchForIds(
default <PT extends IResourcePersistentId> List<PT> searchForIds(
SearchParameterMap theParams,
RequestDetails theRequest,
@Nullable IBaseResource theConditionalOperationTargetOrNull) {

View File

@ -24,12 +24,14 @@ import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.i18n.Msg;
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.RequestDetails;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.param.UriParam;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import org.hl7.fhir.common.hapi.validation.validator.VersionSpecificWorkerContextWrapper;
import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r5.elementmodel.Element;
@ -37,12 +39,11 @@ import org.hl7.fhir.r5.elementmodel.JsonParser;
import org.hl7.fhir.r5.model.CanonicalResource;
import org.hl7.fhir.r5.utils.validation.IResourceValidator;
import org.hl7.fhir.r5.utils.validation.IValidatorResourceFetcher;
import org.hl7.fhir.utilities.CanonicalPair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.util.List;
import java.util.Locale;
public class ValidatorResourceFetcher implements IValidatorResourceFetcher {
@ -50,22 +51,19 @@ public class ValidatorResourceFetcher implements IValidatorResourceFetcher {
private static final Logger ourLog = LoggerFactory.getLogger(ValidatorResourceFetcher.class);
private final FhirContext myFhirContext;
private final IValidationSupport myValidationSupport;
private final DaoRegistry myDaoRegistry;
private final VersionSpecificWorkerContextWrapper myVersionSpecificContextWrapper;
public ValidatorResourceFetcher(
FhirContext theFhirContext, IValidationSupport theValidationSupport, DaoRegistry theDaoRegistry) {
myFhirContext = theFhirContext;
myValidationSupport = theValidationSupport;
myDaoRegistry = theDaoRegistry;
myVersionSpecificContextWrapper =
VersionSpecificWorkerContextWrapper.newVersionSpecificWorkerContextWrapper(myValidationSupport);
VersionSpecificWorkerContextWrapper.newVersionSpecificWorkerContextWrapper(theValidationSupport);
}
@Override
public Element fetch(IResourceValidator iResourceValidator, Object appContext, String theUrl)
throws FHIRFormatError, DefinitionException, FHIRException, IOException {
public Element fetch(IResourceValidator iResourceValidator, Object appContext, String theUrl) throws FHIRException {
IdType id = new IdType(theUrl);
String resourceType = id.getResourceType();
IFhirResourceDao<?> dao = myDaoRegistry.getResourceDao(resourceType);
@ -74,9 +72,13 @@ public class ValidatorResourceFetcher implements IValidatorResourceFetcher {
target = dao.read(id, (RequestDetails) appContext);
} catch (ResourceNotFoundException e) {
ourLog.info("Failed to resolve local reference: {}", theUrl);
return null;
try {
target = fetchByUrl(theUrl, dao, (RequestDetails) appContext);
} catch (ResourceNotFoundException e2) {
ourLog.info("Failed to find resource by URL: {}", theUrl);
return null;
}
}
try {
return new JsonParser(myVersionSpecificContextWrapper)
.parse(myFhirContext.newJsonParser().encodeResourceToString(target), resourceType);
@ -85,15 +87,40 @@ public class ValidatorResourceFetcher implements IValidatorResourceFetcher {
}
}
private IBaseResource fetchByUrl(String url, IFhirResourceDao<?> dao, RequestDetails requestDetails)
throws ResourceNotFoundException {
CanonicalPair pair = new CanonicalPair(url);
SearchParameterMap searchParameterMap = new SearchParameterMap();
searchParameterMap.add("url", new UriParam(pair.getUrl()));
String version = pair.getVersion();
if (version != null && !version.isEmpty()) {
searchParameterMap.add("version", new TokenParam(version));
}
List<IBaseResource> results = null;
try {
results = dao.search(searchParameterMap, requestDetails).getAllResources();
} catch (InvalidRequestException e) {
ourLog.info("Resource does not support 'url' or 'version' Search Parameters");
}
if (results != null && results.size() > 0) {
if (results.size() > 1) {
ourLog.warn(
String.format("Multiple results found for URL '%s', only the first will be considered.", url));
}
return results.get(0);
} else {
throw new ResourceNotFoundException(Msg.code(2444) + "Failed to find resource by URL: " + url);
}
}
@Override
public boolean resolveURL(
IResourceValidator iResourceValidator, Object o, String s, String s1, String s2, boolean isCanonical)
throws IOException, FHIRException {
IResourceValidator iResourceValidator, Object o, String s, String s1, String s2, boolean isCanonical) {
return true;
}
@Override
public byte[] fetchRaw(IResourceValidator iResourceValidator, String s) throws MalformedURLException, IOException {
public byte[] fetchRaw(IResourceValidator iResourceValidator, String s) throws UnsupportedOperationException {
throw new UnsupportedOperationException(Msg.code(577));
}
@ -104,8 +131,7 @@ public class ValidatorResourceFetcher implements IValidatorResourceFetcher {
}
@Override
public CanonicalResource fetchCanonicalResource(IResourceValidator iResourceValidator, String s)
throws URISyntaxException {
public CanonicalResource fetchCanonicalResource(IResourceValidator iResourceValidator, String s) {
return null;
}

View File

@ -0,0 +1,65 @@
package ca.uhn.fhir.jpa.validation;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.DefaultProfileValidationSupport;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.api.server.SystemRequestDetails;
import ca.uhn.fhir.rest.server.SimpleBundleProvider;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.test.BaseTest;
import ca.uhn.fhir.util.ClasspathUtil;
import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator;
import org.hl7.fhir.common.hapi.validation.validator.VersionSpecificWorkerContextWrapper;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r5.elementmodel.Element;
import org.hl7.fhir.r5.utils.XVerExtensionManager;
import org.hl7.fhir.validation.instance.InstanceValidator;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
public class ValidatorResourceFetcherTest extends BaseTest {
private static final FhirContext ourCtx = FhirContext.forR4();
private static final DefaultProfileValidationSupport myDefaultValidationSupport = new DefaultProfileValidationSupport(ourCtx);
private static ValidatorResourceFetcher fetcher;
private static DaoRegistry mockDaoRegistry;
private static IFhirResourceDao<IBaseResource> mockResourceDao;
@SuppressWarnings("unchecked")
@BeforeEach
public void before() {
mockDaoRegistry = mock(DaoRegistry.class);
mockResourceDao = mock(IFhirResourceDao.class);
fetcher = new ValidatorResourceFetcher(ourCtx, myDefaultValidationSupport, mockDaoRegistry);
}
@Test
public void checkFetchByUrl() {
// setup mocks
String resource = ClasspathUtil.loadResource("/q_jon_with_url_version.json");
doReturn(mockResourceDao).when(mockDaoRegistry).getResourceDao("Questionnaire");
doThrow(new ResourceNotFoundException("Not Found")).when(mockResourceDao).read(any(),any());
doReturn(new SimpleBundleProvider(List.of(
ourCtx.newJsonParser().parseResource(resource)
))).when(mockResourceDao).search(any(),any());
VersionSpecificWorkerContextWrapper wrappedWorkerContext = VersionSpecificWorkerContextWrapper.newVersionSpecificWorkerContextWrapper(myDefaultValidationSupport);
InstanceValidator v = new InstanceValidator(
wrappedWorkerContext,
new FhirInstanceValidator.NullEvaluationContext(),
new XVerExtensionManager(null));
RequestDetails r = new SystemRequestDetails();
// test
Element returnedResource = fetcher.fetch(v, r,"http://www.test-url-for-questionnaire.com/Questionnaire/test-id|1.0.0");
assertNotNull(returnedResource);
}
}

File diff suppressed because it is too large Load Diff