Preserve meta values in stored resources (#2481)
* Fix #1731 * Test fix * Test fixes * Avoid intermittent failure
This commit is contained in:
parent
d92a6787c5
commit
d857048207
|
@ -131,7 +131,7 @@ public abstract class BaseParser implements IParser {
|
|||
}
|
||||
|
||||
@Override
|
||||
public IParser setDontEncodeElements(Set<String> theDontEncodeElements) {
|
||||
public IParser setDontEncodeElements(Collection<String> theDontEncodeElements) {
|
||||
if (theDontEncodeElements == null || theDontEncodeElements.isEmpty()) {
|
||||
myDontEncodeElements = null;
|
||||
} else {
|
||||
|
|
|
@ -249,7 +249,7 @@ public interface IParser {
|
|||
* @param theDontEncodeElements The elements to encode
|
||||
* @see #setEncodeElements(Set)
|
||||
*/
|
||||
IParser setDontEncodeElements(Set<String> theDontEncodeElements);
|
||||
IParser setDontEncodeElements(Collection<String> theDontEncodeElements);
|
||||
|
||||
/**
|
||||
* If provided, specifies the elements which should be encoded, to the exclusion of all others. Valid values for this
|
||||
|
@ -264,7 +264,7 @@ public interface IParser {
|
|||
* </ul>
|
||||
*
|
||||
* @param theEncodeElements The elements to encode
|
||||
* @see #setDontEncodeElements(Set)
|
||||
* @see #setDontEncodeElements(Collection)
|
||||
*/
|
||||
IParser setEncodeElements(Set<String> theEncodeElements);
|
||||
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
type: fix
|
||||
issue: 1731
|
||||
title: "When storing resources in the JPA server, extensions in `Resource.meta` were not preserved, nor were
|
||||
any contents in `Bundle.entry.resource.meta`. Both of these things are now correctly persisted and
|
||||
returned. Thanks to Sean McIlvenna for reporting!"
|
|
@ -51,7 +51,6 @@ import ca.uhn.fhir.jpa.partition.IPartitionLookupSvc;
|
|||
import ca.uhn.fhir.jpa.partition.RequestPartitionHelperSvc;
|
||||
import ca.uhn.fhir.jpa.search.PersistedJpaBundleProviderFactory;
|
||||
import ca.uhn.fhir.jpa.search.cache.ISearchCacheSvc;
|
||||
import ca.uhn.fhir.jpa.searchparam.ResourceMetaParams;
|
||||
import ca.uhn.fhir.jpa.searchparam.extractor.LogicalReferenceHelper;
|
||||
import ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams;
|
||||
import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc;
|
||||
|
@ -96,6 +95,7 @@ import org.apache.commons.lang3.tuple.Pair;
|
|||
import org.hl7.fhir.instance.model.api.IAnyResource;
|
||||
import org.hl7.fhir.instance.model.api.IBase;
|
||||
import org.hl7.fhir.instance.model.api.IBaseCoding;
|
||||
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;
|
||||
|
@ -511,11 +511,64 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
|
|||
if (thePerformIndexing) {
|
||||
|
||||
encoding = myConfig.getResourceEncoding();
|
||||
Set<String> excludeElements = ResourceMetaParams.EXCLUDE_ELEMENTS_IN_ENCODED;
|
||||
|
||||
String resourceType = theEntity.getResourceType();
|
||||
|
||||
List<String> excludeElements = new ArrayList<>(8);
|
||||
excludeElements.add("id");
|
||||
|
||||
IBaseMetaType meta = theResource.getMeta();
|
||||
boolean hasExtensions = false;
|
||||
IBaseExtension<?,?> sourceExtension = null;
|
||||
if (meta instanceof IBaseHasExtensions) {
|
||||
List<? extends IBaseExtension<?, ?>> extensions = ((IBaseHasExtensions) meta).getExtension();
|
||||
if (!extensions.isEmpty()) {
|
||||
hasExtensions = true;
|
||||
|
||||
/*
|
||||
* FHIR DSTU3 did not have the Resource.meta.source field, so we use a
|
||||
* custom HAPI FHIR extension in Resource.meta to store that field. However,
|
||||
* we put the value for that field in a separate table so we don't want to serialize
|
||||
* it into the stored BLOB. Therefore: remove it from the resource temporarily
|
||||
* and restore it afterward.
|
||||
*/
|
||||
if (myFhirContext.getVersion().getVersion().equals(FhirVersionEnum.DSTU3)) {
|
||||
for (int i = 0; i < extensions.size(); i++) {
|
||||
if (extensions.get(i).getUrl().equals(HapiExtensions.EXT_META_SOURCE)) {
|
||||
sourceExtension = extensions.remove(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if (hasExtensions) {
|
||||
excludeElements.add(resourceType + ".meta.profile");
|
||||
excludeElements.add(resourceType + ".meta.tag");
|
||||
excludeElements.add(resourceType + ".meta.security");
|
||||
excludeElements.add(resourceType + ".meta.versionId");
|
||||
excludeElements.add(resourceType + ".meta.lastUpdated");
|
||||
excludeElements.add(resourceType + ".meta.source");
|
||||
} else {
|
||||
/*
|
||||
* If there are no extensions in the meta element, we can just exclude the
|
||||
* whole meta element, which avoids adding an empty "meta":{}
|
||||
* from showing up in the serialized JSON.
|
||||
*/
|
||||
excludeElements.add(resourceType + ".meta");
|
||||
}
|
||||
|
||||
theEntity.setFhirVersion(myContext.getVersion().getVersion());
|
||||
|
||||
bytes = encodeResource(theResource, encoding, excludeElements, myContext);
|
||||
|
||||
if (sourceExtension != null) {
|
||||
IBaseExtension<?, ?> newSourceExtension = ((IBaseHasExtensions) meta).addExtension();
|
||||
newSourceExtension.setUrl(sourceExtension.getUrl());
|
||||
newSourceExtension.setValue(sourceExtension.getValue());
|
||||
}
|
||||
|
||||
HashFunction sha256 = Hashing.sha256();
|
||||
String hashSha256 = sha256.hashBytes(bytes).toString();
|
||||
if (hashSha256.equals(theEntity.getHashSha256()) == false) {
|
||||
|
@ -1530,7 +1583,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
|
|||
return resourceText;
|
||||
}
|
||||
|
||||
public static byte[] encodeResource(IBaseResource theResource, ResourceEncodingEnum theEncoding, Set<String> theExcludeElements, FhirContext theContext) {
|
||||
public static byte[] encodeResource(IBaseResource theResource, ResourceEncodingEnum theEncoding, List<String> theExcludeElements, FhirContext theContext) {
|
||||
byte[] bytes;
|
||||
IParser parser = theEncoding.newParser(theContext);
|
||||
parser.setDontEncodeElements(theExcludeElements);
|
||||
|
|
|
@ -0,0 +1,126 @@
|
|||
package ca.uhn.fhir.jpa.dao.r4;
|
||||
|
||||
import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.model.entity.NormalizedQuantitySearchLevel;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantityNormalized;
|
||||
import ca.uhn.fhir.jpa.model.util.UcumServiceUtil;
|
||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||
import ca.uhn.fhir.rest.param.QuantityParam;
|
||||
import ca.uhn.fhir.rest.param.StringParam;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||
import org.apache.commons.lang3.time.DateUtils;
|
||||
import org.hl7.fhir.instance.model.api.IBaseMetaType;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.hl7.fhir.r4.model.Bundle;
|
||||
import org.hl7.fhir.r4.model.DateType;
|
||||
import org.hl7.fhir.r4.model.DecimalType;
|
||||
import org.hl7.fhir.r4.model.Enumerations;
|
||||
import org.hl7.fhir.r4.model.IdType;
|
||||
import org.hl7.fhir.r4.model.InstantType;
|
||||
import org.hl7.fhir.r4.model.Meta;
|
||||
import org.hl7.fhir.r4.model.Observation;
|
||||
import org.hl7.fhir.r4.model.Organization;
|
||||
import org.hl7.fhir.r4.model.Patient;
|
||||
import org.hl7.fhir.r4.model.Quantity;
|
||||
import org.hl7.fhir.r4.model.SampledData;
|
||||
import org.hl7.fhir.r4.model.SearchParameter;
|
||||
import org.hl7.fhir.r4.model.StringType;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.empty;
|
||||
import static org.hamcrest.Matchers.matchesPattern;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
public class FhirResourceDaoR4MetaTest extends BaseJpaR4Test {
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(FhirResourceDaoR4MetaTest.class);
|
||||
|
||||
/**
|
||||
* See #1731
|
||||
*/
|
||||
@Test
|
||||
public void testMetaExtensionsPreserved() {
|
||||
Patient patient = new Patient();
|
||||
patient.setActive(true);
|
||||
patient.getMeta().addExtension("http://foo", new StringType("hello"));
|
||||
IIdType id = myPatientDao.create(patient).getId();
|
||||
|
||||
patient = myPatientDao.read(id);
|
||||
assertTrue(patient.getActive());
|
||||
assertEquals(1, patient.getMeta().getExtensionsByUrl("http://foo").size());
|
||||
assertEquals("hello", patient.getMeta().getExtensionByUrl("http://foo").getValueAsPrimitive().getValueAsString());
|
||||
}
|
||||
|
||||
/**
|
||||
* See #1731
|
||||
*/
|
||||
@Test
|
||||
public void testBundleInnerResourceMetaIsPreserved() {
|
||||
Patient patient = new Patient();
|
||||
patient.setActive(true);
|
||||
patient.getMeta().setLastUpdatedElement(new InstantType("2011-01-01T12:12:12Z"));
|
||||
patient.getMeta().setVersionId("22");
|
||||
patient.getMeta().addProfile("http://foo");
|
||||
patient.getMeta().addTag("http://tag", "value", "the tag");
|
||||
patient.getMeta().addSecurity("http://tag", "security", "the tag");
|
||||
patient.getMeta().addExtension("http://foo", new StringType("hello"));
|
||||
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.setType(Bundle.BundleType.COLLECTION);
|
||||
bundle.addEntry().setResource(patient);
|
||||
IIdType id = myBundleDao.create(bundle).getId();
|
||||
|
||||
bundle = myBundleDao.read(id);
|
||||
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(bundle));
|
||||
patient = (Patient) bundle.getEntryFirstRep().getResource();
|
||||
assertTrue(patient.getActive());
|
||||
assertEquals(1, patient.getMeta().getExtensionsByUrl("http://foo").size());
|
||||
assertEquals("22", patient.getMeta().getVersionId());
|
||||
assertEquals("http://foo", patient.getMeta().getProfile().get(0).getValue());
|
||||
assertEquals("hello", patient.getMeta().getExtensionByUrl("http://foo").getValueAsPrimitive().getValueAsString());
|
||||
}
|
||||
|
||||
/**
|
||||
* See #1731
|
||||
*/
|
||||
@Test
|
||||
public void testMetaValuesNotStoredAfterDeletion() {
|
||||
Patient patient = new Patient();
|
||||
patient.setActive(true);
|
||||
patient.getMeta().addProfile("http://foo");
|
||||
patient.getMeta().addTag("http://tag", "value", "the tag");
|
||||
patient.getMeta().addSecurity("http://tag", "security", "the tag");
|
||||
IIdType id = myPatientDao.create(patient).getId();
|
||||
|
||||
Meta meta = new Meta();
|
||||
meta.addProfile("http://foo");
|
||||
meta.addTag("http://tag", "value", "the tag");
|
||||
meta.addSecurity("http://tag", "security", "the tag");
|
||||
myPatientDao.metaDeleteOperation(id, meta, mySrd);
|
||||
|
||||
patient = myPatientDao.read(id);
|
||||
assertThat(patient.getMeta().getProfile(), empty());
|
||||
assertThat(patient.getMeta().getTag(), empty());
|
||||
assertThat(patient.getMeta().getSecurity(), empty());
|
||||
}
|
||||
|
||||
}
|
|
@ -64,7 +64,9 @@ import java.util.List;
|
|||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.leftPad;
|
||||
import static org.awaitility.Awaitility.await;
|
||||
import static org.hamcrest.CoreMatchers.containsString;
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.not;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.blankOrNullString;
|
||||
|
@ -622,6 +624,12 @@ public class ConsentInterceptorResourceProviderR4Test extends BaseResourceProvid
|
|||
// The paging should have ended now - but the last redacted female result is an empty existing page which should never have been there.
|
||||
assertNotNull(BundleUtil.getLinkUrlOfType(myFhirCtx, response, "next"));
|
||||
|
||||
await()
|
||||
.until(
|
||||
()->mySearchEntityDao.findByUuidAndFetchIncludes(searchId).orElseThrow(() -> new IllegalStateException()).getStatus(),
|
||||
equalTo(SearchStatusEnum.FINISHED)
|
||||
);
|
||||
|
||||
runInTransaction(() -> {
|
||||
Search search = mySearchEntityDao.findByUuidAndFetchIncludes(searchId).orElseThrow(() -> new IllegalStateException());
|
||||
assertEquals(3, search.getNumFound());
|
||||
|
|
|
@ -35,9 +35,7 @@ import org.hl7.fhir.instance.model.api.IAnyResource;
|
|||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class ResourceMetaParams {
|
||||
/**
|
||||
|
@ -48,7 +46,6 @@ public class ResourceMetaParams {
|
|||
* These are parameters which are supported by searches
|
||||
*/
|
||||
public static final Map<String, Class<? extends IQueryParameterType>> RESOURCE_META_PARAMS;
|
||||
public static final Set<String> EXCLUDE_ELEMENTS_IN_ENCODED;
|
||||
|
||||
static {
|
||||
Map<String, Class<? extends IQueryParameterType>> resourceMetaParams = new HashMap<>();
|
||||
|
@ -67,10 +64,5 @@ public class ResourceMetaParams {
|
|||
resourceMetaAndParams.put(Constants.PARAM_HAS, HasAndListParam.class);
|
||||
RESOURCE_META_PARAMS = Collections.unmodifiableMap(resourceMetaParams);
|
||||
RESOURCE_META_AND_PARAMS = Collections.unmodifiableMap(resourceMetaAndParams);
|
||||
|
||||
HashSet<String> excludeElementsInEncoded = new HashSet<>();
|
||||
excludeElementsInEncoded.add("id");
|
||||
excludeElementsInEncoded.add("*.meta");
|
||||
EXCLUDE_ELEMENTS_IN_ENCODED = Collections.unmodifiableSet(excludeElementsInEncoded);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue