Issue 2332 create via put for implementationguide that references some placeholders (#2423)

* begin with failing test

* fixed.  test passes.

* documentation and changelog

* added ConceptMap

* review feedback
This commit is contained in:
Ken Stevens 2021-02-26 09:44:48 -05:00 committed by GitHub
parent 60b462540f
commit 7d769e3b05
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 107 additions and 29 deletions

View File

@ -32,6 +32,7 @@ import org.apache.jena.riot.Lang;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@ -103,7 +104,7 @@ public class FhirContext {
private AddProfileTagEnum myAddProfileTagWhenEncoding = AddProfileTagEnum.ONLY_FOR_CUSTOM;
private volatile Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> myClassToElementDefinition = Collections.emptyMap();
private ArrayList<Class<? extends IBase>> myCustomTypes;
private Map<String, Class<? extends IBaseResource>> myDefaultTypeForProfile = new HashMap<>();
private final Map<String, Class<? extends IBaseResource>> myDefaultTypeForProfile = new HashMap<>();
private volatile Map<String, RuntimeResourceDefinition> myIdToResourceDefinition = Collections.emptyMap();
private volatile boolean myInitialized;
private volatile boolean myInitializing = false;
@ -114,8 +115,8 @@ public class FhirContext {
private volatile INarrativeGenerator myNarrativeGenerator;
private volatile IParserErrorHandler myParserErrorHandler = new LenientErrorHandler();
private ParserOptions myParserOptions = new ParserOptions();
private Set<PerformanceOptionsEnum> myPerformanceOptions = new HashSet<>();
private Collection<Class<? extends IBaseResource>> myResourceTypesToScan;
private final Set<PerformanceOptionsEnum> myPerformanceOptions = new HashSet<>();
private final Collection<Class<? extends IBaseResource>> myResourceTypesToScan;
private volatile IRestfulClientFactory myRestfulClientFactory;
private volatile RuntimeChildUndeclaredExtensionDefinition myRuntimeChildUndeclaredExtensionDefinition;
private IValidationSupport myValidationSupport;
@ -1066,4 +1067,10 @@ public class FhirContext {
return retVal;
}
// TODO KHS add the other primitive types
public IPrimitiveType<Boolean> getPrimitiveBoolean(Boolean theValue) {
IPrimitiveType<Boolean> retval = (IPrimitiveType<Boolean>) getElementDefinition("boolean").newInstance();
retval.setValue(theValue);
return retval;
}
}

View File

@ -101,6 +101,17 @@ public class HapiExtensions {
public static final String EXT_META_SOURCE = "http://hapifhir.io/fhir/StructureDefinition/resource-meta-source";
public static final String EXT_SP_UNIQUE = "http://hapifhir.io/fhir/StructureDefinition/sp-unique";
/**
* URL for extension on a Phonetic String SearchParameter indicating that text values should be phonetically indexed with the named encoder
*/
public static final String EXT_SEARCHPARAM_PHONETIC_ENCODER = "http://hapifhir.io/fhir/StructureDefinition/searchparameter-phonetic-encoder";
/**
* URL for boolean extension added to all placeholder resources
*/
public static final String EXT_RESOURCE_META_PLACEHOLDER = "http://hapifhir.io/fhir/StructureDefinition/resource-meta-placeholder";
/**
* Non instantiable
*/

View File

@ -80,10 +80,7 @@ public class ParametersUtil {
.filter(t -> t instanceof IPrimitiveType<?>)
.map(t -> ((IPrimitiveType<?>) t))
.findFirst();
if (!nameValue.isPresent() || !theParameterName.equals(nameValue.get().getValueAsString())) {
return false;
}
return true;
return nameValue.isPresent() && theParameterName.equals(nameValue.get().getValueAsString());
})
.collect(Collectors.toList());
@ -227,9 +224,7 @@ public class ParametersUtil {
@SuppressWarnings("unchecked")
public static void addParameterToParametersBoolean(FhirContext theCtx, IBaseParameters theParameters, String theName, boolean theValue) {
IPrimitiveType<Boolean> value = (IPrimitiveType<Boolean>) theCtx.getElementDefinition("boolean").newInstance();
value.setValue(theValue);
addParameterToParameters(theCtx, theParameters, theName, value);
addParameterToParameters(theCtx, theParameters, theName, theCtx.getPrimitiveBoolean(theValue));
}
@SuppressWarnings("unchecked")
@ -310,10 +305,7 @@ public class ParametersUtil {
}
public static void addPartBoolean(FhirContext theContext, IBase theParameter, String theName, Boolean theValue) {
IPrimitiveType<Boolean> value = (IPrimitiveType<Boolean>) theContext.getElementDefinition("boolean").newInstance();
value.setValue(theValue);
addPart(theContext, theParameter, theName, value);
addPart(theContext, theParameter, theName, theContext.getPrimitiveBoolean(theValue));
}
public static void addPartDecimal(FhirContext theContext, IBase theParameter, String theName, Double theValue) {

View File

@ -0,0 +1,5 @@
---
type: add
issue: 2332
title: "All created placeholder resources now have a meta extension with the url http://hapifhir.io/fhir/StructureDefinition/resource-meta-placeholder
and the value 'true'. Also, terminology storage is now skipped for placeholder ValueSet and ConceptMap resources."

View File

@ -88,7 +88,7 @@ public class DaoConfig {
* Child Configurations
*/
private ModelConfig myModelConfig = new ModelConfig();
private final ModelConfig myModelConfig = new ModelConfig();
/**
* update setter javadoc if default changes
@ -171,7 +171,7 @@ public class DaoConfig {
*
* @since 4.1.0
*/
private int myPreExpandValueSetsDefaultOffset = 0;
private final int myPreExpandValueSetsDefaultOffset = 0;
/**
* Do not change default of {@code 1000}!
*
@ -1067,6 +1067,9 @@ public class DaoConfig {
* This property can be useful in cases where replication between two servers is wanted.
* Note however that references containing purely numeric IDs will not be auto-created
* as they are never allowed to be client supplied in HAPI FHIR JPA.
*
* All placeholder resources created in this way have a meta extension
* with the URL {@link HapiExtensions#EXT_RESOURCE_META_PLACEHOLDER} and the value "true".
* </p>
*/
public boolean isAutoCreatePlaceholderReferenceTargets() {
@ -1088,6 +1091,9 @@ public class DaoConfig {
* This property can be useful in cases where replication between two servers is wanted.
* Note however that references containing purely numeric IDs will not be auto-created
* as they are never allowed to be client supplied in HAPI FHIR JPA.
*
* All placeholder resources created in this way have a meta extension
* with the URL {@link HapiExtensions#EXT_RESOURCE_META_PLACEHOLDER} and the value "true".
* </p>
*/
public void setAutoCreatePlaceholderReferenceTargets(boolean theAutoCreatePlaceholderReferenceTargets) {

View File

@ -37,7 +37,11 @@ import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.HapiExtensions;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseExtension;
import org.hl7.fhir.instance.model.api.IBaseHasExtensions;
import org.hl7.fhir.instance.model.api.IBaseMetaType;
import org.hl7.fhir.instance.model.api.IBaseReference;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
@ -119,6 +123,13 @@ public class DaoResourceLinkResolver implements IResourceLinkResolver {
@SuppressWarnings("unchecked")
T newResource = (T) missingResourceDef.newInstance();
IBaseMetaType meta = newResource.getMeta();
if (meta instanceof IBaseHasExtensions) {
IBaseExtension<?, ?> extension = ((IBaseHasExtensions) meta).addExtension();
extension.setUrl(HapiExtensions.EXT_RESOURCE_META_PLACEHOLDER);
extension.setValue(myContext.getPrimitiveBoolean(true));
}
IFhirResourceDao<T> placeholderResourceDao = myDaoRegistry.getResourceDao(theType);
ourLog.debug("Automatically creating empty placeholder resource: {}", newResource.getIdElement().getValue());

View File

@ -127,6 +127,7 @@ import org.hl7.fhir.r4.model.ConceptMap;
import org.hl7.fhir.r4.model.Enumerations;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.IntegerType;
import org.hl7.fhir.r4.model.MetadataResource;
import org.hl7.fhir.r4.model.StringType;
import org.hl7.fhir.r4.model.ValueSet;
import org.hl7.fhir.r4.model.codesystems.ConceptSubsumptionOutcome;
@ -656,9 +657,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
return true;
//-- token case
if (startsWithByWordBoundaries(theDisplay, theFilterDisplay)) return true;
return false;
return startsWithByWordBoundaries(theDisplay, theFilterDisplay);
}
private boolean startsWithByWordBoundaries(String theDisplay, String theFilterDisplay) {
@ -1770,10 +1769,15 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
@Override
@Transactional
public void storeTermConceptMapAndChildren(ResourceTable theResourceTable, ConceptMap theConceptMap) {
ourLog.debug("Storing TermConceptMap for {}", theConceptMap.getIdElement().toVersionless().getValueAsString());
ValidateUtil.isTrueOrThrowInvalidRequest(theResourceTable != null, "No resource supplied");
if (isPlaceholder(theConceptMap)) {
ourLog.info("Not storing TermConceptMap for placeholder {}", theConceptMap.getIdElement().toVersionless().getValueAsString());
return;
}
ValidateUtil.isNotBlankOrThrowUnprocessableEntity(theConceptMap.getUrl(), "ConceptMap has no value for ConceptMap.url");
ourLog.info("Storing TermConceptMap for {}", theConceptMap.getIdElement().toVersionless().getValueAsString());
TermConceptMap termConceptMap = new TermConceptMap();
termConceptMap.setResource(theResourceTable);
@ -2063,10 +2067,15 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
@Override
@Transactional
public void storeTermValueSet(ResourceTable theResourceTable, ValueSet theValueSet) {
ourLog.info("Storing TermValueSet for {}", theValueSet.getIdElement().toVersionless().getValueAsString());
ValidateUtil.isTrueOrThrowInvalidRequest(theResourceTable != null, "No resource supplied");
if (isPlaceholder(theValueSet)) {
ourLog.info("Not storing TermValueSet for placeholder {}", theValueSet.getIdElement().toVersionless().getValueAsString());
return;
}
ValidateUtil.isNotBlankOrThrowUnprocessableEntity(theValueSet.getUrl(), "ValueSet has no value for ValueSet.url");
ourLog.info("Storing TermValueSet for {}", theValueSet.getIdElement().toVersionless().getValueAsString());
/*
* Get CodeSystem and validate CodeSystemVersion
@ -2113,6 +2122,9 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
}
}
private boolean isPlaceholder(MetadataResource theResource) {
return theResource.getMeta().getExtensionByUrl(HapiExtensions.EXT_RESOURCE_META_PLACEHOLDER) != null;
}
@Override
@Transactional

View File

@ -4,12 +4,11 @@ import ca.uhn.fhir.context.phonetic.ApacheEncoder;
import ca.uhn.fhir.context.phonetic.PhoneticEncoderEnum;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.util.HapiExtensions;
import org.apache.commons.codec.language.Soundex;
import org.aspectj.lang.annotation.Before;
import org.hl7.fhir.dstu3.model.Enumerations;
import org.hl7.fhir.dstu3.model.Patient;
import org.hl7.fhir.dstu3.model.SearchParameter;
@ -126,7 +125,7 @@ public class FhirResourceDaoDstu3PhoneticSearchNoFtTest extends BaseJpaDstu3Test
// searchParameter.setXpathUsage(SearchParameter.XPathUsageType.PHONETIC);
searchParameter.setStatus(Enumerations.PublicationStatus.ACTIVE);
searchParameter.addExtension()
.setUrl(JpaConstants.EXT_SEARCHPARAM_PHONETIC_ENCODER)
.setUrl(HapiExtensions.EXT_SEARCHPARAM_PHONETIC_ENCODER)
.setValue(new StringType(theEncoder.name()));
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(searchParameter));
mySearchParameterDao.create(searchParameter, mySrd).getId().toUnqualifiedVersionless();

View File

@ -5,6 +5,7 @@ import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
import ca.uhn.fhir.interceptor.api.IInterceptorService;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.dao.data.INpmPackageDao;
import ca.uhn.fhir.jpa.dao.data.INpmPackageVersionDao;
import ca.uhn.fhir.jpa.dao.data.INpmPackageVersionResourceDao;
@ -19,7 +20,6 @@ import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.ReferenceParam;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.param.UriParam;
import ca.uhn.fhir.rest.server.IRestfulServerDefaults;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.interceptor.partition.RequestTenantPartitionInterceptor;
@ -48,6 +48,7 @@ import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
@ -94,6 +95,9 @@ public class NpmR4Test extends BaseJpaR4Test {
private IInterceptorService myInterceptorService;
@Autowired
private RequestTenantPartitionInterceptor myRequestTenantPartitionInterceptor;
@Autowired
@Qualifier("myImplementationGuideDaoR4")
protected IFhirResourceDao<ImplementationGuide> myImplementationGuideDao;
@BeforeEach
public void before() throws Exception {
@ -118,6 +122,7 @@ public class NpmR4Test extends BaseJpaR4Test {
public void after() throws Exception {
JettyUtil.closeServer(myServer);
myDaoConfig.setAllowExternalReferences(new DaoConfig().isAllowExternalReferences());
myDaoConfig.setAutoCreatePlaceholderReferenceTargets(new DaoConfig().isAutoCreatePlaceholderReferenceTargets());
myPartitionSettings.setPartitioningEnabled(false);
myInterceptorService.unregisterInterceptor(myRequestTenantPartitionInterceptor);
}
@ -352,6 +357,34 @@ public class NpmR4Test extends BaseJpaR4Test {
}
}
// Reproduces https://github.com/hapifhir/hapi-fhir/issues/2332
@Test
public void testInstallR4Package_AutoCreatePlaceholder() throws Exception {
myDaoConfig.setAllowExternalReferences(true);
myDaoConfig.setAutoCreatePlaceholderReferenceTargets(true);
byte[] bytes = loadClasspathBytes("/packages/test-auto-create-placeholder.tgz");
myFakeNpmServlet.myResponses.put("/test-ig/1.0.0", bytes);
List<String> resourceList = new ArrayList<>();
resourceList.add("ImplementationGuide");
PackageInstallationSpec spec = new PackageInstallationSpec().setName("test-ig").setVersion("1.0.0").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_AND_INSTALL);
spec.setInstallResourceTypes(resourceList);
PackageInstallOutcomeJson outcome = igInstaller.install(spec);
assertEquals(1, outcome.getResourcesInstalled().get("ImplementationGuide"));
// Be sure no further communication with the server
JettyUtil.closeServer(myServer);
// Search for the installed resources
runInTransaction(() -> {
SearchParameterMap map = SearchParameterMap.newSynchronous();
IBundleProvider result = myImplementationGuideDao.search(map);
assertEquals(1, result.sizeOrThrowNpe());
});
}
@Test
public void testInstallR4Package_DraftResourcesNotInstalled() throws Exception {
myDaoConfig.setAllowExternalReferences(true);

View File

@ -21,6 +21,7 @@ package ca.uhn.fhir.jpa.model.util;
*/
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.util.HapiExtensions;
public class JpaConstants {
@ -195,9 +196,11 @@ public class JpaConstants {
public static final String EXTENSION_EXT_SYSTEMDEFINED = JpaConstants.class.getName() + "_EXTENSION_EXT_SYSTEMDEFINED";
/**
* URL for extension on a Phonetic String SearchParameter indicating that text values should be phonetically indexed with the named encoder
* Deprecated. Please use {@link HapiExtensions#EXT_SEARCHPARAM_PHONETIC_ENCODER} instead.
*/
public static final String EXT_SEARCHPARAM_PHONETIC_ENCODER = "http://hapifhir.io/fhir/StructureDefinition/searchparameter-phonetic-encoder";
@Deprecated
public static final String EXT_SEARCHPARAM_PHONETIC_ENCODER = HapiExtensions.EXT_SEARCHPARAM_PHONETIC_ENCODER;
public static final String VALUESET_FILTER_DISPLAY = "display";
/**

View File

@ -23,7 +23,6 @@ package ca.uhn.fhir.jpa.searchparam.registry;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.context.phonetic.PhoneticEncoderEnum;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.searchparam.JpaRuntimeSearchParam;
import ca.uhn.fhir.model.api.ExtensionDt;
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
@ -431,7 +430,7 @@ public class SearchParameterCanonicalizer {
String nextUrl = next.getUrl();
if (isNotBlank(nextUrl)) {
theRuntimeSearchParam.addExtension(nextUrl, next);
if (JpaConstants.EXT_SEARCHPARAM_PHONETIC_ENCODER.equals(nextUrl)) {
if (HapiExtensions.EXT_SEARCHPARAM_PHONETIC_ENCODER.equals(nextUrl)) {
setEncoder(theRuntimeSearchParam, next.getValue());
}
}