Enable Auto-reference versions in JPA (#2323)
* Automatic reference versions * Auto reference versions * resolve fixmes * Add changelog * Test fixes * Test fixes * Resolve fixmes * Work on versioning * Add support for _includes * Update headers * Change visibility * One more tweak * One more API tweak * Fixes * Account for review comments * Fix errorprone reported issue * Test fix * Test fixes * Test fixes
This commit is contained in:
parent
170d633181
commit
909d9f742c
|
@ -1,5 +1,25 @@
|
||||||
package ca.uhn.fhir.interceptor.api;
|
package ca.uhn.fhir.interceptor.api;
|
||||||
|
|
||||||
|
/*-
|
||||||
|
* #%L
|
||||||
|
* HAPI FHIR - Core Library
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2014 - 2021 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 javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
|
|
@ -173,7 +173,7 @@ public class BundleBuilder {
|
||||||
*
|
*
|
||||||
* @param theResource The resource to create
|
* @param theResource The resource to create
|
||||||
*/
|
*/
|
||||||
public CreateBuilder addCreateEntry(IBaseResource theResource) {
|
public CreateBuilder addTransactionCreateEntry(IBaseResource theResource) {
|
||||||
setBundleField("type", "transaction");
|
setBundleField("type", "transaction");
|
||||||
|
|
||||||
IBase request = addEntryAndReturnRequest(theResource);
|
IBase request = addEntryAndReturnRequest(theResource);
|
||||||
|
@ -338,7 +338,6 @@ public class BundleBuilder {
|
||||||
setBundleField("type", theType);
|
setBundleField("type", theType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static class UpdateBuilder {
|
public static class UpdateBuilder {
|
||||||
|
|
||||||
private final IPrimitiveType<?> myUrl;
|
private final IPrimitiveType<?> myUrl;
|
||||||
|
|
|
@ -108,6 +108,8 @@ ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.successfulDeletes=Successfully delet
|
||||||
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.invalidSearchParameter=Unknown search parameter "{0}" for resource type "{1}". Valid search parameters for this search are: {2}
|
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.invalidSearchParameter=Unknown search parameter "{0}" for resource type "{1}". Valid search parameters for this search are: {2}
|
||||||
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.invalidSortParameter=Unknown _sort parameter value "{0}" for resource type "{1}" (Note: sort parameters values must use a valid Search Parameter). Valid values for this search are: {2}
|
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.invalidSortParameter=Unknown _sort parameter value "{0}" for resource type "{1}" (Note: sort parameters values must use a valid Search Parameter). Valid values for this search are: {2}
|
||||||
|
|
||||||
|
ca.uhn.fhir.jpa.dao.BaseStorageDao.invalidBundleTypeForStorage=Unable to store a Bundle resource on this server with a Bundle.type value of: {0}. Note that if you are trying to perform a FHIR 'transaction' or 'batch' operation you should POST the Bundle resource to the Base URL of the server, not to the '/Bundle' endpoint.
|
||||||
|
|
||||||
ca.uhn.fhir.rest.api.PatchTypeEnum.missingPatchContentType=Missing or invalid content type for PATCH operation
|
ca.uhn.fhir.rest.api.PatchTypeEnum.missingPatchContentType=Missing or invalid content type for PATCH operation
|
||||||
ca.uhn.fhir.rest.api.PatchTypeEnum.invalidPatchContentType=Invalid Content-Type for PATCH operation: {0}
|
ca.uhn.fhir.rest.api.PatchTypeEnum.invalidPatchContentType=Invalid Content-Type for PATCH operation: {0}
|
||||||
ca.uhn.fhir.jpa.dao.BaseTransactionProcessor.unsupportedResourceType=Resource {0} is not supported on this server. Supported resource types: {1}
|
ca.uhn.fhir.jpa.dao.BaseTransactionProcessor.unsupportedResourceType=Resource {0} is not supported on this server. Supported resource types: {1}
|
||||||
|
|
|
@ -84,7 +84,7 @@ public class BundleBuilderExamples {
|
||||||
patient.setActive(true);
|
patient.setActive(true);
|
||||||
|
|
||||||
// Add the patient as a create (aka POST) to the Bundle
|
// Add the patient as a create (aka POST) to the Bundle
|
||||||
builder.addCreateEntry(patient);
|
builder.addTransactionCreateEntry(patient);
|
||||||
|
|
||||||
// Execute the transaction
|
// Execute the transaction
|
||||||
IBaseBundle outcome = myFhirClient.transaction().withBundle(builder.getBundle()).execute();
|
IBaseBundle outcome = myFhirClient.transaction().withBundle(builder.getBundle()).execute();
|
||||||
|
@ -102,7 +102,7 @@ public class BundleBuilderExamples {
|
||||||
patient.addIdentifier().setSystem("http://foo").setValue("bar");
|
patient.addIdentifier().setSystem("http://foo").setValue("bar");
|
||||||
|
|
||||||
// Add the patient as a create (aka POST) to the Bundle
|
// Add the patient as a create (aka POST) to the Bundle
|
||||||
builder.addCreateEntry(patient).conditional("Patient?identifier=http://foo|bar");
|
builder.addTransactionCreateEntry(patient).conditional("Patient?identifier=http://foo|bar");
|
||||||
|
|
||||||
// Execute the transaction
|
// Execute the transaction
|
||||||
IBaseBundle outcome = myFhirClient.transaction().withBundle(builder.getBundle()).execute();
|
IBaseBundle outcome = myFhirClient.transaction().withBundle(builder.getBundle()).execute();
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
---
|
||||||
|
type: add
|
||||||
|
issue: 2323
|
||||||
|
title: "The JPA server has a new setting on the `ModelConfig` bean called \"AutoVersionReferencesAtPaths\". Using
|
||||||
|
this setting, the server can be configured to add the current target resource version ID to any resource
|
||||||
|
references found in a resource being stored. In addition, a new setting has been added to the JPA ModelConfig
|
||||||
|
bean that allows `_include` statements to respect versioned references, and actually include the correct
|
||||||
|
version for the reference."
|
|
@ -265,9 +265,11 @@ public interface IFhirResourceDao<T extends IBaseResource> extends IDao {
|
||||||
*/
|
*/
|
||||||
DeleteMethodOutcome deletePidList(String theUrl, Collection<ResourcePersistentId> theResourceIds, DeleteConflictList theDeleteConflicts, RequestDetails theRequest);
|
DeleteMethodOutcome deletePidList(String theUrl, Collection<ResourcePersistentId> theResourceIds, DeleteConflictList theDeleteConflicts, RequestDetails theRequest);
|
||||||
|
|
||||||
// /**
|
/**
|
||||||
// * Invoke the everything operation
|
* Returns the current version ID for the given resource
|
||||||
// */
|
*/
|
||||||
// IBundleProvider everything(IIdType theId);
|
default String getCurrentVersionId(IIdType theReferenceElement) {
|
||||||
|
return read(theReferenceElement.toVersionless()).getIdElement().getVersionIdPart();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,8 +47,9 @@ public class DaoMethodOutcome extends MethodOutcome {
|
||||||
/**
|
/**
|
||||||
* Was this a NO-OP - Typically because of an update to a resource that already matched the contents provided
|
* Was this a NO-OP - Typically because of an update to a resource that already matched the contents provided
|
||||||
*/
|
*/
|
||||||
public void setNop(boolean theNop) {
|
public DaoMethodOutcome setNop(boolean theNop) {
|
||||||
myNop = theNop;
|
myNop = theNop;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IBasePersistedResource getEntity() {
|
public IBasePersistedResource getEntity() {
|
||||||
|
|
|
@ -259,6 +259,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
StopWatch w = new StopWatch();
|
StopWatch w = new StopWatch();
|
||||||
|
|
||||||
preProcessResourceForStorage(theResource);
|
preProcessResourceForStorage(theResource);
|
||||||
|
preProcessResourceForStorage(theResource, theRequest, theTransactionDetails, thePerformIndexing);
|
||||||
|
|
||||||
ResourceTable entity = new ResourceTable();
|
ResourceTable entity = new ResourceTable();
|
||||||
entity.setResourceType(toResourceName(theResource));
|
entity.setResourceType(toResourceName(theResource));
|
||||||
|
@ -274,7 +275,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
entity = myEntityManager.find(ResourceTable.class, pid.getId());
|
entity = myEntityManager.find(ResourceTable.class, pid.getId());
|
||||||
IBaseResource resource = toResource(entity, false);
|
IBaseResource resource = toResource(entity, false);
|
||||||
theResource.setId(resource.getIdElement().getValue());
|
theResource.setId(resource.getIdElement().getValue());
|
||||||
return toMethodOutcome(theRequest, entity, resource).setCreated(false);
|
return toMethodOutcome(theRequest, entity, resource).setCreated(false).setNop(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1129,6 +1130,12 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
return readEntity(theId, true, theRequest);
|
return readEntity(theId, true, theRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public String getCurrentVersionId(IIdType theReferenceElement) {
|
||||||
|
return Long.toString(readEntity(theReferenceElement.toVersionless(), null).getVersion());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public BaseHasResource readEntity(IIdType theId, boolean theCheckForForcedId, RequestDetails theRequest) {
|
public BaseHasResource readEntity(IIdType theId, boolean theCheckForForcedId, RequestDetails theRequest) {
|
||||||
|
@ -1459,6 +1466,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
T resource = theResource;
|
T resource = theResource;
|
||||||
|
|
||||||
preProcessResourceForStorage(resource);
|
preProcessResourceForStorage(resource);
|
||||||
|
preProcessResourceForStorage(theResource, theRequest, theTransactionDetails, thePerformIndexing);
|
||||||
|
|
||||||
final ResourceTable entity;
|
final ResourceTable entity;
|
||||||
|
|
||||||
|
@ -1529,6 +1537,9 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
resource.setId(entity.getIdDt().getValue());
|
resource.setId(entity.getIdDt().getValue());
|
||||||
DaoMethodOutcome outcome = toMethodOutcome(theRequest, entity, resource).setCreated(wasDeleted);
|
DaoMethodOutcome outcome = toMethodOutcome(theRequest, entity, resource).setCreated(wasDeleted);
|
||||||
outcome.setPreviousResource(oldResource);
|
outcome.setPreviousResource(oldResource);
|
||||||
|
if (!outcome.isNop()) {
|
||||||
|
outcome.setId(outcome.getId().withVersion(Long.toString(outcome.getId().getVersionIdPartAsLong() + 1)));
|
||||||
|
}
|
||||||
return outcome;
|
return outcome;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,8 +27,11 @@ import ca.uhn.fhir.interceptor.api.HookParams;
|
||||||
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
|
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
|
||||||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||||
import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
||||||
|
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
||||||
|
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
|
||||||
import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome;
|
import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome;
|
||||||
import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
|
import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
|
||||||
|
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||||
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
|
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
|
||||||
|
@ -53,11 +56,10 @@ import ca.uhn.fhir.util.OperationOutcomeUtil;
|
||||||
import ca.uhn.fhir.util.ResourceReferenceInfo;
|
import ca.uhn.fhir.util.ResourceReferenceInfo;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseBundle;
|
import org.hl7.fhir.instance.model.api.IBaseBundle;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
|
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
|
||||||
|
import org.hl7.fhir.instance.model.api.IBaseReference;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
import org.hl7.fhir.r4.model.InstantType;
|
import org.hl7.fhir.r4.model.InstantType;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.transaction.annotation.Propagation;
|
import org.springframework.transaction.annotation.Propagation;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
@ -67,6 +69,7 @@ import javax.annotation.Nullable;
|
||||||
import javax.validation.constraints.NotNull;
|
import javax.validation.constraints.NotNull;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.IdentityHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -77,30 +80,85 @@ import static org.apache.commons.lang3.StringUtils.defaultString;
|
||||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||||
|
|
||||||
public abstract class BaseStorageDao {
|
public abstract class BaseStorageDao {
|
||||||
private static final Logger ourLog = LoggerFactory.getLogger(BaseStorageDao.class);
|
|
||||||
@Autowired
|
@Autowired
|
||||||
protected ISearchParamRegistry mySearchParamRegistry;
|
protected ISearchParamRegistry mySearchParamRegistry;
|
||||||
|
@Autowired
|
||||||
|
protected FhirContext myFhirContext;
|
||||||
|
@Autowired
|
||||||
|
protected DaoRegistry myDaoRegistry;
|
||||||
|
@Autowired
|
||||||
|
protected ModelConfig myModelConfig;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* May be overridden by subclasses to validate resources prior to storage
|
* May be overridden by subclasses to validate resources prior to storage
|
||||||
*
|
*
|
||||||
* @param theResource The resource that is about to be stored
|
* @param theResource The resource that is about to be stored
|
||||||
|
* @deprecated Use {@link #preProcessResourceForStorage(IBaseResource, RequestDetails, TransactionDetails, boolean)} instead
|
||||||
*/
|
*/
|
||||||
protected void preProcessResourceForStorage(IBaseResource theResource) {
|
protected void preProcessResourceForStorage(IBaseResource theResource) {
|
||||||
|
// nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* May be overridden by subclasses to validate resources prior to storage
|
||||||
|
*
|
||||||
|
* @param theResource The resource that is about to be stored
|
||||||
|
* @since 5.3.0
|
||||||
|
*/
|
||||||
|
protected void preProcessResourceForStorage(IBaseResource theResource, RequestDetails theRequestDetails, TransactionDetails theTransactionDetails, boolean thePerformIndexing) {
|
||||||
|
|
||||||
|
verifyResourceTypeIsAppropriateForDao(theResource);
|
||||||
|
|
||||||
|
verifyResourceIdIsValid(theResource);
|
||||||
|
|
||||||
|
verifyBundleTypeIsAppropriateForStorage(theResource);
|
||||||
|
|
||||||
|
replaceAbsoluteReferencesWithRelative(theResource);
|
||||||
|
|
||||||
|
performAutoVersioning(theResource, thePerformIndexing);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sanity check - Is this resource the right type for this DAO?
|
||||||
|
*/
|
||||||
|
private void verifyResourceTypeIsAppropriateForDao(IBaseResource theResource) {
|
||||||
String type = getContext().getResourceType(theResource);
|
String type = getContext().getResourceType(theResource);
|
||||||
if (getResourceName() != null && !getResourceName().equals(type)) {
|
if (getResourceName() != null && !getResourceName().equals(type)) {
|
||||||
throw new InvalidRequestException(getContext().getLocalizer().getMessageSanitized(BaseHapiFhirResourceDao.class, "incorrectResourceType", type, getResourceName()));
|
throw new InvalidRequestException(getContext().getLocalizer().getMessageSanitized(BaseHapiFhirResourceDao.class, "incorrectResourceType", type, getResourceName()));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify that the resource ID is actually valid according to FHIR's rules
|
||||||
|
*/
|
||||||
|
private void verifyResourceIdIsValid(IBaseResource theResource) {
|
||||||
if (theResource.getIdElement().hasIdPart()) {
|
if (theResource.getIdElement().hasIdPart()) {
|
||||||
if (!theResource.getIdElement().isIdPartValid()) {
|
if (!theResource.getIdElement().isIdPartValid()) {
|
||||||
throw new InvalidRequestException(getContext().getLocalizer().getMessageSanitized(BaseHapiFhirResourceDao.class, "failedToCreateWithInvalidId", theResource.getIdElement().getIdPart()));
|
throw new InvalidRequestException(getContext().getLocalizer().getMessageSanitized(BaseHapiFhirResourceDao.class, "failedToCreateWithInvalidId", theResource.getIdElement().getIdPart()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
|
* Verify that we're not storing a Bundle with a disallowed bundle type
|
||||||
|
*/
|
||||||
|
private void verifyBundleTypeIsAppropriateForStorage(IBaseResource theResource) {
|
||||||
|
if (theResource instanceof IBaseBundle) {
|
||||||
|
Set<String> allowedBundleTypes = getConfig().getBundleTypesAllowedForStorage();
|
||||||
|
String bundleType = BundleUtil.getBundleType(getContext(), (IBaseBundle) theResource);
|
||||||
|
bundleType = defaultString(bundleType);
|
||||||
|
if (!allowedBundleTypes.contains(bundleType)) {
|
||||||
|
String message = myFhirContext.getLocalizer().getMessage(BaseStorageDao.class, "invalidBundleTypeForStorage", (isNotBlank(bundleType) ? bundleType : "(missing)"));
|
||||||
|
throw new UnprocessableEntityException(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
* Replace absolute references with relative ones if configured to do so
|
* Replace absolute references with relative ones if configured to do so
|
||||||
*/
|
*/
|
||||||
|
private void replaceAbsoluteReferencesWithRelative(IBaseResource theResource) {
|
||||||
if (getConfig().getTreatBaseUrlsAsLocal().isEmpty() == false) {
|
if (getConfig().getTreatBaseUrlsAsLocal().isEmpty() == false) {
|
||||||
FhirTerser t = getContext().newTerser();
|
FhirTerser t = getContext().newTerser();
|
||||||
List<ResourceReferenceInfo> refs = t.getAllResourceReferences(theResource);
|
List<ResourceReferenceInfo> refs = t.getAllResourceReferences(theResource);
|
||||||
|
@ -114,17 +172,38 @@ public abstract class BaseStorageDao {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("Bundle".equals(type)) {
|
|
||||||
Set<String> allowedBundleTypes = getConfig().getBundleTypesAllowedForStorage();
|
|
||||||
String bundleType = BundleUtil.getBundleType(getContext(), (IBaseBundle) theResource);
|
|
||||||
bundleType = defaultString(bundleType);
|
|
||||||
if (!allowedBundleTypes.contains(bundleType)) {
|
|
||||||
String message = "Unable to store a Bundle resource on this server with a Bundle.type value of: " + (isNotBlank(bundleType) ? bundleType : "(missing)");
|
|
||||||
throw new UnprocessableEntityException(message);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle {@link ModelConfig#getAutoVersionReferenceAtPaths() auto-populate-versions}
|
||||||
|
*
|
||||||
|
* We only do this if thePerformIndexing is true because if it's false, that means
|
||||||
|
* we're in a FHIR transaction during the first phase of write operation processing,
|
||||||
|
* meaning that the versions of other resources may not have need updated yet. For example
|
||||||
|
* we're about to store an Observation with a reference to a Patient, and that Patient
|
||||||
|
* is also being updated in the same transaction, during the first "no index" phase,
|
||||||
|
* the Patient will not yet have its version number incremented, so it would be wrong
|
||||||
|
* to use that value. During the second phase it is correct.
|
||||||
|
*
|
||||||
|
* Also note that {@link BaseTransactionProcessor} also has code to do auto-versioning
|
||||||
|
* and it is the one that takes care of the placeholder IDs. Look for the other caller of
|
||||||
|
* {@link #extractReferencesToAutoVersion(FhirContext, ModelConfig, IBaseResource)}
|
||||||
|
* to find this.
|
||||||
|
*/
|
||||||
|
private void performAutoVersioning(IBaseResource theResource, boolean thePerformIndexing) {
|
||||||
|
if (thePerformIndexing) {
|
||||||
|
Set<IBaseReference> referencesToVersion = extractReferencesToAutoVersion(myFhirContext, myModelConfig, theResource);
|
||||||
|
for (IBaseReference nextReference : referencesToVersion) {
|
||||||
|
IIdType referenceElement = nextReference.getReferenceElement();
|
||||||
|
if (!referenceElement.hasBaseUrl()) {
|
||||||
|
String resourceType = referenceElement.getResourceType();
|
||||||
|
IFhirResourceDao<?> dao = myDaoRegistry.getResourceDao(resourceType);
|
||||||
|
String targetVersionId = dao.getCurrentVersionId(referenceElement);
|
||||||
|
String newTargetReference = referenceElement.withVersion(targetVersionId).getValue();
|
||||||
|
nextReference.setReference(newTargetReference);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected DaoMethodOutcome toMethodOutcome(RequestDetails theRequest, @Nonnull final IBasePersistedResource theEntity, @Nonnull IBaseResource theResource) {
|
protected DaoMethodOutcome toMethodOutcome(RequestDetails theRequest, @Nonnull final IBasePersistedResource theEntity, @Nonnull IBaseResource theResource) {
|
||||||
|
@ -267,4 +346,28 @@ public abstract class BaseStorageDao {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see ModelConfig#getAutoVersionReferenceAtPaths()
|
||||||
|
*/
|
||||||
|
@Nonnull
|
||||||
|
public static Set<IBaseReference> extractReferencesToAutoVersion(FhirContext theFhirContext, ModelConfig theModelConfig, IBaseResource theResource) {
|
||||||
|
Map<IBaseReference, Object> references = Collections.emptyMap();
|
||||||
|
if (!theModelConfig.getAutoVersionReferenceAtPaths().isEmpty()) {
|
||||||
|
String resourceName = theFhirContext.getResourceType(theResource);
|
||||||
|
for (String nextPath : theModelConfig.getAutoVersionReferenceAtPathsByResourceType(resourceName)) {
|
||||||
|
List<IBaseReference> nextReferences = theFhirContext.newTerser().getValues(theResource, nextPath, IBaseReference.class);
|
||||||
|
for (IBaseReference next : nextReferences) {
|
||||||
|
if (next.getReferenceElement().hasVersionIdPart()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (references.isEmpty()) {
|
||||||
|
references = new IdentityHashMap<>();
|
||||||
|
}
|
||||||
|
references.put(next, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return references.keySet();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,7 @@ import ca.uhn.fhir.jpa.api.model.DeleteMethodOutcome;
|
||||||
import ca.uhn.fhir.jpa.dao.tx.HapiTransactionService;
|
import ca.uhn.fhir.jpa.dao.tx.HapiTransactionService;
|
||||||
import ca.uhn.fhir.jpa.delete.DeleteConflictService;
|
import ca.uhn.fhir.jpa.delete.DeleteConflictService;
|
||||||
import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
|
import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
|
||||||
|
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||||
import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage;
|
import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage;
|
||||||
import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster;
|
import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster;
|
||||||
|
@ -78,6 +79,7 @@ import org.hl7.fhir.instance.model.api.IBaseBinary;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseBundle;
|
import org.hl7.fhir.instance.model.api.IBaseBundle;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
|
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseParameters;
|
import org.hl7.fhir.instance.model.api.IBaseParameters;
|
||||||
|
import org.hl7.fhir.instance.model.api.IBaseReference;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||||
|
@ -130,10 +132,12 @@ public abstract class BaseTransactionProcessor {
|
||||||
private HapiTransactionService myHapiTransactionService;
|
private HapiTransactionService myHapiTransactionService;
|
||||||
@Autowired
|
@Autowired
|
||||||
private DaoConfig myDaoConfig;
|
private DaoConfig myDaoConfig;
|
||||||
|
@Autowired
|
||||||
|
private ModelConfig myModelConfig;
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void start() {
|
public void start() {
|
||||||
|
ourLog.trace("Starting transaction processor");
|
||||||
}
|
}
|
||||||
|
|
||||||
public <BUNDLE extends IBaseBundle> BUNDLE transaction(RequestDetails theRequestDetails, BUNDLE theRequest) {
|
public <BUNDLE extends IBaseBundle> BUNDLE transaction(RequestDetails theRequestDetails, BUNDLE theRequest) {
|
||||||
|
@ -195,7 +199,7 @@ public abstract class BaseTransactionProcessor {
|
||||||
|
|
||||||
private void handleTransactionCreateOrUpdateOutcome(Map<IIdType, IIdType> idSubstitutions, Map<IIdType, DaoMethodOutcome> idToPersistedOutcome, IIdType nextResourceId, DaoMethodOutcome outcome,
|
private void handleTransactionCreateOrUpdateOutcome(Map<IIdType, IIdType> idSubstitutions, Map<IIdType, DaoMethodOutcome> idToPersistedOutcome, IIdType nextResourceId, DaoMethodOutcome outcome,
|
||||||
IBase newEntry, String theResourceType, IBaseResource theRes, ServletRequestDetails theRequestDetails) {
|
IBase newEntry, String theResourceType, IBaseResource theRes, ServletRequestDetails theRequestDetails) {
|
||||||
IIdType newId = outcome.getId().toUnqualifiedVersionless();
|
IIdType newId = outcome.getId().toUnqualified();
|
||||||
IIdType resourceId = isPlaceholder(nextResourceId) ? nextResourceId : nextResourceId.toUnqualifiedVersionless();
|
IIdType resourceId = isPlaceholder(nextResourceId) ? nextResourceId : nextResourceId.toUnqualifiedVersionless();
|
||||||
if (newId.equals(resourceId) == false) {
|
if (newId.equals(resourceId) == false) {
|
||||||
idSubstitutions.put(resourceId, newId);
|
idSubstitutions.put(resourceId, newId);
|
||||||
|
@ -900,20 +904,32 @@ public abstract class BaseTransactionProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
// References
|
// References
|
||||||
|
Set<IBaseReference> referencesToVersion = BaseStorageDao.extractReferencesToAutoVersion(myContext, myModelConfig, nextResource);
|
||||||
List<ResourceReferenceInfo> allRefs = terser.getAllResourceReferences(nextResource);
|
List<ResourceReferenceInfo> allRefs = terser.getAllResourceReferences(nextResource);
|
||||||
for (ResourceReferenceInfo nextRef : allRefs) {
|
for (ResourceReferenceInfo nextRef : allRefs) {
|
||||||
IIdType nextId = nextRef.getResourceReference().getReferenceElement();
|
IBaseReference resourceReference = nextRef.getResourceReference();
|
||||||
|
IIdType nextId = resourceReference.getReferenceElement();
|
||||||
if (!nextId.hasIdPart()) {
|
if (!nextId.hasIdPart()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (theIdSubstitutions.containsKey(nextId)) {
|
if (theIdSubstitutions.containsKey(nextId)) {
|
||||||
IIdType newId = theIdSubstitutions.get(nextId);
|
IIdType newId = theIdSubstitutions.get(nextId);
|
||||||
ourLog.debug(" * Replacing resource ref {} with {}", nextId, newId);
|
ourLog.debug(" * Replacing resource ref {} with {}", nextId, newId);
|
||||||
nextRef.getResourceReference().setReference(newId.getValue());
|
if (referencesToVersion.contains(resourceReference)) {
|
||||||
|
DaoMethodOutcome outcome = theIdToPersistedOutcome.get(newId);
|
||||||
|
resourceReference.setReference(newId.getValue());
|
||||||
|
} else {
|
||||||
|
resourceReference.setReference(newId.toVersionless().getValue());
|
||||||
|
}
|
||||||
} else if (nextId.getValue().startsWith("urn:")) {
|
} else if (nextId.getValue().startsWith("urn:")) {
|
||||||
throw new InvalidRequestException("Unable to satisfy placeholder ID " + nextId.getValue() + " found in element named '" + nextRef.getName() + "' within resource of type: " + nextResource.getIdElement().getResourceType());
|
throw new InvalidRequestException("Unable to satisfy placeholder ID " + nextId.getValue() + " found in element named '" + nextRef.getName() + "' within resource of type: " + nextResource.getIdElement().getResourceType());
|
||||||
} else {
|
} else {
|
||||||
ourLog.debug(" * Reference [{}] does not exist in bundle", nextId);
|
if (referencesToVersion.contains(resourceReference)) {
|
||||||
|
DaoMethodOutcome outcome = theIdToPersistedOutcome.get(nextId);
|
||||||
|
if (!outcome.isNop() && !Boolean.TRUE.equals(outcome.getCreated())) {
|
||||||
|
resourceReference.setReference(nextId.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -928,7 +944,7 @@ public abstract class BaseTransactionProcessor {
|
||||||
if (theIdSubstitutions.containsKey(nextUriString)) {
|
if (theIdSubstitutions.containsKey(nextUriString)) {
|
||||||
IIdType newId = theIdSubstitutions.get(nextUriString);
|
IIdType newId = theIdSubstitutions.get(nextUriString);
|
||||||
ourLog.debug(" * Replacing resource ref {} with {}", nextUriString, newId);
|
ourLog.debug(" * Replacing resource ref {} with {}", nextUriString, newId);
|
||||||
nextRef.setValueAsString(newId.getValue());
|
nextRef.setValueAsString(newId.toVersionless().getValue());
|
||||||
} else {
|
} else {
|
||||||
ourLog.debug(" * Reference [{}] does not exist in bundle", nextUriString);
|
ourLog.debug(" * Reference [{}] does not exist in bundle", nextUriString);
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,8 @@ package ca.uhn.fhir.jpa.dao;
|
||||||
|
|
||||||
import ca.uhn.fhir.model.dstu2.resource.Bundle;
|
import ca.uhn.fhir.model.dstu2.resource.Bundle;
|
||||||
import ca.uhn.fhir.model.dstu2.resource.Bundle.Entry;
|
import ca.uhn.fhir.model.dstu2.resource.Bundle.Entry;
|
||||||
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
|
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
|
||||||
|
@ -32,8 +34,8 @@ import static org.apache.commons.lang3.StringUtils.defaultString;
|
||||||
public class FhirResourceDaoBundleDstu2 extends BaseHapiFhirResourceDao<Bundle> {
|
public class FhirResourceDaoBundleDstu2 extends BaseHapiFhirResourceDao<Bundle> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void preProcessResourceForStorage(IBaseResource theResource) {
|
protected void preProcessResourceForStorage(IBaseResource theResource, RequestDetails theRequestDetails, TransactionDetails theTransactionDetails, boolean thePerformIndexing) {
|
||||||
super.preProcessResourceForStorage(theResource);
|
super.preProcessResourceForStorage(theResource, theRequestDetails, theTransactionDetails, thePerformIndexing);
|
||||||
|
|
||||||
for (Entry next : ((Bundle)theResource).getEntry()) {
|
for (Entry next : ((Bundle)theResource).getEntry()) {
|
||||||
next.setFullUrl((String) null);
|
next.setFullUrl((String) null);
|
||||||
|
|
|
@ -29,7 +29,9 @@ import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
|
||||||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||||
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
||||||
import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
||||||
|
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
||||||
import ca.uhn.fhir.jpa.api.dao.IDao;
|
import ca.uhn.fhir.jpa.api.dao.IDao;
|
||||||
|
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
|
||||||
import ca.uhn.fhir.jpa.config.HapiFhirLocalContainerEntityManagerFactoryBean;
|
import ca.uhn.fhir.jpa.config.HapiFhirLocalContainerEntityManagerFactoryBean;
|
||||||
import ca.uhn.fhir.jpa.config.HibernatePropertiesProvider;
|
import ca.uhn.fhir.jpa.config.HibernatePropertiesProvider;
|
||||||
import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao;
|
import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao;
|
||||||
|
@ -42,6 +44,8 @@ import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
||||||
import ca.uhn.fhir.jpa.entity.ResourceSearchView;
|
import ca.uhn.fhir.jpa.entity.ResourceSearchView;
|
||||||
import ca.uhn.fhir.jpa.interceptor.JpaPreResourceAccessDetails;
|
import ca.uhn.fhir.jpa.interceptor.JpaPreResourceAccessDetails;
|
||||||
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
|
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
|
||||||
|
import ca.uhn.fhir.jpa.model.entity.IBaseResourceEntity;
|
||||||
|
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceTag;
|
import ca.uhn.fhir.jpa.model.entity.ResourceTag;
|
||||||
import ca.uhn.fhir.jpa.model.search.SearchRuntimeDetails;
|
import ca.uhn.fhir.jpa.model.search.SearchRuntimeDetails;
|
||||||
|
@ -87,6 +91,7 @@ import com.healthmarketscience.sqlbuilder.Condition;
|
||||||
import org.apache.commons.lang3.Validate;
|
import org.apache.commons.lang3.Validate;
|
||||||
import org.hl7.fhir.instance.model.api.IAnyResource;
|
import org.hl7.fhir.instance.model.api.IAnyResource;
|
||||||
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.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
@ -147,6 +152,8 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
@Autowired
|
@Autowired
|
||||||
private DaoConfig myDaoConfig;
|
private DaoConfig myDaoConfig;
|
||||||
@Autowired
|
@Autowired
|
||||||
|
private DaoRegistry myDaoRegistry;
|
||||||
|
@Autowired
|
||||||
private IResourceSearchViewDao myResourceSearchViewDao;
|
private IResourceSearchViewDao myResourceSearchViewDao;
|
||||||
@Autowired
|
@Autowired
|
||||||
private FhirContext myContext;
|
private FhirContext myContext;
|
||||||
|
@ -174,8 +181,11 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
private SqlObjectFactory mySqlBuilderFactory;
|
private SqlObjectFactory mySqlBuilderFactory;
|
||||||
@Autowired
|
@Autowired
|
||||||
private HibernatePropertiesProvider myDialectProvider;
|
private HibernatePropertiesProvider myDialectProvider;
|
||||||
|
@Autowired
|
||||||
|
private ModelConfig myModelConfig;
|
||||||
|
|
||||||
private boolean hasNextIteratorQuery = false;
|
private boolean hasNextIteratorQuery = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
*/
|
*/
|
||||||
|
@ -578,8 +588,14 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
case QUANTITY:
|
case QUANTITY:
|
||||||
theQueryStack.addSortOnQuantity(myResourceName, theParamName, theAscending);
|
theQueryStack.addSortOnQuantity(myResourceName, theParamName, theAscending);
|
||||||
break;
|
break;
|
||||||
|
case NUMBER:
|
||||||
|
case REFERENCE:
|
||||||
|
case COMPOSITE:
|
||||||
|
case URI:
|
||||||
|
case HAS:
|
||||||
|
case SPECIAL:
|
||||||
default:
|
default:
|
||||||
throw new InvalidRequestException("Don't know how to handle composite parameter with type of " + theParamType + " on _sort="+ theParamName);
|
throw new InvalidRequestException("Don't know how to handle composite parameter with type of " + theParamType + " on _sort=" + theParamName);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -587,34 +603,61 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
private void doLoadPids(Collection<ResourcePersistentId> thePids, Collection<ResourcePersistentId> theIncludedPids, List<IBaseResource> theResourceListToPopulate, boolean theForHistoryOperation,
|
private void doLoadPids(Collection<ResourcePersistentId> thePids, Collection<ResourcePersistentId> theIncludedPids, List<IBaseResource> theResourceListToPopulate, boolean theForHistoryOperation,
|
||||||
Map<ResourcePersistentId, Integer> thePosition) {
|
Map<ResourcePersistentId, Integer> thePosition) {
|
||||||
|
|
||||||
List<Long> myLongPersistentIds;
|
Map<Long, Long> resourcePidToVersion = null;
|
||||||
if (thePids.size() < getMaximumPageSize()) {
|
for (ResourcePersistentId next : thePids) {
|
||||||
myLongPersistentIds = normalizeIdListForLastNInClause(ResourcePersistentId.toLongList(thePids));
|
if (next.getVersion() != null && myModelConfig.isRespectVersionsForSearchIncludes()) {
|
||||||
} else {
|
if (resourcePidToVersion == null) {
|
||||||
myLongPersistentIds = ResourcePersistentId.toLongList(thePids);
|
resourcePidToVersion = new HashMap<>();
|
||||||
|
}
|
||||||
|
resourcePidToVersion.put(next.getIdAsLong(), next.getVersion());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Long> versionlessPids = ResourcePersistentId.toLongList(thePids);
|
||||||
|
if (versionlessPids.size() < getMaximumPageSize()) {
|
||||||
|
versionlessPids = normalizeIdListForLastNInClause(versionlessPids);
|
||||||
}
|
}
|
||||||
|
|
||||||
// -- get the resource from the searchView
|
// -- get the resource from the searchView
|
||||||
Collection<ResourceSearchView> resourceSearchViewList = myResourceSearchViewDao.findByResourceIds(myLongPersistentIds);
|
Collection<ResourceSearchView> resourceSearchViewList = myResourceSearchViewDao.findByResourceIds(versionlessPids);
|
||||||
|
|
||||||
//-- preload all tags with tag definition if any
|
//-- preload all tags with tag definition if any
|
||||||
Map<ResourcePersistentId, Collection<ResourceTag>> tagMap = getResourceTagMap(resourceSearchViewList);
|
Map<Long, Collection<ResourceTag>> tagMap = getResourceTagMap(resourceSearchViewList);
|
||||||
|
|
||||||
ResourcePersistentId resourceId;
|
for (IBaseResourceEntity next : resourceSearchViewList) {
|
||||||
for (ResourceSearchView next : resourceSearchViewList) {
|
|
||||||
if (next.getDeleted() != null) {
|
if (next.getDeleted() != null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
Class<? extends IBaseResource> resourceType = myContext.getResourceDefinition(next.getResourceType()).getImplementingClass();
|
Class<? extends IBaseResource> resourceType = myContext.getResourceDefinition(next.getResourceType()).getImplementingClass();
|
||||||
|
|
||||||
resourceId = new ResourcePersistentId(next.getId());
|
ResourcePersistentId resourceId = new ResourcePersistentId(next.getResourceId());
|
||||||
|
|
||||||
IBaseResource resource = myCallingDao.toResource(resourceType, next, tagMap.get(resourceId), theForHistoryOperation);
|
/*
|
||||||
|
* If a specific version is requested via an include, we'll replace the current version
|
||||||
|
* with the specific desired version. This is not the most efficient thing, given that
|
||||||
|
* we're loading the current version and then turning around and throwing it away again.
|
||||||
|
* This could be optimized and probably should be, but it's not critical given that
|
||||||
|
* this only applies to includes, which don't tend to be massive in numbers.
|
||||||
|
*/
|
||||||
|
if (resourcePidToVersion != null) {
|
||||||
|
Long version = resourcePidToVersion.get(next.getResourceId());
|
||||||
|
if (version != null && !version.equals(next.getVersion())) {
|
||||||
|
resourceId.setVersion(version);
|
||||||
|
IFhirResourceDao<? extends IBaseResource> dao = myDaoRegistry.getResourceDao(resourceType);
|
||||||
|
next = dao.readEntity(next.getIdDt().withVersion(Long.toString(version)), null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IBaseResource resource = null;
|
||||||
|
if (next != null) {
|
||||||
|
resource = myCallingDao.toResource(resourceType, next, tagMap.get(next.getId()), theForHistoryOperation);
|
||||||
|
}
|
||||||
if (resource == null) {
|
if (resource == null) {
|
||||||
ourLog.warn("Unable to find resource {}/{}/_history/{} in database", next.getResourceType(), next.getIdDt().getIdPart(), next.getVersion());
|
ourLog.warn("Unable to find resource {}/{}/_history/{} in database", next.getResourceType(), next.getIdDt().getIdPart(), next.getVersion());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
Integer index = thePosition.get(resourceId);
|
Integer index = thePosition.get(resourceId);
|
||||||
if (index == null) {
|
if (index == null) {
|
||||||
ourLog.warn("Got back unexpected resource PID {}", resourceId);
|
ourLog.warn("Got back unexpected resource PID {}", resourceId);
|
||||||
|
@ -639,17 +682,17 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<ResourcePersistentId, Collection<ResourceTag>> getResourceTagMap(Collection<ResourceSearchView> theResourceSearchViewList) {
|
private Map<Long, Collection<ResourceTag>> getResourceTagMap(Collection<? extends IBaseResourceEntity> theResourceSearchViewList) {
|
||||||
|
|
||||||
List<Long> idList = new ArrayList<>(theResourceSearchViewList.size());
|
List<Long> idList = new ArrayList<>(theResourceSearchViewList.size());
|
||||||
|
|
||||||
//-- find all resource has tags
|
//-- find all resource has tags
|
||||||
for (ResourceSearchView resource : theResourceSearchViewList) {
|
for (IBaseResourceEntity resource : theResourceSearchViewList) {
|
||||||
if (resource.isHasTags())
|
if (resource.isHasTags())
|
||||||
idList.add(resource.getId());
|
idList.add(resource.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<ResourcePersistentId, Collection<ResourceTag>> tagMap = new HashMap<>();
|
Map<Long, Collection<ResourceTag>> tagMap = new HashMap<>();
|
||||||
|
|
||||||
//-- no tags
|
//-- no tags
|
||||||
if (idList.size() == 0)
|
if (idList.size() == 0)
|
||||||
|
@ -664,11 +707,11 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
for (ResourceTag tag : tagList) {
|
for (ResourceTag tag : tagList) {
|
||||||
|
|
||||||
resourceId = new ResourcePersistentId(tag.getResourceId());
|
resourceId = new ResourcePersistentId(tag.getResourceId());
|
||||||
tagCol = tagMap.get(resourceId);
|
tagCol = tagMap.get(resourceId.getIdAsLong());
|
||||||
if (tagCol == null) {
|
if (tagCol == null) {
|
||||||
tagCol = new ArrayList<>();
|
tagCol = new ArrayList<>();
|
||||||
tagCol.add(tag);
|
tagCol.add(tag);
|
||||||
tagMap.put(resourceId, tagCol);
|
tagMap.put(resourceId.getIdAsLong(), tagCol);
|
||||||
} else {
|
} else {
|
||||||
tagCol.add(tag);
|
tagCol.add(tag);
|
||||||
}
|
}
|
||||||
|
@ -712,8 +755,12 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
if (theRevIncludes == null || theRevIncludes.isEmpty()) {
|
if (theRevIncludes == null || theRevIncludes.isEmpty()) {
|
||||||
return new HashSet<>();
|
return new HashSet<>();
|
||||||
}
|
}
|
||||||
String searchFieldName = theReverseMode ? "myTargetResourcePid" : "mySourceResourcePid";
|
String searchPidFieldName = theReverseMode ? "myTargetResourcePid" : "mySourceResourcePid";
|
||||||
String findFieldName = theReverseMode ? "mySourceResourcePid" : "myTargetResourcePid";
|
String findPidFieldName = theReverseMode ? "mySourceResourcePid" : "myTargetResourcePid";
|
||||||
|
String findVersionFieldName = null;
|
||||||
|
if (!theReverseMode && myModelConfig.isRespectVersionsForSearchIncludes()) {
|
||||||
|
findVersionFieldName = "myTargetResourceVersion";
|
||||||
|
}
|
||||||
|
|
||||||
List<ResourcePersistentId> nextRoundMatches = new ArrayList<>(theMatches);
|
List<ResourcePersistentId> nextRoundMatches = new ArrayList<>(theMatches);
|
||||||
HashSet<ResourcePersistentId> allAdded = new HashSet<>();
|
HashSet<ResourcePersistentId> allAdded = new HashSet<>();
|
||||||
|
@ -737,24 +784,37 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
|
|
||||||
boolean matchAll = "*".equals(nextInclude.getValue());
|
boolean matchAll = "*".equals(nextInclude.getValue());
|
||||||
if (matchAll) {
|
if (matchAll) {
|
||||||
String sql;
|
StringBuilder sqlBuilder = new StringBuilder();
|
||||||
sql = "SELECT r." + findFieldName + " FROM ResourceLink r WHERE r." + searchFieldName + " IN (:target_pids) ";
|
sqlBuilder.append("SELECT r.").append(findPidFieldName);
|
||||||
|
if (findVersionFieldName != null) {
|
||||||
|
sqlBuilder.append(", r." + findVersionFieldName);
|
||||||
|
}
|
||||||
|
sqlBuilder.append(" FROM ResourceLink r WHERE r.");
|
||||||
|
sqlBuilder.append(searchPidFieldName);
|
||||||
|
sqlBuilder.append(" IN (:target_pids)");
|
||||||
|
String sql = sqlBuilder.toString();
|
||||||
List<Collection<ResourcePersistentId>> partitions = partition(nextRoundMatches, getMaximumPageSize());
|
List<Collection<ResourcePersistentId>> partitions = partition(nextRoundMatches, getMaximumPageSize());
|
||||||
for (Collection<ResourcePersistentId> nextPartition : partitions) {
|
for (Collection<ResourcePersistentId> nextPartition : partitions) {
|
||||||
TypedQuery<Long> q = theEntityManager.createQuery(sql, Long.class);
|
TypedQuery<?> q = theEntityManager.createQuery(sql, Object[].class);
|
||||||
q.setParameter("target_pids", ResourcePersistentId.toLongList(nextPartition));
|
q.setParameter("target_pids", ResourcePersistentId.toLongList(nextPartition));
|
||||||
List<Long> results = q.getResultList();
|
List<?> results = q.getResultList();
|
||||||
for (Long resourceLink : results) {
|
for (Object nextRow : results) {
|
||||||
if (resourceLink == null) {
|
if (nextRow == null) {
|
||||||
// This can happen if there are outgoing references which are canonical or point to
|
// This can happen if there are outgoing references which are canonical or point to
|
||||||
// other servers
|
// other servers
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (theReverseMode) {
|
|
||||||
pidsToInclude.add(new ResourcePersistentId(resourceLink));
|
Long resourceLink;
|
||||||
|
Long version = null;
|
||||||
|
if (findVersionFieldName != null) {
|
||||||
|
resourceLink = (Long) ((Object[]) nextRow)[0];
|
||||||
|
version = (Long) ((Object[]) nextRow)[1];
|
||||||
} else {
|
} else {
|
||||||
pidsToInclude.add(new ResourcePersistentId(resourceLink));
|
resourceLink = (Long)nextRow;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pidsToInclude.add(new ResourcePersistentId(resourceLink, version));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -789,17 +849,22 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
String sql;
|
String sql;
|
||||||
|
|
||||||
boolean haveTargetTypesDefinedByParam = param.hasTargets();
|
boolean haveTargetTypesDefinedByParam = param.hasTargets();
|
||||||
|
String fieldsToLoad = "r." + findPidFieldName;
|
||||||
|
if (findVersionFieldName != null) {
|
||||||
|
fieldsToLoad += ", r." + findVersionFieldName;
|
||||||
|
}
|
||||||
|
|
||||||
if (targetResourceType != null) {
|
if (targetResourceType != null) {
|
||||||
sql = "SELECT r." + findFieldName + " FROM ResourceLink r WHERE r.mySourcePath = :src_path AND r." + searchFieldName + " IN (:target_pids) AND r.myTargetResourceType = :target_resource_type";
|
sql = "SELECT " + fieldsToLoad + " FROM ResourceLink r WHERE r.mySourcePath = :src_path AND r." + searchPidFieldName + " IN (:target_pids) AND r.myTargetResourceType = :target_resource_type";
|
||||||
} else if (haveTargetTypesDefinedByParam) {
|
} else if (haveTargetTypesDefinedByParam) {
|
||||||
sql = "SELECT r." + findFieldName + " FROM ResourceLink r WHERE r.mySourcePath = :src_path AND r." + searchFieldName + " IN (:target_pids) AND r.myTargetResourceType in (:target_resource_types)";
|
sql = "SELECT " + fieldsToLoad + " FROM ResourceLink r WHERE r.mySourcePath = :src_path AND r." + searchPidFieldName + " IN (:target_pids) AND r.myTargetResourceType in (:target_resource_types)";
|
||||||
} else {
|
} else {
|
||||||
sql = "SELECT r." + findFieldName + " FROM ResourceLink r WHERE r.mySourcePath = :src_path AND r." + searchFieldName + " IN (:target_pids)";
|
sql = "SELECT " + fieldsToLoad + " FROM ResourceLink r WHERE r.mySourcePath = :src_path AND r." + searchPidFieldName + " IN (:target_pids)";
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Collection<ResourcePersistentId>> partitions = partition(nextRoundMatches, getMaximumPageSize());
|
List<Collection<ResourcePersistentId>> partitions = partition(nextRoundMatches, getMaximumPageSize());
|
||||||
for (Collection<ResourcePersistentId> nextPartition : partitions) {
|
for (Collection<ResourcePersistentId> nextPartition : partitions) {
|
||||||
TypedQuery<Long> q = theEntityManager.createQuery(sql, Long.class);
|
TypedQuery<?> q = theEntityManager.createQuery(sql, Object[].class);
|
||||||
q.setParameter("src_path", nextPath);
|
q.setParameter("src_path", nextPath);
|
||||||
q.setParameter("target_pids", ResourcePersistentId.toLongList(nextPartition));
|
q.setParameter("target_pids", ResourcePersistentId.toLongList(nextPartition));
|
||||||
if (targetResourceType != null) {
|
if (targetResourceType != null) {
|
||||||
|
@ -807,10 +872,18 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
} else if (haveTargetTypesDefinedByParam) {
|
} else if (haveTargetTypesDefinedByParam) {
|
||||||
q.setParameter("target_resource_types", param.getTargets());
|
q.setParameter("target_resource_types", param.getTargets());
|
||||||
}
|
}
|
||||||
List<Long> results = q.getResultList();
|
List<?> results = q.getResultList();
|
||||||
for (Long resourceLink : results) {
|
for (Object resourceLink : results) {
|
||||||
if (resourceLink != null) {
|
if (resourceLink != null) {
|
||||||
pidsToInclude.add(new ResourcePersistentId(resourceLink));
|
ResourcePersistentId persistentId;
|
||||||
|
if (findVersionFieldName != null) {
|
||||||
|
persistentId = new ResourcePersistentId(((Object[])resourceLink)[0]);
|
||||||
|
persistentId.setVersion((Long) ((Object[])resourceLink)[1]);
|
||||||
|
} else {
|
||||||
|
persistentId = new ResourcePersistentId(resourceLink);
|
||||||
|
}
|
||||||
|
assert persistentId.getId() instanceof Long;
|
||||||
|
pidsToInclude.add(persistentId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1051,6 +1124,7 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
private final boolean myHaveRawSqlHooks;
|
private final boolean myHaveRawSqlHooks;
|
||||||
private final boolean myHavePerfTraceFoundIdHook;
|
private final boolean myHavePerfTraceFoundIdHook;
|
||||||
private final SortSpec mySort;
|
private final SortSpec mySort;
|
||||||
|
private final Integer myOffset;
|
||||||
private boolean myFirst = true;
|
private boolean myFirst = true;
|
||||||
private IncludesIterator myIncludesIterator;
|
private IncludesIterator myIncludesIterator;
|
||||||
private ResourcePersistentId myNext;
|
private ResourcePersistentId myNext;
|
||||||
|
@ -1059,8 +1133,6 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
private boolean myStillNeedToFetchIncludes;
|
private boolean myStillNeedToFetchIncludes;
|
||||||
private int mySkipCount = 0;
|
private int mySkipCount = 0;
|
||||||
private int myNonSkipCount = 0;
|
private int myNonSkipCount = 0;
|
||||||
private final Integer myOffset;
|
|
||||||
|
|
||||||
private ArrayList<SearchQueryExecutor> myQueryList = new ArrayList<>();
|
private ArrayList<SearchQueryExecutor> myQueryList = new ArrayList<>();
|
||||||
|
|
||||||
private QueryIterator(SearchRuntimeDetails theSearchRuntimeDetails, RequestDetails theRequest) {
|
private QueryIterator(SearchRuntimeDetails theSearchRuntimeDetails, RequestDetails theRequest) {
|
||||||
|
|
|
@ -7,6 +7,7 @@ import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
||||||
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
||||||
import ca.uhn.fhir.jpa.dao.r4.TransactionProcessorVersionAdapterR4;
|
import ca.uhn.fhir.jpa.dao.r4.TransactionProcessorVersionAdapterR4;
|
||||||
import ca.uhn.fhir.jpa.dao.tx.HapiTransactionService;
|
import ca.uhn.fhir.jpa.dao.tx.HapiTransactionService;
|
||||||
|
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
import org.hibernate.Session;
|
import org.hibernate.Session;
|
||||||
import org.hibernate.internal.SessionImpl;
|
import org.hibernate.internal.SessionImpl;
|
||||||
|
@ -50,6 +51,9 @@ public class TransactionProcessorTest {
|
||||||
private MatchResourceUrlService myMatchResourceUrlService;
|
private MatchResourceUrlService myMatchResourceUrlService;
|
||||||
@MockBean
|
@MockBean
|
||||||
private HapiTransactionService myHapiTransactionService;
|
private HapiTransactionService myHapiTransactionService;
|
||||||
|
@MockBean
|
||||||
|
private ModelConfig myModelConfig;
|
||||||
|
|
||||||
@MockBean(answer = Answers.RETURNS_DEEP_STUBS)
|
@MockBean(answer = Answers.RETURNS_DEEP_STUBS)
|
||||||
private SessionImpl mySession;
|
private SessionImpl mySession;
|
||||||
|
|
||||||
|
|
|
@ -389,7 +389,7 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
|
||||||
myBundleDao.create(bundle, mySrd);
|
myBundleDao.create(bundle, mySrd);
|
||||||
fail();
|
fail();
|
||||||
} catch (UnprocessableEntityException e) {
|
} catch (UnprocessableEntityException e) {
|
||||||
assertEquals("Unable to store a Bundle resource on this server with a Bundle.type value of: (missing)", e.getMessage());
|
assertEquals("Unable to store a Bundle resource on this server with a Bundle.type value of: (missing). Note that if you are trying to perform a FHIR transaction or batch operation you should POST the Bundle resource to the Base URL of the server, not to the /Bundle endpoint.", e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
bundle = new Bundle();
|
bundle = new Bundle();
|
||||||
|
@ -399,7 +399,7 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
|
||||||
myBundleDao.create(bundle, mySrd);
|
myBundleDao.create(bundle, mySrd);
|
||||||
fail();
|
fail();
|
||||||
} catch (UnprocessableEntityException e) {
|
} catch (UnprocessableEntityException e) {
|
||||||
assertEquals("Unable to store a Bundle resource on this server with a Bundle.type value of: batch-response", e.getMessage());
|
assertEquals("Unable to store a Bundle resource on this server with a Bundle.type value of: batch-response. Note that if you are trying to perform a FHIR transaction or batch operation you should POST the Bundle resource to the Base URL of the server, not to the /Bundle endpoint.", e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
bundle = new Bundle();
|
bundle = new Bundle();
|
||||||
|
|
|
@ -4,8 +4,6 @@ import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
|
||||||
import ca.uhn.fhir.jpa.rp.dstu3.PatientResourceProvider;
|
import ca.uhn.fhir.jpa.rp.dstu3.PatientResourceProvider;
|
||||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||||
import ca.uhn.fhir.util.TestUtil;
|
|
||||||
import org.junit.jupiter.api.AfterAll;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
|
||||||
import javax.servlet.ServletConfig;
|
import javax.servlet.ServletConfig;
|
||||||
|
|
|
@ -526,7 +526,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
|
||||||
myBundleDao.create(bundle, mySrd);
|
myBundleDao.create(bundle, mySrd);
|
||||||
fail();
|
fail();
|
||||||
} catch (UnprocessableEntityException e) {
|
} catch (UnprocessableEntityException e) {
|
||||||
assertEquals("Unable to store a Bundle resource on this server with a Bundle.type value of: (missing)", e.getMessage());
|
assertEquals("Unable to store a Bundle resource on this server with a Bundle.type value of: (missing). Note that if you are trying to perform a FHIR transaction or batch operation you should POST the Bundle resource to the Base URL of the server, not to the /Bundle endpoint.", e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
bundle = new Bundle();
|
bundle = new Bundle();
|
||||||
|
@ -536,7 +536,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
|
||||||
myBundleDao.create(bundle, mySrd);
|
myBundleDao.create(bundle, mySrd);
|
||||||
fail();
|
fail();
|
||||||
} catch (UnprocessableEntityException e) {
|
} catch (UnprocessableEntityException e) {
|
||||||
assertEquals("Unable to store a Bundle resource on this server with a Bundle.type value of: searchset", e.getMessage());
|
assertEquals("Unable to store a Bundle resource on this server with a Bundle.type value of: searchset. Note that if you are trying to perform a FHIR transaction or batch operation you should POST the Bundle resource to the Base URL of the server, not to the /Bundle endpoint.", e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
bundle = new Bundle();
|
bundle = new Bundle();
|
||||||
|
|
|
@ -2985,7 +2985,7 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest {
|
||||||
dr.addPresentedForm(attachment);
|
dr.addPresentedForm(attachment);
|
||||||
|
|
||||||
Attachment attachment2 = new Attachment();
|
Attachment attachment2 = new Attachment();
|
||||||
attachment2.setUrl(IdType.newRandomUuid().getValue()); // this one has no subscitution
|
attachment2.setUrl(IdType.newRandomUuid().getValue()); // this one has no substitution
|
||||||
dr.addPresentedForm(attachment2);
|
dr.addPresentedForm(attachment2);
|
||||||
|
|
||||||
Bundle transactionBundle = new Bundle();
|
Bundle transactionBundle = new Bundle();
|
||||||
|
|
|
@ -842,7 +842,7 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test {
|
||||||
myBundleDao.create(bundle, mySrd);
|
myBundleDao.create(bundle, mySrd);
|
||||||
fail();
|
fail();
|
||||||
} catch (UnprocessableEntityException e) {
|
} catch (UnprocessableEntityException e) {
|
||||||
assertEquals("Unable to store a Bundle resource on this server with a Bundle.type value of: (missing)", e.getMessage());
|
assertEquals("Unable to store a Bundle resource on this server with a Bundle.type value of: (missing). Note that if you are trying to perform a FHIR transaction or batch operation you should POST the Bundle resource to the Base URL of the server, not to the /Bundle endpoint.", e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
bundle = new Bundle();
|
bundle = new Bundle();
|
||||||
|
@ -852,7 +852,7 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test {
|
||||||
myBundleDao.create(bundle, mySrd);
|
myBundleDao.create(bundle, mySrd);
|
||||||
fail();
|
fail();
|
||||||
} catch (UnprocessableEntityException e) {
|
} catch (UnprocessableEntityException e) {
|
||||||
assertEquals("Unable to store a Bundle resource on this server with a Bundle.type value of: searchset", e.getMessage());
|
assertEquals("Unable to store a Bundle resource on this server with a Bundle.type value of: searchset. Note that if you are trying to perform a FHIR transaction or batch operation you should POST the Bundle resource to the Base URL of the server, not to the /Bundle endpoint.", e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
bundle = new Bundle();
|
bundle = new Bundle();
|
||||||
|
|
|
@ -0,0 +1,482 @@
|
||||||
|
package ca.uhn.fhir.jpa.dao.r4;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
||||||
|
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
|
||||||
|
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||||
|
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||||
|
import ca.uhn.fhir.util.BundleBuilder;
|
||||||
|
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.Encounter;
|
||||||
|
import org.hl7.fhir.r4.model.IdType;
|
||||||
|
import org.hl7.fhir.r4.model.Observation;
|
||||||
|
import org.hl7.fhir.r4.model.Patient;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
public class FhirResourceDaoR4VersionedReferenceTest extends BaseJpaR4Test {
|
||||||
|
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4VersionedReferenceTest.class);
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
public void afterEach() {
|
||||||
|
myFhirCtx.getParserOptions().setStripVersionsFromReferences(true);
|
||||||
|
myDaoConfig.setDeleteEnabled(new DaoConfig().isDeleteEnabled());
|
||||||
|
myModelConfig.setRespectVersionsForSearchIncludes(new ModelConfig().isRespectVersionsForSearchIncludes());
|
||||||
|
myModelConfig.setAutoVersionReferenceAtPaths(new ModelConfig().getAutoVersionReferenceAtPaths());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testStoreAndRetrieveVersionedReference() {
|
||||||
|
myFhirCtx.getParserOptions().setStripVersionsFromReferences(false);
|
||||||
|
|
||||||
|
Patient p = new Patient();
|
||||||
|
p.setActive(true);
|
||||||
|
IIdType patientId = myPatientDao.create(p).getId().toUnqualified();
|
||||||
|
assertEquals("1", patientId.getVersionIdPart());
|
||||||
|
assertEquals(null, patientId.getBaseUrl());
|
||||||
|
String patientIdString = patientId.getValue();
|
||||||
|
|
||||||
|
Observation observation = new Observation();
|
||||||
|
observation.getSubject().setReference(patientIdString);
|
||||||
|
IIdType observationId = myObservationDao.create(observation).getId().toUnqualified();
|
||||||
|
|
||||||
|
// Read back
|
||||||
|
observation = myObservationDao.read(observationId);
|
||||||
|
assertEquals(patientIdString, observation.getSubject().getReference());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDontOverwriteExistingVersion() {
|
||||||
|
myFhirCtx.getParserOptions().setStripVersionsFromReferences(false);
|
||||||
|
|
||||||
|
Patient p = new Patient();
|
||||||
|
p.setActive(true);
|
||||||
|
myPatientDao.create(p);
|
||||||
|
|
||||||
|
// Update the patient
|
||||||
|
p.setActive(false);
|
||||||
|
IIdType patientId = myPatientDao.update(p).getId().toUnqualified();
|
||||||
|
|
||||||
|
assertEquals("2", patientId.getVersionIdPart());
|
||||||
|
assertEquals(null, patientId.getBaseUrl());
|
||||||
|
|
||||||
|
Observation observation = new Observation();
|
||||||
|
observation.getSubject().setReference(patientId.withVersion("1").getValue());
|
||||||
|
IIdType observationId = myObservationDao.create(observation).getId().toUnqualified();
|
||||||
|
|
||||||
|
// Read back
|
||||||
|
observation = myObservationDao.read(observationId);
|
||||||
|
assertEquals(patientId.withVersion("1").getValue(), observation.getSubject().getReference());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInsertVersionedReferenceAtPath() {
|
||||||
|
myFhirCtx.getParserOptions().setStripVersionsFromReferences(false);
|
||||||
|
myModelConfig.setAutoVersionReferenceAtPaths("Observation.subject");
|
||||||
|
|
||||||
|
Patient p = new Patient();
|
||||||
|
p.setActive(true);
|
||||||
|
IIdType patientId = myPatientDao.create(p).getId().toUnqualified();
|
||||||
|
assertEquals("1", patientId.getVersionIdPart());
|
||||||
|
assertEquals(null, patientId.getBaseUrl());
|
||||||
|
String patientIdString = patientId.getValue();
|
||||||
|
|
||||||
|
// Create - put an unversioned reference in the subject
|
||||||
|
Observation observation = new Observation();
|
||||||
|
observation.getSubject().setReference(patientId.toVersionless().getValue());
|
||||||
|
IIdType observationId = myObservationDao.create(observation).getId().toUnqualified();
|
||||||
|
|
||||||
|
// Read back and verify that reference is now versioned
|
||||||
|
observation = myObservationDao.read(observationId);
|
||||||
|
assertEquals(patientIdString, observation.getSubject().getReference());
|
||||||
|
|
||||||
|
myCaptureQueriesListener.clear();
|
||||||
|
|
||||||
|
// Update - put an unversioned reference in the subject
|
||||||
|
observation = new Observation();
|
||||||
|
observation.setId(observationId);
|
||||||
|
observation.addIdentifier().setSystem("http://foo").setValue("bar");
|
||||||
|
observation.getSubject().setReference(patientId.toVersionless().getValue());
|
||||||
|
myObservationDao.update(observation);
|
||||||
|
|
||||||
|
// Make sure we're not introducing any extra DB operations
|
||||||
|
assertEquals(5, myCaptureQueriesListener.logSelectQueries().size());
|
||||||
|
|
||||||
|
// Read back and verify that reference is now versioned
|
||||||
|
observation = myObservationDao.read(observationId);
|
||||||
|
assertEquals(patientIdString, observation.getSubject().getReference());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInsertVersionedReferenceAtPath_InTransaction_SourceAndTargetBothCreated() {
|
||||||
|
myFhirCtx.getParserOptions().setStripVersionsFromReferences(false);
|
||||||
|
myModelConfig.setAutoVersionReferenceAtPaths("Observation.subject");
|
||||||
|
|
||||||
|
|
||||||
|
BundleBuilder builder = new BundleBuilder(myFhirCtx);
|
||||||
|
|
||||||
|
Patient patient = new Patient();
|
||||||
|
patient.setId(IdType.newRandomUuid());
|
||||||
|
patient.setActive(true);
|
||||||
|
builder.addTransactionCreateEntry(patient);
|
||||||
|
|
||||||
|
Encounter encounter = new Encounter();
|
||||||
|
encounter.setId(IdType.newRandomUuid());
|
||||||
|
encounter.addIdentifier().setSystem("http://baz").setValue("baz");
|
||||||
|
builder.addTransactionCreateEntry(encounter);
|
||||||
|
|
||||||
|
Observation observation = new Observation();
|
||||||
|
observation.getSubject().setReference(patient.getId()); // versioned
|
||||||
|
observation.getEncounter().setReference(encounter.getId()); // not versioned
|
||||||
|
builder.addTransactionCreateEntry(observation);
|
||||||
|
|
||||||
|
Bundle outcome = mySystemDao.transaction(mySrd, (Bundle) builder.getBundle());
|
||||||
|
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
|
||||||
|
IdType patientId = new IdType(outcome.getEntry().get(0).getResponse().getLocation());
|
||||||
|
IdType encounterId = new IdType(outcome.getEntry().get(1).getResponse().getLocation());
|
||||||
|
IdType observationId = new IdType(outcome.getEntry().get(2).getResponse().getLocation());
|
||||||
|
assertTrue(patientId.hasVersionIdPart());
|
||||||
|
assertTrue(encounterId.hasVersionIdPart());
|
||||||
|
assertTrue(observationId.hasVersionIdPart());
|
||||||
|
|
||||||
|
// Read back and verify that reference is now versioned
|
||||||
|
observation = myObservationDao.read(observationId);
|
||||||
|
assertEquals(patientId.getValue(), observation.getSubject().getReference());
|
||||||
|
assertEquals(encounterId.toVersionless().getValue(), observation.getEncounter().getReference());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInsertVersionedReferenceAtPath_InTransaction_TargetConditionalCreatedNop() {
|
||||||
|
myFhirCtx.getParserOptions().setStripVersionsFromReferences(false);
|
||||||
|
myModelConfig.setAutoVersionReferenceAtPaths("Observation.subject");
|
||||||
|
|
||||||
|
{
|
||||||
|
// Create patient
|
||||||
|
Patient patient = new Patient();
|
||||||
|
patient.setId(IdType.newRandomUuid());
|
||||||
|
patient.setActive(true);
|
||||||
|
myPatientDao.create(patient).getId();
|
||||||
|
|
||||||
|
// Update patient to make a second version
|
||||||
|
patient.setActive(false);
|
||||||
|
myPatientDao.update(patient);
|
||||||
|
|
||||||
|
// Create encounter
|
||||||
|
Encounter encounter = new Encounter();
|
||||||
|
encounter.setId(IdType.newRandomUuid());
|
||||||
|
encounter.addIdentifier().setSystem("http://baz").setValue("baz");
|
||||||
|
myEncounterDao.create(encounter);
|
||||||
|
}
|
||||||
|
|
||||||
|
BundleBuilder builder = new BundleBuilder(myFhirCtx);
|
||||||
|
|
||||||
|
Patient patient = new Patient();
|
||||||
|
patient.setId(IdType.newRandomUuid());
|
||||||
|
patient.setActive(true);
|
||||||
|
builder.addTransactionCreateEntry(patient).conditional("Patient?active=false");
|
||||||
|
|
||||||
|
Encounter encounter = new Encounter();
|
||||||
|
encounter.setId(IdType.newRandomUuid());
|
||||||
|
encounter.addIdentifier().setSystem("http://baz").setValue("baz");
|
||||||
|
builder.addTransactionCreateEntry(encounter).conditional("Encounter?identifier=http://baz|baz");
|
||||||
|
|
||||||
|
Observation observation = new Observation();
|
||||||
|
observation.getSubject().setReference(patient.getId()); // versioned
|
||||||
|
observation.getEncounter().setReference(encounter.getId()); // not versioned
|
||||||
|
builder.addTransactionCreateEntry(observation);
|
||||||
|
|
||||||
|
Bundle outcome = mySystemDao.transaction(mySrd, (Bundle) builder.getBundle());
|
||||||
|
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
|
||||||
|
assertEquals("200 OK", outcome.getEntry().get(0).getResponse().getStatus());
|
||||||
|
assertEquals("200 OK", outcome.getEntry().get(1).getResponse().getStatus());
|
||||||
|
assertEquals("201 Created", outcome.getEntry().get(2).getResponse().getStatus());
|
||||||
|
IdType patientId = new IdType(outcome.getEntry().get(0).getResponse().getLocation());
|
||||||
|
IdType encounterId = new IdType(outcome.getEntry().get(1).getResponse().getLocation());
|
||||||
|
IdType observationId = new IdType(outcome.getEntry().get(2).getResponse().getLocation());
|
||||||
|
assertEquals("2", patientId.getVersionIdPart());
|
||||||
|
assertEquals("1", encounterId.getVersionIdPart());
|
||||||
|
assertEquals("1", observationId.getVersionIdPart());
|
||||||
|
|
||||||
|
// Read back and verify that reference is now versioned
|
||||||
|
observation = myObservationDao.read(observationId);
|
||||||
|
assertEquals(patientId.getValue(), observation.getSubject().getReference());
|
||||||
|
assertEquals(encounterId.toVersionless().getValue(), observation.getEncounter().getReference());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInsertVersionedReferenceAtPath_InTransaction_TargetUpdate() {
|
||||||
|
myFhirCtx.getParserOptions().setStripVersionsFromReferences(false);
|
||||||
|
myDaoConfig.setDeleteEnabled(false);
|
||||||
|
myModelConfig.setAutoVersionReferenceAtPaths("Observation.subject");
|
||||||
|
|
||||||
|
{
|
||||||
|
// Create patient
|
||||||
|
Patient patient = new Patient();
|
||||||
|
patient.setId("PATIENT");
|
||||||
|
patient.setActive(true);
|
||||||
|
myPatientDao.update(patient).getId();
|
||||||
|
|
||||||
|
// Update patient to make a second version
|
||||||
|
patient.setActive(false);
|
||||||
|
myPatientDao.update(patient);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
BundleBuilder builder = new BundleBuilder(myFhirCtx);
|
||||||
|
|
||||||
|
Patient patient = new Patient();
|
||||||
|
patient.setId("Patient/PATIENT");
|
||||||
|
patient.setActive(true);
|
||||||
|
builder.addTransactionUpdateEntry(patient);
|
||||||
|
|
||||||
|
Observation observation = new Observation();
|
||||||
|
observation.getSubject().setReference(patient.getId()); // versioned
|
||||||
|
builder.addTransactionCreateEntry(observation);
|
||||||
|
|
||||||
|
myCaptureQueriesListener.clear();
|
||||||
|
Bundle outcome = mySystemDao.transaction(mySrd, (Bundle) builder.getBundle());
|
||||||
|
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
|
||||||
|
assertEquals("200 OK", outcome.getEntry().get(0).getResponse().getStatus());
|
||||||
|
assertEquals("201 Created", outcome.getEntry().get(1).getResponse().getStatus());
|
||||||
|
IdType patientId = new IdType(outcome.getEntry().get(0).getResponse().getLocation());
|
||||||
|
IdType observationId = new IdType(outcome.getEntry().get(1).getResponse().getLocation());
|
||||||
|
assertEquals("3", patientId.getVersionIdPart());
|
||||||
|
assertEquals("1", observationId.getVersionIdPart());
|
||||||
|
|
||||||
|
// Make sure we're not introducing any extra DB operations
|
||||||
|
assertEquals(3, myCaptureQueriesListener.logSelectQueries().size());
|
||||||
|
|
||||||
|
// Read back and verify that reference is now versioned
|
||||||
|
observation = myObservationDao.read(observationId);
|
||||||
|
assertEquals(patientId.getValue(), observation.getSubject().getReference());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInsertVersionedReferenceAtPath_InTransaction_TargetUpdateConditional() {
|
||||||
|
myFhirCtx.getParserOptions().setStripVersionsFromReferences(false);
|
||||||
|
myModelConfig.setAutoVersionReferenceAtPaths("Observation.subject");
|
||||||
|
|
||||||
|
{
|
||||||
|
// Create patient
|
||||||
|
Patient patient = new Patient();
|
||||||
|
patient.setId(IdType.newRandomUuid());
|
||||||
|
patient.setActive(true);
|
||||||
|
myPatientDao.create(patient).getId();
|
||||||
|
|
||||||
|
// Update patient to make a second version
|
||||||
|
patient.setActive(false);
|
||||||
|
myPatientDao.update(patient);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
BundleBuilder builder = new BundleBuilder(myFhirCtx);
|
||||||
|
|
||||||
|
Patient patient = new Patient();
|
||||||
|
patient.setId(IdType.newRandomUuid());
|
||||||
|
patient.setActive(true);
|
||||||
|
builder
|
||||||
|
.addTransactionUpdateEntry(patient)
|
||||||
|
.conditional("Patient?active=false");
|
||||||
|
|
||||||
|
Observation observation = new Observation();
|
||||||
|
observation.getSubject().setReference(patient.getId()); // versioned
|
||||||
|
builder.addTransactionCreateEntry(observation);
|
||||||
|
|
||||||
|
myCaptureQueriesListener.clear();
|
||||||
|
|
||||||
|
Bundle outcome = mySystemDao.transaction(mySrd, (Bundle) builder.getBundle());
|
||||||
|
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
|
||||||
|
assertEquals("200 OK", outcome.getEntry().get(0).getResponse().getStatus());
|
||||||
|
assertEquals("201 Created", outcome.getEntry().get(1).getResponse().getStatus());
|
||||||
|
IdType patientId = new IdType(outcome.getEntry().get(0).getResponse().getLocation());
|
||||||
|
IdType observationId = new IdType(outcome.getEntry().get(1).getResponse().getLocation());
|
||||||
|
assertEquals("3", patientId.getVersionIdPart());
|
||||||
|
assertEquals("1", observationId.getVersionIdPart());
|
||||||
|
|
||||||
|
// Make sure we're not introducing any extra DB operations
|
||||||
|
assertEquals(4, myCaptureQueriesListener.logSelectQueries().size());
|
||||||
|
|
||||||
|
// Read back and verify that reference is now versioned
|
||||||
|
observation = myObservationDao.read(observationId);
|
||||||
|
assertEquals(patientId.getValue(), observation.getSubject().getReference());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSearchAndIncludeVersionedReference_Asynchronous() {
|
||||||
|
myFhirCtx.getParserOptions().setStripVersionsFromReferences(false);
|
||||||
|
myModelConfig.setRespectVersionsForSearchIncludes(true);
|
||||||
|
|
||||||
|
// Create the patient
|
||||||
|
Patient p = new Patient();
|
||||||
|
p.addIdentifier().setSystem("http://foo").setValue("1");
|
||||||
|
myPatientDao.create(p);
|
||||||
|
|
||||||
|
// Update the patient
|
||||||
|
p.getIdentifier().get(0).setValue("2");
|
||||||
|
IIdType patientId = myPatientDao.update(p).getId().toUnqualified();
|
||||||
|
assertEquals("2", patientId.getVersionIdPart());
|
||||||
|
|
||||||
|
Observation observation = new Observation();
|
||||||
|
observation.getSubject().setReference(patientId.withVersion("1").getValue());
|
||||||
|
IIdType observationId = myObservationDao.create(observation).getId().toUnqualified();
|
||||||
|
|
||||||
|
// Search - Non Synchronous for *
|
||||||
|
{
|
||||||
|
IBundleProvider outcome = myObservationDao.search(new SearchParameterMap().addInclude(IBaseResource.INCLUDE_ALL));
|
||||||
|
assertEquals(1, outcome.sizeOrThrowNpe());
|
||||||
|
List<IBaseResource> resources = outcome.getResources(0, 1);
|
||||||
|
assertEquals(2, resources.size());
|
||||||
|
assertEquals(observationId.getValue(), resources.get(0).getIdElement().getValue());
|
||||||
|
assertEquals(patientId.withVersion("1").getValue(), resources.get(1).getIdElement().getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search - Non Synchronous for named include
|
||||||
|
{
|
||||||
|
IBundleProvider outcome = myObservationDao.search(new SearchParameterMap().addInclude(Observation.INCLUDE_PATIENT));
|
||||||
|
assertEquals(1, outcome.sizeOrThrowNpe());
|
||||||
|
List<IBaseResource> resources = outcome.getResources(0, 1);
|
||||||
|
assertEquals(2, resources.size());
|
||||||
|
assertEquals(observationId.getValue(), resources.get(0).getIdElement().getValue());
|
||||||
|
assertEquals(patientId.withVersion("1").getValue(), resources.get(1).getIdElement().getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSearchAndIncludeVersionedReference_Synchronous() {
|
||||||
|
myFhirCtx.getParserOptions().setStripVersionsFromReferences(false);
|
||||||
|
myModelConfig.setRespectVersionsForSearchIncludes(true);
|
||||||
|
|
||||||
|
// Create the patient
|
||||||
|
Patient p = new Patient();
|
||||||
|
p.addIdentifier().setSystem("http://foo").setValue("1");
|
||||||
|
myPatientDao.create(p);
|
||||||
|
|
||||||
|
// Update the patient
|
||||||
|
p.getIdentifier().get(0).setValue("2");
|
||||||
|
IIdType patientId = myPatientDao.update(p).getId().toUnqualified();
|
||||||
|
assertEquals("2", patientId.getVersionIdPart());
|
||||||
|
|
||||||
|
Observation observation = new Observation();
|
||||||
|
observation.getSubject().setReference(patientId.withVersion("1").getValue());
|
||||||
|
IIdType observationId = myObservationDao.create(observation).getId().toUnqualified();
|
||||||
|
|
||||||
|
// Search - Non Synchronous for *
|
||||||
|
{
|
||||||
|
IBundleProvider outcome = myObservationDao.search(SearchParameterMap.newSynchronous().addInclude(IBaseResource.INCLUDE_ALL));
|
||||||
|
assertEquals(2, outcome.sizeOrThrowNpe());
|
||||||
|
List<IBaseResource> resources = outcome.getResources(0, 2);
|
||||||
|
assertEquals(2, resources.size());
|
||||||
|
assertEquals(observationId.getValue(), resources.get(0).getIdElement().getValue());
|
||||||
|
assertEquals(patientId.withVersion("1").getValue(), resources.get(1).getIdElement().getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search - Non Synchronous for named include
|
||||||
|
{
|
||||||
|
IBundleProvider outcome = myObservationDao.search(SearchParameterMap.newSynchronous().addInclude(Observation.INCLUDE_PATIENT));
|
||||||
|
assertEquals(2, outcome.sizeOrThrowNpe());
|
||||||
|
List<IBaseResource> resources = outcome.getResources(0, 2);
|
||||||
|
assertEquals(2, resources.size());
|
||||||
|
assertEquals(observationId.getValue(), resources.get(0).getIdElement().getValue());
|
||||||
|
assertEquals(patientId.withVersion("1").getValue(), resources.get(1).getIdElement().getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSearchAndIncludeUnersionedReference_Asynchronous() {
|
||||||
|
myFhirCtx.getParserOptions().setStripVersionsFromReferences(true);
|
||||||
|
myModelConfig.setRespectVersionsForSearchIncludes(true);
|
||||||
|
|
||||||
|
// Create the patient
|
||||||
|
Patient p = new Patient();
|
||||||
|
p.addIdentifier().setSystem("http://foo").setValue("1");
|
||||||
|
myPatientDao.create(p);
|
||||||
|
|
||||||
|
// Update the patient
|
||||||
|
p.getIdentifier().get(0).setValue("2");
|
||||||
|
IIdType patientId = myPatientDao.update(p).getId().toUnqualified();
|
||||||
|
assertEquals("2", patientId.getVersionIdPart());
|
||||||
|
|
||||||
|
Observation observation = new Observation();
|
||||||
|
observation.getSubject().setReference(patientId.withVersion("1").getValue());
|
||||||
|
IIdType observationId = myObservationDao.create(observation).getId().toUnqualified();
|
||||||
|
|
||||||
|
// Search - Non Synchronous for *
|
||||||
|
{
|
||||||
|
IBundleProvider outcome = myObservationDao.search(new SearchParameterMap().addInclude(IBaseResource.INCLUDE_ALL));
|
||||||
|
assertEquals(1, outcome.sizeOrThrowNpe());
|
||||||
|
List<IBaseResource> resources = outcome.getResources(0, 1);
|
||||||
|
assertEquals(2, resources.size());
|
||||||
|
assertEquals(observationId.getValue(), resources.get(0).getIdElement().getValue());
|
||||||
|
assertEquals(patientId.withVersion("2").getValue(), resources.get(1).getIdElement().getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search - Non Synchronous for named include
|
||||||
|
{
|
||||||
|
IBundleProvider outcome = myObservationDao.search(new SearchParameterMap().addInclude(Observation.INCLUDE_PATIENT));
|
||||||
|
assertEquals(1, outcome.sizeOrThrowNpe());
|
||||||
|
List<IBaseResource> resources = outcome.getResources(0, 1);
|
||||||
|
assertEquals(2, resources.size());
|
||||||
|
assertEquals(observationId.getValue(), resources.get(0).getIdElement().getValue());
|
||||||
|
assertEquals(patientId.withVersion("2").getValue(), resources.get(1).getIdElement().getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSearchAndIncludeUnversionedReference_Synchronous() {
|
||||||
|
myFhirCtx.getParserOptions().setStripVersionsFromReferences(true);
|
||||||
|
myModelConfig.setRespectVersionsForSearchIncludes(true);
|
||||||
|
|
||||||
|
// Create the patient
|
||||||
|
Patient p = new Patient();
|
||||||
|
p.addIdentifier().setSystem("http://foo").setValue("1");
|
||||||
|
myPatientDao.create(p);
|
||||||
|
|
||||||
|
// Update the patient
|
||||||
|
p.getIdentifier().get(0).setValue("2");
|
||||||
|
IIdType patientId = myPatientDao.update(p).getId().toUnqualified();
|
||||||
|
assertEquals("2", patientId.getVersionIdPart());
|
||||||
|
|
||||||
|
Observation observation = new Observation();
|
||||||
|
observation.getSubject().setReference(patientId.withVersion("1").getValue());
|
||||||
|
IIdType observationId = myObservationDao.create(observation).getId().toUnqualified();
|
||||||
|
|
||||||
|
// Search - Non Synchronous for *
|
||||||
|
{
|
||||||
|
IBundleProvider outcome = myObservationDao.search(SearchParameterMap.newSynchronous().addInclude(IBaseResource.INCLUDE_ALL));
|
||||||
|
assertEquals(2, outcome.sizeOrThrowNpe());
|
||||||
|
List<IBaseResource> resources = outcome.getResources(0, 2);
|
||||||
|
assertEquals(2, resources.size());
|
||||||
|
assertEquals(observationId.getValue(), resources.get(0).getIdElement().getValue());
|
||||||
|
assertEquals(patientId.withVersion("2").getValue(), resources.get(1).getIdElement().getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search - Non Synchronous for named include
|
||||||
|
{
|
||||||
|
IBundleProvider outcome = myObservationDao.search(SearchParameterMap.newSynchronous().addInclude(Observation.INCLUDE_PATIENT));
|
||||||
|
assertEquals(2, outcome.sizeOrThrowNpe());
|
||||||
|
List<IBaseResource> resources = outcome.getResources(0, 2);
|
||||||
|
assertEquals(2, resources.size());
|
||||||
|
assertEquals(observationId.getValue(), resources.get(0).getIdElement().getValue());
|
||||||
|
assertEquals(patientId.withVersion("2").getValue(), resources.get(1).getIdElement().getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -257,7 +257,7 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test {
|
||||||
client.create().resource(resBody).execute().getId();
|
client.create().resource(resBody).execute().getId();
|
||||||
fail();
|
fail();
|
||||||
} catch (UnprocessableEntityException e) {
|
} catch (UnprocessableEntityException e) {
|
||||||
assertThat(e.getMessage(), containsString("Unable to store a Bundle resource on this server with a Bundle.type value of: transaction"));
|
assertThat(e.getMessage(), containsString("Unable to store a Bundle resource on this server with a Bundle.type value of: transaction. Note that if you are trying to perform a FHIR transaction or batch operation you should POST the Bundle resource to the Base URL of the server, not to the /Bundle endpoint."));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -468,7 +468,7 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
|
||||||
client.create().resource(resBody).execute().getId();
|
client.create().resource(resBody).execute().getId();
|
||||||
fail();
|
fail();
|
||||||
} catch (UnprocessableEntityException e) {
|
} catch (UnprocessableEntityException e) {
|
||||||
assertThat(e.getMessage(), containsString("Unable to store a Bundle resource on this server with a Bundle.type value of: transaction"));
|
assertThat(e.getMessage(), containsString("Unable to store a Bundle resource on this server with a Bundle.type value of: transaction. Note that if you are trying to perform a FHIR transaction or batch operation you should POST the Bundle resource to the Base URL of the server, not to the /Bundle endpoint."));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -798,7 +798,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
|
||||||
client.create().resource(resBody).execute();
|
client.create().resource(resBody).execute();
|
||||||
fail();
|
fail();
|
||||||
} catch (UnprocessableEntityException e) {
|
} catch (UnprocessableEntityException e) {
|
||||||
assertThat(e.getMessage(), containsString("Unable to store a Bundle resource on this server with a Bundle.type value of: transaction"));
|
assertThat(e.getMessage(), containsString("Unable to store a Bundle resource on this server with a Bundle.type value of: transaction. Note that if you are trying to perform a FHIR transaction or batch operation you should POST the Bundle resource to the Base URL of the server, not to the /Bundle endpoint."));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -123,6 +123,9 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
|
||||||
Builder.BuilderWithTableName quantityTable = version.onTable("HFJ_SPIDX_QUANTITY");
|
Builder.BuilderWithTableName quantityTable = version.onTable("HFJ_SPIDX_QUANTITY");
|
||||||
quantityTable.modifyColumn("20210116.1", "SP_VALUE").nullable().failureAllowed().withType(ColumnTypeEnum.DOUBLE);
|
quantityTable.modifyColumn("20210116.1", "SP_VALUE").nullable().failureAllowed().withType(ColumnTypeEnum.DOUBLE);
|
||||||
|
|
||||||
|
// HFJ_RES_LINK
|
||||||
|
version.onTable("HFJ_RES_LINK")
|
||||||
|
.addColumn("20210126.1", "TARGET_RESOURCE_VERSION").nullable().type(ColumnTypeEnum.LONG);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void init520() {
|
protected void init520() {
|
||||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.api;
|
||||||
|
|
||||||
/*-
|
/*-
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR Model
|
* HAPI FHIR JPA Model
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||||
* %%
|
* %%
|
||||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.config;
|
||||||
|
|
||||||
/*-
|
/*-
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR Model
|
* HAPI FHIR JPA Model
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||||
* %%
|
* %%
|
||||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.cross;
|
||||||
|
|
||||||
/*-
|
/*-
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR Model
|
* HAPI FHIR JPA Model
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||||
* %%
|
* %%
|
||||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.cross;
|
||||||
|
|
||||||
/*-
|
/*-
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR Model
|
* HAPI FHIR JPA Model
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||||
* %%
|
* %%
|
||||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.cross;
|
||||||
|
|
||||||
/*-
|
/*-
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR Model
|
* HAPI FHIR JPA Model
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||||
* %%
|
* %%
|
||||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR Model
|
* HAPI FHIR JPA Model
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||||
* %%
|
* %%
|
||||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
||||||
|
|
||||||
/*-
|
/*-
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR Model
|
* HAPI FHIR JPA Model
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||||
* %%
|
* %%
|
||||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
||||||
|
|
||||||
/*-
|
/*-
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR Model
|
* HAPI FHIR JPA Model
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||||
* %%
|
* %%
|
||||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR Model
|
* HAPI FHIR JPA Model
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||||
* %%
|
* %%
|
||||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR Model
|
* HAPI FHIR JPA Model
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||||
* %%
|
* %%
|
||||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
||||||
|
|
||||||
/*-
|
/*-
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR Model
|
* HAPI FHIR JPA Model
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||||
* %%
|
* %%
|
||||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR Model
|
* HAPI FHIR JPA Model
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||||
* %%
|
* %%
|
||||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR Model
|
* HAPI FHIR JPA Model
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||||
* %%
|
* %%
|
||||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
||||||
|
|
||||||
/*-
|
/*-
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR Model
|
* HAPI FHIR JPA Model
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||||
* %%
|
* %%
|
||||||
|
@ -20,20 +20,25 @@ package ca.uhn.fhir.jpa.model.entity;
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import ca.uhn.fhir.context.ParserOptions;
|
||||||
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
|
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import org.apache.commons.lang3.ObjectUtils;
|
|
||||||
import org.apache.commons.lang3.Validate;
|
import org.apache.commons.lang3.Validate;
|
||||||
import org.hl7.fhir.dstu2.model.Subscription;
|
import org.hl7.fhir.dstu2.model.Subscription;
|
||||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||||
import org.hl7.fhir.r4.model.DateTimeType;
|
import org.hl7.fhir.r4.model.DateTimeType;
|
||||||
|
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
|
||||||
|
|
||||||
// TODO: move this to ca.uhn.fhir.jpa.model.config
|
// TODO: move this to ca.uhn.fhir.jpa.model.config
|
||||||
public class ModelConfig {
|
public class ModelConfig {
|
||||||
|
|
||||||
|
@ -90,6 +95,9 @@ public class ModelConfig {
|
||||||
private IPrimitiveType<Date> myPeriodIndexEndOfTime;
|
private IPrimitiveType<Date> myPeriodIndexEndOfTime;
|
||||||
|
|
||||||
private NormalizedQuantitySearchLevel myNormalizedQuantitySearchLevel;
|
private NormalizedQuantitySearchLevel myNormalizedQuantitySearchLevel;
|
||||||
|
private Set<String> myAutoVersionReferenceAtPaths = Collections.emptySet();
|
||||||
|
private Map<String, Set<String>> myTypeToAutoVersionReferenceAtPaths = Collections.emptyMap();
|
||||||
|
private boolean myRespectVersionsForSearchIncludes;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
|
@ -268,7 +276,7 @@ public class ModelConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
HashSet<String> treatBaseUrlsAsLocal = new HashSet<>();
|
HashSet<String> treatBaseUrlsAsLocal = new HashSet<>();
|
||||||
for (String next : ObjectUtils.defaultIfNull(theTreatBaseUrlsAsLocal, new HashSet<String>())) {
|
for (String next : defaultIfNull(theTreatBaseUrlsAsLocal, new HashSet<String>())) {
|
||||||
while (next.endsWith("/")) {
|
while (next.endsWith("/")) {
|
||||||
next = next.substring(0, next.length() - 1);
|
next = next.substring(0, next.length() - 1);
|
||||||
}
|
}
|
||||||
|
@ -618,6 +626,110 @@ public class ModelConfig {
|
||||||
myNormalizedQuantitySearchLevel = theNormalizedQuantitySearchLevel;
|
myNormalizedQuantitySearchLevel = theNormalizedQuantitySearchLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When set with resource paths (e.g. <code>"Observation.subject"</code>), any references found at the given paths
|
||||||
|
* will automatically have versions appended. The version used will be the current version of the given resource.
|
||||||
|
*
|
||||||
|
* @since 5.3.0
|
||||||
|
*/
|
||||||
|
public Set<String> getAutoVersionReferenceAtPaths() {
|
||||||
|
return myAutoVersionReferenceAtPaths;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When set with resource paths (e.g. <code>"Observation.subject"</code>), any references found at the given paths
|
||||||
|
* will automatically have versions appended. The version used will be the current version of the given resource.
|
||||||
|
* <p>
|
||||||
|
* Versions will only be added if the reference does not already have a version, so any versioned references
|
||||||
|
* supplied by the client will take precedence over the automatic current version.
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Note that for this setting to be useful, the {@link ParserOptions}
|
||||||
|
* {@link ParserOptions#getDontStripVersionsFromReferencesAtPaths() DontStripVersionsFromReferencesAtPaths}
|
||||||
|
* option must also be set.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param thePaths A collection of reference paths for which the versions will be appended automatically
|
||||||
|
* when serializing, e.g. "Patient.managingOrganization" or "AuditEvent.object.reference". Note that
|
||||||
|
* only resource name and field names with dots separating is allowed here (no repetition
|
||||||
|
* indicators, FluentPath expressions, etc.)
|
||||||
|
* @since 5.3.0
|
||||||
|
*/
|
||||||
|
public void setAutoVersionReferenceAtPaths(String... thePaths) {
|
||||||
|
Set<String> paths = Collections.emptySet();
|
||||||
|
if (thePaths != null) {
|
||||||
|
paths = new HashSet<>(Arrays.asList(thePaths));
|
||||||
|
}
|
||||||
|
setAutoVersionReferenceAtPaths(paths);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When set with resource paths (e.g. <code>"Observation.subject"</code>), any references found at the given paths
|
||||||
|
* will automatically have versions appended. The version used will be the current version of the given resource.
|
||||||
|
* <p>
|
||||||
|
* Versions will only be added if the reference does not already have a version, so any versioned references
|
||||||
|
* supplied by the client will take precedence over the automatic current version.
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Note that for this setting to be useful, the {@link ParserOptions}
|
||||||
|
* {@link ParserOptions#getDontStripVersionsFromReferencesAtPaths() DontStripVersionsFromReferencesAtPaths}
|
||||||
|
* option must also be set
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param thePaths A collection of reference paths for which the versions will be appended automatically
|
||||||
|
* when serializing, e.g. "Patient.managingOrganization" or "AuditEvent.object.reference". Note that
|
||||||
|
* only resource name and field names with dots separating is allowed here (no repetition
|
||||||
|
* indicators, FluentPath expressions, etc.)
|
||||||
|
* @since 5.3.0
|
||||||
|
*/
|
||||||
|
public void setAutoVersionReferenceAtPaths(Set<String> thePaths) {
|
||||||
|
Set<String> paths = defaultIfNull(thePaths, Collections.emptySet());
|
||||||
|
Map<String, Set<String>> byType = new HashMap<>();
|
||||||
|
for (String nextPath : paths) {
|
||||||
|
int doxIdx = nextPath.indexOf('.');
|
||||||
|
Validate.isTrue(doxIdx > 0, "Invalid path for auto-version reference at path: %s", nextPath);
|
||||||
|
String type = nextPath.substring(0, doxIdx);
|
||||||
|
byType.computeIfAbsent(type, t -> new HashSet<>()).add(nextPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
myAutoVersionReferenceAtPaths = paths;
|
||||||
|
myTypeToAutoVersionReferenceAtPaths = byType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a sub-collection of {@link #getAutoVersionReferenceAtPaths()} containing only paths
|
||||||
|
* for the given resource type.
|
||||||
|
*
|
||||||
|
* @since 5.3.0
|
||||||
|
*/
|
||||||
|
public Set<String> getAutoVersionReferenceAtPathsByResourceType(String theResourceType) {
|
||||||
|
Validate.notEmpty(theResourceType, "theResourceType must not be null or empty");
|
||||||
|
Set<String> retVal = myTypeToAutoVersionReferenceAtPaths.get(theResourceType);
|
||||||
|
retVal = defaultIfNull(retVal, Collections.emptySet());
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should searches with <code>_include</code> respect versioned references, and pull the specific requested version.
|
||||||
|
* This may have performance impacts on heavily loaded systems.
|
||||||
|
*
|
||||||
|
* @since 5.3.0
|
||||||
|
*/
|
||||||
|
public boolean isRespectVersionsForSearchIncludes() {
|
||||||
|
return myRespectVersionsForSearchIncludes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should searches with <code>_include</code> respect versioned references, and pull the specific requested version.
|
||||||
|
* This may have performance impacts on heavily loaded systems.
|
||||||
|
*
|
||||||
|
* @since 5.3.0
|
||||||
|
*/
|
||||||
|
public void setRespectVersionsForSearchIncludes(boolean theRespectVersionsForSearchIncludes) {
|
||||||
|
myRespectVersionsForSearchIncludes = theRespectVersionsForSearchIncludes;
|
||||||
|
}
|
||||||
|
|
||||||
private static void validateTreatBaseUrlsAsLocal(String theUrl) {
|
private static void validateTreatBaseUrlsAsLocal(String theUrl) {
|
||||||
Validate.notBlank(theUrl, "Base URL must not be null or empty");
|
Validate.notBlank(theUrl, "Base URL must not be null or empty");
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
||||||
|
|
||||||
/*-
|
/*-
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR Model
|
* HAPI FHIR JPA Model
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||||
* %%
|
* %%
|
||||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
||||||
|
|
||||||
/*-
|
/*-
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR Model
|
* HAPI FHIR JPA Model
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||||
* %%
|
* %%
|
||||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
||||||
|
|
||||||
/*-
|
/*-
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR Model
|
* HAPI FHIR JPA Model
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||||
* %%
|
* %%
|
||||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
||||||
|
|
||||||
/*-
|
/*-
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR Model
|
* HAPI FHIR JPA Model
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||||
* %%
|
* %%
|
||||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
||||||
|
|
||||||
/*-
|
/*-
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR Model
|
* HAPI FHIR JPA Model
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||||
* %%
|
* %%
|
||||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR Model
|
* HAPI FHIR JPA Model
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||||
* %%
|
* %%
|
||||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
||||||
|
|
||||||
/*-
|
/*-
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR Model
|
* HAPI FHIR JPA Model
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||||
* %%
|
* %%
|
||||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR Model
|
* HAPI FHIR JPA Model
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||||
* %%
|
* %%
|
||||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR Model
|
* HAPI FHIR JPA Model
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||||
* %%
|
* %%
|
||||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
||||||
|
|
||||||
/*-
|
/*-
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR Model
|
* HAPI FHIR JPA Model
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||||
* %%
|
* %%
|
||||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR Model
|
* HAPI FHIR JPA Model
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||||
* %%
|
* %%
|
||||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR Model
|
* HAPI FHIR JPA Model
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||||
* %%
|
* %%
|
||||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR Model
|
* HAPI FHIR JPA Model
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||||
* %%
|
* %%
|
||||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR Model
|
* HAPI FHIR JPA Model
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||||
* %%
|
* %%
|
||||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR Model
|
* HAPI FHIR JPA Model
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||||
* %%
|
* %%
|
||||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR Model
|
* HAPI FHIR JPA Model
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||||
* %%
|
* %%
|
||||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR Model
|
* HAPI FHIR JPA Model
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||||
* %%
|
* %%
|
||||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR Model
|
* HAPI FHIR JPA Model
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||||
* %%
|
* %%
|
||||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR Model
|
* HAPI FHIR JPA Model
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||||
* %%
|
* %%
|
||||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR Model
|
* HAPI FHIR JPA Model
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||||
* %%
|
* %%
|
||||||
|
@ -26,6 +26,7 @@ import org.apache.commons.lang3.builder.HashCodeBuilder;
|
||||||
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField;
|
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import javax.persistence.Column;
|
import javax.persistence.Column;
|
||||||
import javax.persistence.Entity;
|
import javax.persistence.Entity;
|
||||||
import javax.persistence.FetchType;
|
import javax.persistence.FetchType;
|
||||||
|
@ -88,12 +89,12 @@ public class ResourceLink extends BaseResourceIndex {
|
||||||
@Column(name = "TARGET_RESOURCE_URL", length = 200, nullable = true)
|
@Column(name = "TARGET_RESOURCE_URL", length = 200, nullable = true)
|
||||||
@FullTextField
|
@FullTextField
|
||||||
private String myTargetResourceUrl;
|
private String myTargetResourceUrl;
|
||||||
|
@Column(name = "TARGET_RESOURCE_VERSION", nullable = true)
|
||||||
|
private Long myTargetResourceVersion;
|
||||||
@FullTextField
|
@FullTextField
|
||||||
@Column(name = "SP_UPDATED", nullable = true) // TODO: make this false after HAPI 2.3
|
@Column(name = "SP_UPDATED", nullable = true) // TODO: make this false after HAPI 2.3
|
||||||
@Temporal(TemporalType.TIMESTAMP)
|
@Temporal(TemporalType.TIMESTAMP)
|
||||||
private Date myUpdated;
|
private Date myUpdated;
|
||||||
|
|
||||||
@Transient
|
@Transient
|
||||||
private transient String myTargetResourceId;
|
private transient String myTargetResourceId;
|
||||||
|
|
||||||
|
@ -101,6 +102,14 @@ public class ResourceLink extends BaseResourceIndex {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Long getTargetResourceVersion() {
|
||||||
|
return myTargetResourceVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTargetResourceVersion(Long theTargetResourceVersion) {
|
||||||
|
myTargetResourceVersion = theTargetResourceVersion;
|
||||||
|
}
|
||||||
|
|
||||||
public String getTargetResourceId() {
|
public String getTargetResourceId() {
|
||||||
if (myTargetResourceId == null && myTargetResource != null) {
|
if (myTargetResourceId == null && myTargetResource != null) {
|
||||||
myTargetResourceId = getTargetResource().getIdDt().getIdPart();
|
myTargetResourceId = getTargetResource().getIdDt().getIdPart();
|
||||||
|
@ -178,10 +187,6 @@ public class ResourceLink extends BaseResourceIndex {
|
||||||
return myTargetResourceUrl;
|
return myTargetResourceUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Long getTargetResourcePid() {
|
|
||||||
return myTargetResourcePid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTargetResourceUrl(IIdType theTargetResourceUrl) {
|
public void setTargetResourceUrl(IIdType theTargetResourceUrl) {
|
||||||
Validate.isTrue(theTargetResourceUrl.hasBaseUrl());
|
Validate.isTrue(theTargetResourceUrl.hasBaseUrl());
|
||||||
Validate.isTrue(theTargetResourceUrl.hasResourceType());
|
Validate.isTrue(theTargetResourceUrl.hasResourceType());
|
||||||
|
@ -199,6 +204,10 @@ public class ResourceLink extends BaseResourceIndex {
|
||||||
myTargetResourceUrl = theTargetResourceUrl.getValue();
|
myTargetResourceUrl = theTargetResourceUrl.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Long getTargetResourcePid() {
|
||||||
|
return myTargetResourcePid;
|
||||||
|
}
|
||||||
|
|
||||||
public void setTargetResourceUrlCanonical(String theTargetResourceUrl) {
|
public void setTargetResourceUrlCanonical(String theTargetResourceUrl) {
|
||||||
Validate.notBlank(theTargetResourceUrl);
|
Validate.notBlank(theTargetResourceUrl);
|
||||||
|
|
||||||
|
@ -279,11 +288,15 @@ public class ResourceLink extends BaseResourceIndex {
|
||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ResourceLink forLocalReference(String theSourcePath, ResourceTable theSourceResource, String theTargetResourceType, Long theTargetResourcePid, String theTargetResourceId, Date theUpdated) {
|
/**
|
||||||
|
* @param theTargetResourceVersion This should only be populated if the reference actually had a version
|
||||||
|
*/
|
||||||
|
public static ResourceLink forLocalReference(String theSourcePath, ResourceTable theSourceResource, String theTargetResourceType, Long theTargetResourcePid, String theTargetResourceId, Date theUpdated, @Nullable Long theTargetResourceVersion) {
|
||||||
ResourceLink retVal = new ResourceLink();
|
ResourceLink retVal = new ResourceLink();
|
||||||
retVal.setSourcePath(theSourcePath);
|
retVal.setSourcePath(theSourcePath);
|
||||||
retVal.setSourceResource(theSourceResource);
|
retVal.setSourceResource(theSourceResource);
|
||||||
retVal.setTargetResource(theTargetResourceType, theTargetResourcePid, theTargetResourceId);
|
retVal.setTargetResource(theTargetResourceType, theTargetResourcePid, theTargetResourceId);
|
||||||
|
retVal.setTargetResourceVersion(theTargetResourceVersion);
|
||||||
retVal.setUpdated(theUpdated);
|
retVal.setUpdated(theUpdated);
|
||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR Model
|
* HAPI FHIR JPA Model
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||||
* %%
|
* %%
|
||||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR Model
|
* HAPI FHIR JPA Model
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||||
* %%
|
* %%
|
||||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
||||||
|
|
||||||
/*-
|
/*-
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR Model
|
* HAPI FHIR JPA Model
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||||
* %%
|
* %%
|
||||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR Model
|
* HAPI FHIR JPA Model
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||||
* %%
|
* %%
|
||||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR Model
|
* HAPI FHIR JPA Model
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||||
* %%
|
* %%
|
||||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.sched;
|
||||||
|
|
||||||
/*-
|
/*-
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR Model
|
* HAPI FHIR JPA Model
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||||
* %%
|
* %%
|
||||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.sched;
|
||||||
|
|
||||||
/*-
|
/*-
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR Model
|
* HAPI FHIR JPA Model
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||||
* %%
|
* %%
|
||||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.sched;
|
||||||
|
|
||||||
/*-
|
/*-
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR Model
|
* HAPI FHIR JPA Model
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||||
* %%
|
* %%
|
||||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.sched;
|
||||||
|
|
||||||
/*-
|
/*-
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR Model
|
* HAPI FHIR JPA Model
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||||
* %%
|
* %%
|
||||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.sched;
|
||||||
|
|
||||||
/*-
|
/*-
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR Model
|
* HAPI FHIR JPA Model
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||||
* %%
|
* %%
|
||||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.search;
|
||||||
|
|
||||||
/*-
|
/*-
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR Model
|
* HAPI FHIR JPA Model
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||||
* %%
|
* %%
|
||||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.search;
|
||||||
|
|
||||||
/*-
|
/*-
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR Model
|
* HAPI FHIR JPA Model
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||||
* %%
|
* %%
|
||||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.search;
|
||||||
|
|
||||||
/*-
|
/*-
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR Model
|
* HAPI FHIR JPA Model
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||||
* %%
|
* %%
|
||||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.search;
|
||||||
|
|
||||||
/*-
|
/*-
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR Model
|
* HAPI FHIR JPA Model
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||||
* %%
|
* %%
|
||||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.util;
|
||||||
|
|
||||||
/*-
|
/*-
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR Model
|
* HAPI FHIR JPA Model
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||||
* %%
|
* %%
|
||||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.util;
|
||||||
|
|
||||||
/*-
|
/*-
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR Model
|
* HAPI FHIR JPA Model
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||||
* %%
|
* %%
|
||||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.util;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR Model
|
* HAPI FHIR JPA Model
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||||
* %%
|
* %%
|
||||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.util;
|
||||||
|
|
||||||
/*-
|
/*-
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR Model
|
* HAPI FHIR JPA Model
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||||
* %%
|
* %%
|
||||||
|
|
|
@ -162,8 +162,9 @@ public class SearchParameterMap implements Serializable {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addInclude(Include theInclude) {
|
public SearchParameterMap addInclude(Include theInclude) {
|
||||||
getIncludes().add(theInclude);
|
getIncludes().add(theInclude);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addLastUpdateParam(StringBuilder b, DateParam date) {
|
private void addLastUpdateParam(StringBuilder b, DateParam date) {
|
||||||
|
|
|
@ -268,8 +268,8 @@ public class SearchParamExtractorService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Class<? extends IBaseResource> type = resourceDefinition.getImplementingClass();
|
Class<? extends IBaseResource> type = resourceDefinition.getImplementingClass();
|
||||||
String id = nextId.getIdPart();
|
String targetId = nextId.getIdPart();
|
||||||
if (StringUtils.isBlank(id)) {
|
if (StringUtils.isBlank(targetId)) {
|
||||||
String msg = "Invalid resource reference found at path[" + path + "] - Does not contain resource ID - " + nextId.getValue();
|
String msg = "Invalid resource reference found at path[" + path + "] - Does not contain resource ID - " + nextId.getValue();
|
||||||
if (theFailOnInvalidReference) {
|
if (theFailOnInvalidReference) {
|
||||||
throw new InvalidRequestException(msg);
|
throw new InvalidRequestException(msg);
|
||||||
|
@ -279,9 +279,11 @@ public class SearchParamExtractorService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ResourcePersistentId resolvedTargetId = theTransactionDetails.getResolvedResourceIds().get(thePathAndRef.getRef().getReferenceElement());
|
IIdType referenceElement = thePathAndRef.getRef().getReferenceElement();
|
||||||
|
ResourcePersistentId resolvedTargetId = theTransactionDetails.getResolvedResourceId(referenceElement);
|
||||||
ResourceLink resourceLink;
|
ResourceLink resourceLink;
|
||||||
|
|
||||||
|
Long targetVersionId = nextId.getVersionIdPartAsLong();
|
||||||
if (resolvedTargetId != null) {
|
if (resolvedTargetId != null) {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -289,7 +291,7 @@ public class SearchParamExtractorService {
|
||||||
* need to resolve it again
|
* need to resolve it again
|
||||||
*/
|
*/
|
||||||
myResourceLinkResolver.validateTypeOrThrowException(type);
|
myResourceLinkResolver.validateTypeOrThrowException(type);
|
||||||
resourceLink = ResourceLink.forLocalReference(thePathAndRef.getPath(), theEntity, typeString, resolvedTargetId.getIdAsLong(), nextId.getIdPart(), transactionDate);
|
resourceLink = ResourceLink.forLocalReference(thePathAndRef.getPath(), theEntity, typeString, resolvedTargetId.getIdAsLong(), targetId, transactionDate, targetVersionId);
|
||||||
|
|
||||||
} else if (theFailOnInvalidReference) {
|
} else if (theFailOnInvalidReference) {
|
||||||
|
|
||||||
|
@ -305,7 +307,7 @@ public class SearchParamExtractorService {
|
||||||
} else {
|
} else {
|
||||||
// Cache the outcome in the current transaction in case there are more references
|
// Cache the outcome in the current transaction in case there are more references
|
||||||
ResourcePersistentId persistentId = new ResourcePersistentId(resourceLink.getTargetResourcePid());
|
ResourcePersistentId persistentId = new ResourcePersistentId(resourceLink.getTargetResourcePid());
|
||||||
theTransactionDetails.addResolvedResourceId(thePathAndRef.getRef().getReferenceElement(), persistentId);
|
theTransactionDetails.addResolvedResourceId(referenceElement, persistentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
@ -317,7 +319,7 @@ public class SearchParamExtractorService {
|
||||||
ResourceTable target;
|
ResourceTable target;
|
||||||
target = new ResourceTable();
|
target = new ResourceTable();
|
||||||
target.setResourceType(typeString);
|
target.setResourceType(typeString);
|
||||||
resourceLink = ResourceLink.forLocalReference(thePathAndRef.getPath(), theEntity, typeString, null, nextId.getIdPart(), transactionDate);
|
resourceLink = ResourceLink.forLocalReference(thePathAndRef.getPath(), theEntity, typeString, null, targetId, transactionDate, targetVersionId);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -346,7 +348,8 @@ public class SearchParamExtractorService {
|
||||||
String targetResourceType = targetResource.getResourceType();
|
String targetResourceType = targetResource.getResourceType();
|
||||||
Long targetResourcePid = targetResource.getResourceId();
|
Long targetResourcePid = targetResource.getResourceId();
|
||||||
String targetResourceIdPart = theNextId.getIdPart();
|
String targetResourceIdPart = theNextId.getIdPart();
|
||||||
return ResourceLink.forLocalReference(nextPathAndRef.getPath(), theEntity, targetResourceType, targetResourcePid, targetResourceIdPart, theUpdateTime);
|
Long targetVersion = theNextId.getVersionIdPartAsLong();
|
||||||
|
return ResourceLink.forLocalReference(nextPathAndRef.getPath(), theEntity, targetResourceType, targetResourcePid, targetResourceIdPart, theUpdateTime, targetVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,7 @@ public class ResourceIndexedSearchParamsTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void matchResourceLinksStringCompareToLong() {
|
public void matchResourceLinksStringCompareToLong() {
|
||||||
ResourceLink link = ResourceLink.forLocalReference("organization", mySource, "Organization", 123L, LONG_ID, new Date());
|
ResourceLink link = ResourceLink.forLocalReference("organization", mySource, "Organization", 123L, LONG_ID, new Date(), null);
|
||||||
myParams.getResourceLinks().add(link);
|
myParams.getResourceLinks().add(link);
|
||||||
|
|
||||||
ReferenceParam referenceParam = getReferenceParam(STRING_ID);
|
ReferenceParam referenceParam = getReferenceParam(STRING_ID);
|
||||||
|
@ -46,7 +46,7 @@ public class ResourceIndexedSearchParamsTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void matchResourceLinksStringCompareToString() {
|
public void matchResourceLinksStringCompareToString() {
|
||||||
ResourceLink link = ResourceLink.forLocalReference("organization", mySource, "Organization", 123L, STRING_ID, new Date());
|
ResourceLink link = ResourceLink.forLocalReference("organization", mySource, "Organization", 123L, STRING_ID, new Date(), null);
|
||||||
myParams.getResourceLinks().add(link);
|
myParams.getResourceLinks().add(link);
|
||||||
|
|
||||||
ReferenceParam referenceParam = getReferenceParam(STRING_ID);
|
ReferenceParam referenceParam = getReferenceParam(STRING_ID);
|
||||||
|
@ -56,7 +56,7 @@ public class ResourceIndexedSearchParamsTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void matchResourceLinksLongCompareToString() {
|
public void matchResourceLinksLongCompareToString() {
|
||||||
ResourceLink link = ResourceLink.forLocalReference("organization", mySource, "Organization", 123L, STRING_ID, new Date());
|
ResourceLink link = ResourceLink.forLocalReference("organization", mySource, "Organization", 123L, STRING_ID, new Date(), null);
|
||||||
myParams.getResourceLinks().add(link);
|
myParams.getResourceLinks().add(link);
|
||||||
|
|
||||||
ReferenceParam referenceParam = getReferenceParam(LONG_ID);
|
ReferenceParam referenceParam = getReferenceParam(LONG_ID);
|
||||||
|
@ -66,7 +66,7 @@ public class ResourceIndexedSearchParamsTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void matchResourceLinksLongCompareToLong() {
|
public void matchResourceLinksLongCompareToLong() {
|
||||||
ResourceLink link = ResourceLink.forLocalReference("organization", mySource, "Organization", 123L, LONG_ID, new Date());
|
ResourceLink link = ResourceLink.forLocalReference("organization", mySource, "Organization", 123L, LONG_ID, new Date(), null);
|
||||||
myParams.getResourceLinks().add(link);
|
myParams.getResourceLinks().add(link);
|
||||||
|
|
||||||
ReferenceParam referenceParam = getReferenceParam(LONG_ID);
|
ReferenceParam referenceParam = getReferenceParam(LONG_ID);
|
||||||
|
|
|
@ -34,10 +34,20 @@ import java.util.Optional;
|
||||||
public class ResourcePersistentId {
|
public class ResourcePersistentId {
|
||||||
|
|
||||||
private Object myId;
|
private Object myId;
|
||||||
|
private Long myVersion;
|
||||||
|
|
||||||
public ResourcePersistentId(Object theId) {
|
public ResourcePersistentId(Object theId) {
|
||||||
|
this(theId, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param theVersion This should only be populated if a specific version is needed. If you want the current version,
|
||||||
|
* leave this as <code>null</code>
|
||||||
|
*/
|
||||||
|
public ResourcePersistentId(Object theId, Long theVersion) {
|
||||||
assert !(theId instanceof Optional);
|
assert !(theId instanceof Optional);
|
||||||
myId = theId;
|
myId = theId;
|
||||||
|
myVersion = theVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -47,12 +57,18 @@ public class ResourcePersistentId {
|
||||||
}
|
}
|
||||||
ResourcePersistentId that = (ResourcePersistentId) theO;
|
ResourcePersistentId that = (ResourcePersistentId) theO;
|
||||||
|
|
||||||
return ObjectUtil.equals(myId, that.myId);
|
boolean retVal = ObjectUtil.equals(myId, that.myId);
|
||||||
|
retVal &= ObjectUtil.equals(myVersion, that.myVersion);
|
||||||
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return myId.hashCode();
|
int retVal = myId.hashCode();
|
||||||
|
if (myVersion != null) {
|
||||||
|
retVal += myVersion.hashCode();
|
||||||
|
}
|
||||||
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Object getId() {
|
public Object getId() {
|
||||||
|
@ -72,6 +88,18 @@ public class ResourcePersistentId {
|
||||||
return myId.toString();
|
return myId.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Long getVersion() {
|
||||||
|
return myVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param theVersion This should only be populated if a specific version is needed. If you want the current version,
|
||||||
|
* leave this as <code>null</code>
|
||||||
|
*/
|
||||||
|
public void setVersion(Long theVersion) {
|
||||||
|
myVersion = theVersion;
|
||||||
|
}
|
||||||
|
|
||||||
public static List<Long> toLongList(Collection<ResourcePersistentId> thePids) {
|
public static List<Long> toLongList(Collection<ResourcePersistentId> thePids) {
|
||||||
List<Long> retVal = new ArrayList<>(thePids.size());
|
List<Long> retVal = new ArrayList<>(thePids.size());
|
||||||
for (ResourcePersistentId next : thePids) {
|
for (ResourcePersistentId next : thePids) {
|
||||||
|
|
|
@ -27,6 +27,7 @@ import com.google.common.collect.ListMultimap;
|
||||||
import org.apache.commons.lang3.Validate;
|
import org.apache.commons.lang3.Validate;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
|
@ -48,7 +49,7 @@ import java.util.function.Supplier;
|
||||||
public class TransactionDetails {
|
public class TransactionDetails {
|
||||||
|
|
||||||
private final Date myTransactionDate;
|
private final Date myTransactionDate;
|
||||||
private Map<IIdType, ResourcePersistentId> myResolvedResourceIds = Collections.emptyMap();
|
private Map<String, ResourcePersistentId> myResolvedResourceIds = Collections.emptyMap();
|
||||||
private Map<String, Object> myUserData;
|
private Map<String, Object> myUserData;
|
||||||
private ListMultimap<Pointcut, HookParams> myDeferredInterceptorBroadcasts;
|
private ListMultimap<Pointcut, HookParams> myDeferredInterceptorBroadcasts;
|
||||||
private EnumSet<Pointcut> myDeferredInterceptorBroadcastPointcuts;
|
private EnumSet<Pointcut> myDeferredInterceptorBroadcastPointcuts;
|
||||||
|
@ -72,8 +73,10 @@ public class TransactionDetails {
|
||||||
* "<code>Observation/123</code>") and a storage ID for that resource. Resources should only be placed within
|
* "<code>Observation/123</code>") and a storage ID for that resource. Resources should only be placed within
|
||||||
* the TransactionDetails if they are known to exist and be valid targets for other resources to link to.
|
* the TransactionDetails if they are known to exist and be valid targets for other resources to link to.
|
||||||
*/
|
*/
|
||||||
public Map<IIdType, ResourcePersistentId> getResolvedResourceIds() {
|
@Nullable
|
||||||
return myResolvedResourceIds;
|
public ResourcePersistentId getResolvedResourceId(IIdType theId) {
|
||||||
|
String idValue = theId.toVersionless().getValue();
|
||||||
|
return myResolvedResourceIds.get(idValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -88,7 +91,7 @@ public class TransactionDetails {
|
||||||
if (myResolvedResourceIds.isEmpty()) {
|
if (myResolvedResourceIds.isEmpty()) {
|
||||||
myResolvedResourceIds = new HashMap<>();
|
myResolvedResourceIds = new HashMap<>();
|
||||||
}
|
}
|
||||||
myResolvedResourceIds.put(theResourceId, thePersistentId);
|
myResolvedResourceIds.put(theResourceId.toVersionless().getValue(), thePersistentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -111,10 +114,11 @@ public class TransactionDetails {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets an arbitraty object that will last the lifetime of the current transaction
|
* Gets an arbitrary object that will last the lifetime of the current transaction
|
||||||
*
|
*
|
||||||
* @see #putUserData(String, Object)
|
* @see #putUserData(String, Object)
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
public <T> T getUserData(String theKey) {
|
public <T> T getUserData(String theKey) {
|
||||||
if (myUserData != null) {
|
if (myUserData != null) {
|
||||||
return (T) myUserData.get(theKey);
|
return (T) myUserData.get(theKey);
|
||||||
|
@ -126,6 +130,7 @@ public class TransactionDetails {
|
||||||
* Fetches the existing value in the user data map, or uses {@literal theSupplier} to create a new object and
|
* Fetches the existing value in the user data map, or uses {@literal theSupplier} to create a new object and
|
||||||
* puts that in the map, and returns it
|
* puts that in the map, and returns it
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
public <T> T getOrCreateUserData(String theKey, Supplier<T> theSupplier) {
|
public <T> T getOrCreateUserData(String theKey, Supplier<T> theSupplier) {
|
||||||
T retVal = (T) getUserData(theKey);
|
T retVal = (T) getUserData(theKey);
|
||||||
if (retVal == null) {
|
if (retVal == null) {
|
||||||
|
|
|
@ -2124,7 +2124,7 @@ public class GenericClientR4Test extends BaseGenericClientR4Test {
|
||||||
});
|
});
|
||||||
|
|
||||||
BundleBuilder builder = new BundleBuilder(ourCtx);
|
BundleBuilder builder = new BundleBuilder(ourCtx);
|
||||||
builder.addCreateEntry(new Patient().setActive(true));
|
builder.addTransactionCreateEntry(new Patient().setActive(true));
|
||||||
|
|
||||||
IBaseBundle outcome = client.transaction().withBundle(builder.getBundle()).execute();
|
IBaseBundle outcome = client.transaction().withBundle(builder.getBundle()).execute();
|
||||||
assertNull(outcome);
|
assertNull(outcome);
|
||||||
|
|
|
@ -143,7 +143,7 @@ public class BundleBuilderTest {
|
||||||
|
|
||||||
Patient patient = new Patient();
|
Patient patient = new Patient();
|
||||||
patient.setActive(true);
|
patient.setActive(true);
|
||||||
builder.addCreateEntry(patient);
|
builder.addTransactionCreateEntry(patient);
|
||||||
|
|
||||||
Bundle bundle = (Bundle) builder.getBundle();
|
Bundle bundle = (Bundle) builder.getBundle();
|
||||||
ourLog.info("Bundle:\n{}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(bundle));
|
ourLog.info("Bundle:\n{}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(bundle));
|
||||||
|
@ -162,7 +162,7 @@ public class BundleBuilderTest {
|
||||||
|
|
||||||
Patient patient = new Patient();
|
Patient patient = new Patient();
|
||||||
patient.setActive(true);
|
patient.setActive(true);
|
||||||
builder.addCreateEntry(patient).conditional("Patient?active=true");
|
builder.addTransactionCreateEntry(patient).conditional("Patient?active=true");
|
||||||
|
|
||||||
Bundle bundle = (Bundle) builder.getBundle();
|
Bundle bundle = (Bundle) builder.getBundle();
|
||||||
ourLog.info("Bundle:\n{}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(bundle));
|
ourLog.info("Bundle:\n{}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(bundle));
|
||||||
|
|
3
pom.xml
3
pom.xml
|
@ -738,8 +738,7 @@
|
||||||
<commons_lang3_version>3.9</commons_lang3_version>
|
<commons_lang3_version>3.9</commons_lang3_version>
|
||||||
<derby_version>10.14.2.0</derby_version>
|
<derby_version>10.14.2.0</derby_version>
|
||||||
<!--<derby_version>10.15.1.3</derby_version>-->
|
<!--<derby_version>10.15.1.3</derby_version>-->
|
||||||
<error_prone_annotations_version>2.3.4</error_prone_annotations_version>
|
<error_prone_core_version>2.5.1</error_prone_core_version>
|
||||||
<error_prone_core_version>2.3.4</error_prone_core_version>
|
|
||||||
<nullaway_version>0.7.9</nullaway_version>
|
<nullaway_version>0.7.9</nullaway_version>
|
||||||
<guava_version>30.1-jre</guava_version>
|
<guava_version>30.1-jre</guava_version>
|
||||||
<gson_version>2.8.5</gson_version>
|
<gson_version>2.8.5</gson_version>
|
||||||
|
|
Loading…
Reference in New Issue