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;
|
||||
|
||||
/*-
|
||||
* #%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 java.util.List;
|
||||
|
||||
|
|
|
@ -173,7 +173,7 @@ public class BundleBuilder {
|
|||
*
|
||||
* @param theResource The resource to create
|
||||
*/
|
||||
public CreateBuilder addCreateEntry(IBaseResource theResource) {
|
||||
public CreateBuilder addTransactionCreateEntry(IBaseResource theResource) {
|
||||
setBundleField("type", "transaction");
|
||||
|
||||
IBase request = addEntryAndReturnRequest(theResource);
|
||||
|
@ -338,7 +338,6 @@ public class BundleBuilder {
|
|||
setBundleField("type", theType);
|
||||
}
|
||||
|
||||
|
||||
public static class UpdateBuilder {
|
||||
|
||||
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.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.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}
|
||||
|
|
|
@ -84,7 +84,7 @@ public class BundleBuilderExamples {
|
|||
patient.setActive(true);
|
||||
|
||||
// Add the patient as a create (aka POST) to the Bundle
|
||||
builder.addCreateEntry(patient);
|
||||
builder.addTransactionCreateEntry(patient);
|
||||
|
||||
// Execute the transaction
|
||||
IBaseBundle outcome = myFhirClient.transaction().withBundle(builder.getBundle()).execute();
|
||||
|
@ -102,7 +102,7 @@ public class BundleBuilderExamples {
|
|||
patient.addIdentifier().setSystem("http://foo").setValue("bar");
|
||||
|
||||
// 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
|
||||
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);
|
||||
|
||||
// /**
|
||||
// * Invoke the everything operation
|
||||
// */
|
||||
// IBundleProvider everything(IIdType theId);
|
||||
/**
|
||||
* Returns the current version ID for the given resource
|
||||
*/
|
||||
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
|
||||
*/
|
||||
public void setNop(boolean theNop) {
|
||||
public DaoMethodOutcome setNop(boolean theNop) {
|
||||
myNop = theNop;
|
||||
return this;
|
||||
}
|
||||
|
||||
public IBasePersistedResource getEntity() {
|
||||
|
|
|
@ -259,6 +259,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
StopWatch w = new StopWatch();
|
||||
|
||||
preProcessResourceForStorage(theResource);
|
||||
preProcessResourceForStorage(theResource, theRequest, theTransactionDetails, thePerformIndexing);
|
||||
|
||||
ResourceTable entity = new ResourceTable();
|
||||
entity.setResourceType(toResourceName(theResource));
|
||||
|
@ -274,7 +275,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
entity = myEntityManager.find(ResourceTable.class, pid.getId());
|
||||
IBaseResource resource = toResource(entity, false);
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public String getCurrentVersionId(IIdType theReferenceElement) {
|
||||
return Long.toString(readEntity(theReferenceElement.toVersionless(), null).getVersion());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
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;
|
||||
|
||||
preProcessResourceForStorage(resource);
|
||||
preProcessResourceForStorage(theResource, theRequest, theTransactionDetails, thePerformIndexing);
|
||||
|
||||
final ResourceTable entity;
|
||||
|
||||
|
@ -1529,6 +1537,9 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
resource.setId(entity.getIdDt().getValue());
|
||||
DaoMethodOutcome outcome = toMethodOutcome(theRequest, entity, resource).setCreated(wasDeleted);
|
||||
outcome.setPreviousResource(oldResource);
|
||||
if (!outcome.isNop()) {
|
||||
outcome.setId(outcome.getId().withVersion(Long.toString(outcome.getId().getVersionIdPartAsLong() + 1)));
|
||||
}
|
||||
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.Pointcut;
|
||||
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.model.cross.IBasePersistedResource;
|
||||
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||
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 org.hl7.fhir.instance.model.api.IBaseBundle;
|
||||
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.IIdType;
|
||||
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.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
@ -67,6 +69,7 @@ import javax.annotation.Nullable;
|
|||
import javax.validation.constraints.NotNull;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
@ -77,30 +80,85 @@ import static org.apache.commons.lang3.StringUtils.defaultString;
|
|||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
public abstract class BaseStorageDao {
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(BaseStorageDao.class);
|
||||
@Autowired
|
||||
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
|
||||
*
|
||||
* @param theResource The resource that is about to be stored
|
||||
* @deprecated Use {@link #preProcessResourceForStorage(IBaseResource, RequestDetails, TransactionDetails, boolean)} instead
|
||||
*/
|
||||
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);
|
||||
if (getResourceName() != null && !getResourceName().equals(type)) {
|
||||
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().isIdPartValid()) {
|
||||
throw new InvalidRequestException(getContext().getLocalizer().getMessageSanitized(BaseHapiFhirResourceDao.class, "failedToCreateWithInvalidId", theResource.getIdElement().getIdPart()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Replace absolute references with relative ones if configured to do so
|
||||
*/
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
private void replaceAbsoluteReferencesWithRelative(IBaseResource theResource) {
|
||||
if (getConfig().getTreatBaseUrlsAsLocal().isEmpty() == false) {
|
||||
FhirTerser t = getContext().newTerser();
|
||||
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) {
|
||||
|
@ -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.delete.DeleteConflictService;
|
||||
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.search.StorageProcessingMessage;
|
||||
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.IBaseOperationOutcome;
|
||||
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.IIdType;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
|
@ -130,10 +132,12 @@ public abstract class BaseTransactionProcessor {
|
|||
private HapiTransactionService myHapiTransactionService;
|
||||
@Autowired
|
||||
private DaoConfig myDaoConfig;
|
||||
@Autowired
|
||||
private ModelConfig myModelConfig;
|
||||
|
||||
@PostConstruct
|
||||
public void start() {
|
||||
|
||||
ourLog.trace("Starting transaction processor");
|
||||
}
|
||||
|
||||
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,
|
||||
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();
|
||||
if (newId.equals(resourceId) == false) {
|
||||
idSubstitutions.put(resourceId, newId);
|
||||
|
@ -900,20 +904,32 @@ public abstract class BaseTransactionProcessor {
|
|||
}
|
||||
|
||||
// References
|
||||
Set<IBaseReference> referencesToVersion = BaseStorageDao.extractReferencesToAutoVersion(myContext, myModelConfig, nextResource);
|
||||
List<ResourceReferenceInfo> allRefs = terser.getAllResourceReferences(nextResource);
|
||||
for (ResourceReferenceInfo nextRef : allRefs) {
|
||||
IIdType nextId = nextRef.getResourceReference().getReferenceElement();
|
||||
IBaseReference resourceReference = nextRef.getResourceReference();
|
||||
IIdType nextId = resourceReference.getReferenceElement();
|
||||
if (!nextId.hasIdPart()) {
|
||||
continue;
|
||||
}
|
||||
if (theIdSubstitutions.containsKey(nextId)) {
|
||||
IIdType newId = theIdSubstitutions.get(nextId);
|
||||
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:")) {
|
||||
throw new InvalidRequestException("Unable to satisfy placeholder ID " + nextId.getValue() + " found in element named '" + nextRef.getName() + "' within resource of type: " + nextResource.getIdElement().getResourceType());
|
||||
} 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)) {
|
||||
IIdType newId = theIdSubstitutions.get(nextUriString);
|
||||
ourLog.debug(" * Replacing resource ref {} with {}", nextUriString, newId);
|
||||
nextRef.setValueAsString(newId.getValue());
|
||||
nextRef.setValueAsString(newId.toVersionless().getValue());
|
||||
} else {
|
||||
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.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 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> {
|
||||
|
||||
@Override
|
||||
protected void preProcessResourceForStorage(IBaseResource theResource) {
|
||||
super.preProcessResourceForStorage(theResource);
|
||||
protected void preProcessResourceForStorage(IBaseResource theResource, RequestDetails theRequestDetails, TransactionDetails theTransactionDetails, boolean thePerformIndexing) {
|
||||
super.preProcessResourceForStorage(theResource, theRequestDetails, theTransactionDetails, thePerformIndexing);
|
||||
|
||||
for (Entry next : ((Bundle)theResource).getEntry()) {
|
||||
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.model.RequestPartitionId;
|
||||
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.IFhirResourceDao;
|
||||
import ca.uhn.fhir.jpa.config.HapiFhirLocalContainerEntityManagerFactoryBean;
|
||||
import ca.uhn.fhir.jpa.config.HibernatePropertiesProvider;
|
||||
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.interceptor.JpaPreResourceAccessDetails;
|
||||
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.ResourceTag;
|
||||
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.hl7.fhir.instance.model.api.IAnyResource;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.r4.model.IdType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
@ -147,6 +152,8 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
@Autowired
|
||||
private DaoConfig myDaoConfig;
|
||||
@Autowired
|
||||
private DaoRegistry myDaoRegistry;
|
||||
@Autowired
|
||||
private IResourceSearchViewDao myResourceSearchViewDao;
|
||||
@Autowired
|
||||
private FhirContext myContext;
|
||||
|
@ -174,8 +181,11 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
private SqlObjectFactory mySqlBuilderFactory;
|
||||
@Autowired
|
||||
private HibernatePropertiesProvider myDialectProvider;
|
||||
|
||||
@Autowired
|
||||
private ModelConfig myModelConfig;
|
||||
|
||||
private boolean hasNextIteratorQuery = false;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
|
@ -428,7 +438,7 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
//-- exclude the pids already in the previous iterator
|
||||
if (hasNextIteratorQuery)
|
||||
sqlBuilder.excludeResourceIdsPredicate(myPidSet);
|
||||
|
||||
|
||||
/*
|
||||
* Sort
|
||||
*
|
||||
|
@ -532,23 +542,23 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
break;
|
||||
case QUANTITY:
|
||||
theQueryStack.addSortOnQuantity(myResourceName, theSort.getParamName(), ascending);
|
||||
break;
|
||||
break;
|
||||
case COMPOSITE:
|
||||
List<RuntimeSearchParam> compositList = param.getCompositeOf();
|
||||
if (compositList == null) {
|
||||
throw new InvalidRequestException("The composite _sort parameter " + theSort.getParamName() + " is not defined by the resource " + myResourceName);
|
||||
}
|
||||
if (compositList.size() != 2) {
|
||||
throw new InvalidRequestException("The composite _sort parameter " + theSort.getParamName()
|
||||
+ " must have 2 composite types declared in parameter annotation, found "
|
||||
+ compositList.size());
|
||||
throw new InvalidRequestException("The composite _sort parameter " + theSort.getParamName()
|
||||
+ " must have 2 composite types declared in parameter annotation, found "
|
||||
+ compositList.size());
|
||||
}
|
||||
RuntimeSearchParam left = compositList.get(0);
|
||||
RuntimeSearchParam right = compositList.get(1);
|
||||
|
||||
|
||||
createCompositeSort(theQueryStack, myResourceName, left.getParamType(), left.getName(), ascending);
|
||||
createCompositeSort(theQueryStack, myResourceName, right.getParamType(), right.getName(), ascending);
|
||||
|
||||
|
||||
break;
|
||||
case SPECIAL:
|
||||
case HAS:
|
||||
|
@ -562,24 +572,30 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
createSort(theQueryStack, theSort.getChain());
|
||||
|
||||
}
|
||||
|
||||
|
||||
private void createCompositeSort(QueryStack theQueryStack, String theResourceName, RestSearchParameterTypeEnum theParamType, String theParamName, boolean theAscending) {
|
||||
|
||||
switch (theParamType) {
|
||||
case STRING:
|
||||
theQueryStack.addSortOnString(myResourceName, theParamName, theAscending);
|
||||
break;
|
||||
case DATE:
|
||||
theQueryStack.addSortOnDate(myResourceName, theParamName, theAscending);
|
||||
break;
|
||||
case TOKEN:
|
||||
theQueryStack.addSortOnToken(myResourceName, theParamName, theAscending);
|
||||
break;
|
||||
case QUANTITY:
|
||||
theQueryStack.addSortOnQuantity(myResourceName, theParamName, theAscending);
|
||||
break;
|
||||
default:
|
||||
throw new InvalidRequestException("Don't know how to handle composite parameter with type of " + theParamType + " on _sort="+ theParamName);
|
||||
case STRING:
|
||||
theQueryStack.addSortOnString(myResourceName, theParamName, theAscending);
|
||||
break;
|
||||
case DATE:
|
||||
theQueryStack.addSortOnDate(myResourceName, theParamName, theAscending);
|
||||
break;
|
||||
case TOKEN:
|
||||
theQueryStack.addSortOnToken(myResourceName, theParamName, theAscending);
|
||||
break;
|
||||
case QUANTITY:
|
||||
theQueryStack.addSortOnQuantity(myResourceName, theParamName, theAscending);
|
||||
break;
|
||||
case NUMBER:
|
||||
case REFERENCE:
|
||||
case COMPOSITE:
|
||||
case URI:
|
||||
case HAS:
|
||||
case SPECIAL:
|
||||
default:
|
||||
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,
|
||||
Map<ResourcePersistentId, Integer> thePosition) {
|
||||
|
||||
List<Long> myLongPersistentIds;
|
||||
if (thePids.size() < getMaximumPageSize()) {
|
||||
myLongPersistentIds = normalizeIdListForLastNInClause(ResourcePersistentId.toLongList(thePids));
|
||||
} else {
|
||||
myLongPersistentIds = ResourcePersistentId.toLongList(thePids);
|
||||
Map<Long, Long> resourcePidToVersion = null;
|
||||
for (ResourcePersistentId next : thePids) {
|
||||
if (next.getVersion() != null && myModelConfig.isRespectVersionsForSearchIncludes()) {
|
||||
if (resourcePidToVersion == null) {
|
||||
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
|
||||
Collection<ResourceSearchView> resourceSearchViewList = myResourceSearchViewDao.findByResourceIds(myLongPersistentIds);
|
||||
Collection<ResourceSearchView> resourceSearchViewList = myResourceSearchViewDao.findByResourceIds(versionlessPids);
|
||||
|
||||
//-- 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 (ResourceSearchView next : resourceSearchViewList) {
|
||||
for (IBaseResourceEntity next : resourceSearchViewList) {
|
||||
if (next.getDeleted() != null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
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) {
|
||||
ourLog.warn("Unable to find resource {}/{}/_history/{} in database", next.getResourceType(), next.getIdDt().getIdPart(), next.getVersion());
|
||||
continue;
|
||||
}
|
||||
|
||||
Integer index = thePosition.get(resourceId);
|
||||
if (index == null) {
|
||||
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());
|
||||
|
||||
//-- find all resource has tags
|
||||
for (ResourceSearchView resource : theResourceSearchViewList) {
|
||||
for (IBaseResourceEntity resource : theResourceSearchViewList) {
|
||||
if (resource.isHasTags())
|
||||
idList.add(resource.getId());
|
||||
}
|
||||
|
||||
Map<ResourcePersistentId, Collection<ResourceTag>> tagMap = new HashMap<>();
|
||||
Map<Long, Collection<ResourceTag>> tagMap = new HashMap<>();
|
||||
|
||||
//-- no tags
|
||||
if (idList.size() == 0)
|
||||
|
@ -664,11 +707,11 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
for (ResourceTag tag : tagList) {
|
||||
|
||||
resourceId = new ResourcePersistentId(tag.getResourceId());
|
||||
tagCol = tagMap.get(resourceId);
|
||||
tagCol = tagMap.get(resourceId.getIdAsLong());
|
||||
if (tagCol == null) {
|
||||
tagCol = new ArrayList<>();
|
||||
tagCol.add(tag);
|
||||
tagMap.put(resourceId, tagCol);
|
||||
tagMap.put(resourceId.getIdAsLong(), tagCol);
|
||||
} else {
|
||||
tagCol.add(tag);
|
||||
}
|
||||
|
@ -712,8 +755,12 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
if (theRevIncludes == null || theRevIncludes.isEmpty()) {
|
||||
return new HashSet<>();
|
||||
}
|
||||
String searchFieldName = theReverseMode ? "myTargetResourcePid" : "mySourceResourcePid";
|
||||
String findFieldName = theReverseMode ? "mySourceResourcePid" : "myTargetResourcePid";
|
||||
String searchPidFieldName = theReverseMode ? "myTargetResourcePid" : "mySourceResourcePid";
|
||||
String findPidFieldName = theReverseMode ? "mySourceResourcePid" : "myTargetResourcePid";
|
||||
String findVersionFieldName = null;
|
||||
if (!theReverseMode && myModelConfig.isRespectVersionsForSearchIncludes()) {
|
||||
findVersionFieldName = "myTargetResourceVersion";
|
||||
}
|
||||
|
||||
List<ResourcePersistentId> nextRoundMatches = new ArrayList<>(theMatches);
|
||||
HashSet<ResourcePersistentId> allAdded = new HashSet<>();
|
||||
|
@ -737,24 +784,37 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
|
||||
boolean matchAll = "*".equals(nextInclude.getValue());
|
||||
if (matchAll) {
|
||||
String sql;
|
||||
sql = "SELECT r." + findFieldName + " FROM ResourceLink r WHERE r." + searchFieldName + " IN (:target_pids) ";
|
||||
StringBuilder sqlBuilder = new StringBuilder();
|
||||
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());
|
||||
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));
|
||||
List<Long> results = q.getResultList();
|
||||
for (Long resourceLink : results) {
|
||||
if (resourceLink == null) {
|
||||
List<?> results = q.getResultList();
|
||||
for (Object nextRow : results) {
|
||||
if (nextRow == null) {
|
||||
// This can happen if there are outgoing references which are canonical or point to
|
||||
// other servers
|
||||
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 {
|
||||
pidsToInclude.add(new ResourcePersistentId(resourceLink));
|
||||
resourceLink = (Long)nextRow;
|
||||
}
|
||||
|
||||
pidsToInclude.add(new ResourcePersistentId(resourceLink, version));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -789,17 +849,22 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
String sql;
|
||||
|
||||
boolean haveTargetTypesDefinedByParam = param.hasTargets();
|
||||
String fieldsToLoad = "r." + findPidFieldName;
|
||||
if (findVersionFieldName != null) {
|
||||
fieldsToLoad += ", r." + findVersionFieldName;
|
||||
}
|
||||
|
||||
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) {
|
||||
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 {
|
||||
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());
|
||||
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("target_pids", ResourcePersistentId.toLongList(nextPartition));
|
||||
if (targetResourceType != null) {
|
||||
|
@ -807,10 +872,18 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
} else if (haveTargetTypesDefinedByParam) {
|
||||
q.setParameter("target_resource_types", param.getTargets());
|
||||
}
|
||||
List<Long> results = q.getResultList();
|
||||
for (Long resourceLink : results) {
|
||||
List<?> results = q.getResultList();
|
||||
for (Object resourceLink : results) {
|
||||
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 myHavePerfTraceFoundIdHook;
|
||||
private final SortSpec mySort;
|
||||
private final Integer myOffset;
|
||||
private boolean myFirst = true;
|
||||
private IncludesIterator myIncludesIterator;
|
||||
private ResourcePersistentId myNext;
|
||||
|
@ -1059,8 +1133,6 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
private boolean myStillNeedToFetchIncludes;
|
||||
private int mySkipCount = 0;
|
||||
private int myNonSkipCount = 0;
|
||||
private final Integer myOffset;
|
||||
|
||||
private ArrayList<SearchQueryExecutor> myQueryList = new ArrayList<>();
|
||||
|
||||
private QueryIterator(SearchRuntimeDetails theSearchRuntimeDetails, RequestDetails theRequest) {
|
||||
|
@ -1249,7 +1321,7 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
if (myNext == null) {
|
||||
fetchNext();
|
||||
}
|
||||
return !NO_MORE.equals(myNext);
|
||||
return !NO_MORE.equals(myNext);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -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.dao.r4.TransactionProcessorVersionAdapterR4;
|
||||
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 org.hibernate.Session;
|
||||
import org.hibernate.internal.SessionImpl;
|
||||
|
@ -50,6 +51,9 @@ public class TransactionProcessorTest {
|
|||
private MatchResourceUrlService myMatchResourceUrlService;
|
||||
@MockBean
|
||||
private HapiTransactionService myHapiTransactionService;
|
||||
@MockBean
|
||||
private ModelConfig myModelConfig;
|
||||
|
||||
@MockBean(answer = Answers.RETURNS_DEEP_STUBS)
|
||||
private SessionImpl mySession;
|
||||
|
||||
|
|
|
@ -389,7 +389,7 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
|
|||
myBundleDao.create(bundle, mySrd);
|
||||
fail();
|
||||
} 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();
|
||||
|
@ -399,7 +399,7 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
|
|||
myBundleDao.create(bundle, mySrd);
|
||||
fail();
|
||||
} 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();
|
||||
|
|
|
@ -4,8 +4,6 @@ import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
|
|||
import ca.uhn.fhir.jpa.rp.dstu3.PatientResourceProvider;
|
||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||
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 javax.servlet.ServletConfig;
|
||||
|
|
|
@ -526,7 +526,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
|
|||
myBundleDao.create(bundle, mySrd);
|
||||
fail();
|
||||
} 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();
|
||||
|
@ -536,7 +536,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
|
|||
myBundleDao.create(bundle, mySrd);
|
||||
fail();
|
||||
} 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();
|
||||
|
|
|
@ -2985,7 +2985,7 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest {
|
|||
dr.addPresentedForm(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);
|
||||
|
||||
Bundle transactionBundle = new Bundle();
|
||||
|
|
|
@ -842,7 +842,7 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test {
|
|||
myBundleDao.create(bundle, mySrd);
|
||||
fail();
|
||||
} 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();
|
||||
|
@ -852,7 +852,7 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test {
|
|||
myBundleDao.create(bundle, mySrd);
|
||||
fail();
|
||||
} 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();
|
||||
|
|
|
@ -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();
|
||||
fail();
|
||||
} 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();
|
||||
fail();
|
||||
} 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();
|
||||
fail();
|
||||
} 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");
|
||||
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() {
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.api;
|
|||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.config;
|
|||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.cross;
|
|||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.cross;
|
|||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.cross;
|
|||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
@ -20,20 +20,25 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.context.ParserOptions;
|
||||
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.hl7.fhir.dstu2.model.Subscription;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
import org.hl7.fhir.r4.model.DateTimeType;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
|
||||
|
||||
// TODO: move this to ca.uhn.fhir.jpa.model.config
|
||||
public class ModelConfig {
|
||||
|
||||
|
@ -90,6 +95,9 @@ public class ModelConfig {
|
|||
private IPrimitiveType<Date> myPeriodIndexEndOfTime;
|
||||
|
||||
private NormalizedQuantitySearchLevel myNormalizedQuantitySearchLevel;
|
||||
private Set<String> myAutoVersionReferenceAtPaths = Collections.emptySet();
|
||||
private Map<String, Set<String>> myTypeToAutoVersionReferenceAtPaths = Collections.emptyMap();
|
||||
private boolean myRespectVersionsForSearchIncludes;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
|
@ -268,7 +276,7 @@ public class ModelConfig {
|
|||
}
|
||||
|
||||
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("/")) {
|
||||
next = next.substring(0, next.length() - 1);
|
||||
}
|
||||
|
@ -618,6 +626,110 @@ public class ModelConfig {
|
|||
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) {
|
||||
Validate.notBlank(theUrl, "Base URL must not be null or empty");
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* 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.hl7.fhir.instance.model.api.IIdType;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.FetchType;
|
||||
|
@ -88,12 +89,12 @@ public class ResourceLink extends BaseResourceIndex {
|
|||
@Column(name = "TARGET_RESOURCE_URL", length = 200, nullable = true)
|
||||
@FullTextField
|
||||
private String myTargetResourceUrl;
|
||||
|
||||
@Column(name = "TARGET_RESOURCE_VERSION", nullable = true)
|
||||
private Long myTargetResourceVersion;
|
||||
@FullTextField
|
||||
@Column(name = "SP_UPDATED", nullable = true) // TODO: make this false after HAPI 2.3
|
||||
@Temporal(TemporalType.TIMESTAMP)
|
||||
private Date myUpdated;
|
||||
|
||||
@Transient
|
||||
private transient String myTargetResourceId;
|
||||
|
||||
|
@ -101,6 +102,14 @@ public class ResourceLink extends BaseResourceIndex {
|
|||
super();
|
||||
}
|
||||
|
||||
public Long getTargetResourceVersion() {
|
||||
return myTargetResourceVersion;
|
||||
}
|
||||
|
||||
public void setTargetResourceVersion(Long theTargetResourceVersion) {
|
||||
myTargetResourceVersion = theTargetResourceVersion;
|
||||
}
|
||||
|
||||
public String getTargetResourceId() {
|
||||
if (myTargetResourceId == null && myTargetResource != null) {
|
||||
myTargetResourceId = getTargetResource().getIdDt().getIdPart();
|
||||
|
@ -178,10 +187,6 @@ public class ResourceLink extends BaseResourceIndex {
|
|||
return myTargetResourceUrl;
|
||||
}
|
||||
|
||||
public Long getTargetResourcePid() {
|
||||
return myTargetResourcePid;
|
||||
}
|
||||
|
||||
public void setTargetResourceUrl(IIdType theTargetResourceUrl) {
|
||||
Validate.isTrue(theTargetResourceUrl.hasBaseUrl());
|
||||
Validate.isTrue(theTargetResourceUrl.hasResourceType());
|
||||
|
@ -199,6 +204,10 @@ public class ResourceLink extends BaseResourceIndex {
|
|||
myTargetResourceUrl = theTargetResourceUrl.getValue();
|
||||
}
|
||||
|
||||
public Long getTargetResourcePid() {
|
||||
return myTargetResourcePid;
|
||||
}
|
||||
|
||||
public void setTargetResourceUrlCanonical(String theTargetResourceUrl) {
|
||||
Validate.notBlank(theTargetResourceUrl);
|
||||
|
||||
|
@ -279,11 +288,15 @@ public class ResourceLink extends BaseResourceIndex {
|
|||
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();
|
||||
retVal.setSourcePath(theSourcePath);
|
||||
retVal.setSourceResource(theSourceResource);
|
||||
retVal.setTargetResource(theTargetResourceType, theTargetResourcePid, theTargetResourceId);
|
||||
retVal.setTargetResourceVersion(theTargetResourceVersion);
|
||||
retVal.setUpdated(theUpdated);
|
||||
return retVal;
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.sched;
|
|||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.sched;
|
|||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.sched;
|
|||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.sched;
|
|||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.sched;
|
|||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.search;
|
|||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.search;
|
|||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.search;
|
|||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.search;
|
|||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.util;
|
|||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.util;
|
|||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.model.util;
|
|||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.util;
|
|||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Model
|
||||
* HAPI FHIR JPA Model
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
|
|
|
@ -162,8 +162,9 @@ public class SearchParameterMap implements Serializable {
|
|||
return this;
|
||||
}
|
||||
|
||||
public void addInclude(Include theInclude) {
|
||||
public SearchParameterMap addInclude(Include theInclude) {
|
||||
getIncludes().add(theInclude);
|
||||
return this;
|
||||
}
|
||||
|
||||
private void addLastUpdateParam(StringBuilder b, DateParam date) {
|
||||
|
|
|
@ -268,8 +268,8 @@ public class SearchParamExtractorService {
|
|||
}
|
||||
|
||||
Class<? extends IBaseResource> type = resourceDefinition.getImplementingClass();
|
||||
String id = nextId.getIdPart();
|
||||
if (StringUtils.isBlank(id)) {
|
||||
String targetId = nextId.getIdPart();
|
||||
if (StringUtils.isBlank(targetId)) {
|
||||
String msg = "Invalid resource reference found at path[" + path + "] - Does not contain resource ID - " + nextId.getValue();
|
||||
if (theFailOnInvalidReference) {
|
||||
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;
|
||||
|
||||
Long targetVersionId = nextId.getVersionIdPartAsLong();
|
||||
if (resolvedTargetId != null) {
|
||||
|
||||
/*
|
||||
|
@ -289,7 +291,7 @@ public class SearchParamExtractorService {
|
|||
* need to resolve it again
|
||||
*/
|
||||
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) {
|
||||
|
||||
|
@ -305,7 +307,7 @@ public class SearchParamExtractorService {
|
|||
} else {
|
||||
// Cache the outcome in the current transaction in case there are more references
|
||||
ResourcePersistentId persistentId = new ResourcePersistentId(resourceLink.getTargetResourcePid());
|
||||
theTransactionDetails.addResolvedResourceId(thePathAndRef.getRef().getReferenceElement(), persistentId);
|
||||
theTransactionDetails.addResolvedResourceId(referenceElement, persistentId);
|
||||
}
|
||||
|
||||
} else {
|
||||
|
@ -317,7 +319,7 @@ public class SearchParamExtractorService {
|
|||
ResourceTable target;
|
||||
target = new ResourceTable();
|
||||
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();
|
||||
Long targetResourcePid = targetResource.getResourceId();
|
||||
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
|
||||
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);
|
||||
|
||||
ReferenceParam referenceParam = getReferenceParam(STRING_ID);
|
||||
|
@ -46,7 +46,7 @@ public class ResourceIndexedSearchParamsTest {
|
|||
|
||||
@Test
|
||||
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);
|
||||
|
||||
ReferenceParam referenceParam = getReferenceParam(STRING_ID);
|
||||
|
@ -56,7 +56,7 @@ public class ResourceIndexedSearchParamsTest {
|
|||
|
||||
@Test
|
||||
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);
|
||||
|
||||
ReferenceParam referenceParam = getReferenceParam(LONG_ID);
|
||||
|
@ -66,7 +66,7 @@ public class ResourceIndexedSearchParamsTest {
|
|||
|
||||
@Test
|
||||
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);
|
||||
|
||||
ReferenceParam referenceParam = getReferenceParam(LONG_ID);
|
||||
|
|
|
@ -34,10 +34,20 @@ import java.util.Optional;
|
|||
public class ResourcePersistentId {
|
||||
|
||||
private Object myId;
|
||||
private Long myVersion;
|
||||
|
||||
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);
|
||||
myId = theId;
|
||||
myVersion = theVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -47,12 +57,18 @@ public class ResourcePersistentId {
|
|||
}
|
||||
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
|
||||
public int hashCode() {
|
||||
return myId.hashCode();
|
||||
int retVal = myId.hashCode();
|
||||
if (myVersion != null) {
|
||||
retVal += myVersion.hashCode();
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
public Object getId() {
|
||||
|
@ -72,6 +88,18 @@ public class ResourcePersistentId {
|
|||
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) {
|
||||
List<Long> retVal = new ArrayList<>(thePids.size());
|
||||
for (ResourcePersistentId next : thePids) {
|
||||
|
|
|
@ -27,6 +27,7 @@ import com.google.common.collect.ListMultimap;
|
|||
import org.apache.commons.lang3.Validate;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.EnumSet;
|
||||
|
@ -48,7 +49,7 @@ import java.util.function.Supplier;
|
|||
public class TransactionDetails {
|
||||
|
||||
private final Date myTransactionDate;
|
||||
private Map<IIdType, ResourcePersistentId> myResolvedResourceIds = Collections.emptyMap();
|
||||
private Map<String, ResourcePersistentId> myResolvedResourceIds = Collections.emptyMap();
|
||||
private Map<String, Object> myUserData;
|
||||
private ListMultimap<Pointcut, HookParams> myDeferredInterceptorBroadcasts;
|
||||
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
|
||||
* the TransactionDetails if they are known to exist and be valid targets for other resources to link to.
|
||||
*/
|
||||
public Map<IIdType, ResourcePersistentId> getResolvedResourceIds() {
|
||||
return myResolvedResourceIds;
|
||||
@Nullable
|
||||
public ResourcePersistentId getResolvedResourceId(IIdType theId) {
|
||||
String idValue = theId.toVersionless().getValue();
|
||||
return myResolvedResourceIds.get(idValue);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -88,7 +91,7 @@ public class TransactionDetails {
|
|||
if (myResolvedResourceIds.isEmpty()) {
|
||||
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)
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getUserData(String theKey) {
|
||||
if (myUserData != null) {
|
||||
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
|
||||
* puts that in the map, and returns it
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getOrCreateUserData(String theKey, Supplier<T> theSupplier) {
|
||||
T retVal = (T) getUserData(theKey);
|
||||
if (retVal == null) {
|
||||
|
|
|
@ -2124,7 +2124,7 @@ public class GenericClientR4Test extends BaseGenericClientR4Test {
|
|||
});
|
||||
|
||||
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();
|
||||
assertNull(outcome);
|
||||
|
|
|
@ -143,7 +143,7 @@ public class BundleBuilderTest {
|
|||
|
||||
Patient patient = new Patient();
|
||||
patient.setActive(true);
|
||||
builder.addCreateEntry(patient);
|
||||
builder.addTransactionCreateEntry(patient);
|
||||
|
||||
Bundle bundle = (Bundle) builder.getBundle();
|
||||
ourLog.info("Bundle:\n{}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(bundle));
|
||||
|
@ -162,7 +162,7 @@ public class BundleBuilderTest {
|
|||
|
||||
Patient patient = new Patient();
|
||||
patient.setActive(true);
|
||||
builder.addCreateEntry(patient).conditional("Patient?active=true");
|
||||
builder.addTransactionCreateEntry(patient).conditional("Patient?active=true");
|
||||
|
||||
Bundle bundle = (Bundle) builder.getBundle();
|
||||
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>
|
||||
<derby_version>10.14.2.0</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.3.4</error_prone_core_version>
|
||||
<error_prone_core_version>2.5.1</error_prone_core_version>
|
||||
<nullaway_version>0.7.9</nullaway_version>
|
||||
<guava_version>30.1-jre</guava_version>
|
||||
<gson_version>2.8.5</gson_version>
|
||||
|
|
Loading…
Reference in New Issue