(#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 * @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( 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. * 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) { SearchParameterMap theParams, RequestDetails theRequest) {
return searchForIds(theParams, theRequest, null); 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 * create/update, this is the resource being searched for
* @since 5.5.0 * @since 5.5.0
*/ */
default <T extends IResourcePersistentId> List<T> searchForIds( default <PT extends IResourcePersistentId> List<PT> searchForIds(
SearchParameterMap theParams, SearchParameterMap theParams,
RequestDetails theRequest, RequestDetails theRequest,
@Nullable IBaseResource theConditionalOperationTargetOrNull) { @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.i18n.Msg;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry; import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; 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.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 ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import org.hl7.fhir.common.hapi.validation.validator.VersionSpecificWorkerContextWrapper; 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.FHIRException;
import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.IdType; import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r5.elementmodel.Element; 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.model.CanonicalResource;
import org.hl7.fhir.r5.utils.validation.IResourceValidator; import org.hl7.fhir.r5.utils.validation.IResourceValidator;
import org.hl7.fhir.r5.utils.validation.IValidatorResourceFetcher; import org.hl7.fhir.r5.utils.validation.IValidatorResourceFetcher;
import org.hl7.fhir.utilities.CanonicalPair;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.IOException; import java.util.List;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.util.Locale; import java.util.Locale;
public class ValidatorResourceFetcher implements IValidatorResourceFetcher { public class ValidatorResourceFetcher implements IValidatorResourceFetcher {
@ -50,22 +51,19 @@ public class ValidatorResourceFetcher implements IValidatorResourceFetcher {
private static final Logger ourLog = LoggerFactory.getLogger(ValidatorResourceFetcher.class); private static final Logger ourLog = LoggerFactory.getLogger(ValidatorResourceFetcher.class);
private final FhirContext myFhirContext; private final FhirContext myFhirContext;
private final IValidationSupport myValidationSupport;
private final DaoRegistry myDaoRegistry; private final DaoRegistry myDaoRegistry;
private final VersionSpecificWorkerContextWrapper myVersionSpecificContextWrapper; private final VersionSpecificWorkerContextWrapper myVersionSpecificContextWrapper;
public ValidatorResourceFetcher( public ValidatorResourceFetcher(
FhirContext theFhirContext, IValidationSupport theValidationSupport, DaoRegistry theDaoRegistry) { FhirContext theFhirContext, IValidationSupport theValidationSupport, DaoRegistry theDaoRegistry) {
myFhirContext = theFhirContext; myFhirContext = theFhirContext;
myValidationSupport = theValidationSupport;
myDaoRegistry = theDaoRegistry; myDaoRegistry = theDaoRegistry;
myVersionSpecificContextWrapper = myVersionSpecificContextWrapper =
VersionSpecificWorkerContextWrapper.newVersionSpecificWorkerContextWrapper(myValidationSupport); VersionSpecificWorkerContextWrapper.newVersionSpecificWorkerContextWrapper(theValidationSupport);
} }
@Override @Override
public Element fetch(IResourceValidator iResourceValidator, Object appContext, String theUrl) public Element fetch(IResourceValidator iResourceValidator, Object appContext, String theUrl) throws FHIRException {
throws FHIRFormatError, DefinitionException, FHIRException, IOException {
IdType id = new IdType(theUrl); IdType id = new IdType(theUrl);
String resourceType = id.getResourceType(); String resourceType = id.getResourceType();
IFhirResourceDao<?> dao = myDaoRegistry.getResourceDao(resourceType); IFhirResourceDao<?> dao = myDaoRegistry.getResourceDao(resourceType);
@ -74,9 +72,13 @@ public class ValidatorResourceFetcher implements IValidatorResourceFetcher {
target = dao.read(id, (RequestDetails) appContext); target = dao.read(id, (RequestDetails) appContext);
} catch (ResourceNotFoundException e) { } catch (ResourceNotFoundException e) {
ourLog.info("Failed to resolve local reference: {}", theUrl); 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 { try {
return new JsonParser(myVersionSpecificContextWrapper) return new JsonParser(myVersionSpecificContextWrapper)
.parse(myFhirContext.newJsonParser().encodeResourceToString(target), resourceType); .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 @Override
public boolean resolveURL( public boolean resolveURL(
IResourceValidator iResourceValidator, Object o, String s, String s1, String s2, boolean isCanonical) IResourceValidator iResourceValidator, Object o, String s, String s1, String s2, boolean isCanonical) {
throws IOException, FHIRException {
return true; return true;
} }
@Override @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)); throw new UnsupportedOperationException(Msg.code(577));
} }
@ -104,8 +131,7 @@ public class ValidatorResourceFetcher implements IValidatorResourceFetcher {
} }
@Override @Override
public CanonicalResource fetchCanonicalResource(IResourceValidator iResourceValidator, String s) public CanonicalResource fetchCanonicalResource(IResourceValidator iResourceValidator, String s) {
throws URISyntaxException {
return null; 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