stashing minor refactors
This commit is contained in:
parent
ee9c630228
commit
b51c722755
|
@ -961,6 +961,7 @@ public class FhirTerser {
|
||||||
for (BaseRuntimeChildDefinition nextChild : childDef.getChildrenAndExtension()) {
|
for (BaseRuntimeChildDefinition nextChild : childDef.getChildrenAndExtension()) {
|
||||||
|
|
||||||
List<?> values = nextChild.getAccessor().getValues(theElement);
|
List<?> values = nextChild.getAccessor().getValues(theElement);
|
||||||
|
|
||||||
if (values != null) {
|
if (values != null) {
|
||||||
for (Object nextValueObject : values) {
|
for (Object nextValueObject : values) {
|
||||||
IBase nextValue;
|
IBase nextValue;
|
||||||
|
|
|
@ -240,8 +240,10 @@ public abstract class BaseTransactionProcessor {
|
||||||
myVersionAdapter.populateEntryWithOperationOutcome(caughtEx, nextEntry);
|
myVersionAdapter.populateEntryWithOperationOutcome(caughtEx, nextEntry);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleTransactionCreateOrUpdateOutcome(Map<IIdType, IIdType> idSubstitutions, Map<IIdType, DaoMethodOutcome> idToPersistedOutcome, IIdType nextResourceId, DaoMethodOutcome outcome,
|
private void handleTransactionCreateOrUpdateOutcome(Map<IIdType, IIdType> idSubstitutions, Map<IIdType, DaoMethodOutcome> idToPersistedOutcome,
|
||||||
IBase newEntry, String theResourceType, IBaseResource theRes, RequestDetails theRequestDetails) {
|
IIdType nextResourceId, DaoMethodOutcome outcome,
|
||||||
|
IBase newEntry, String theResourceType,
|
||||||
|
IBaseResource theRes, RequestDetails theRequestDetails) {
|
||||||
IIdType newId = outcome.getId().toUnqualified();
|
IIdType newId = outcome.getId().toUnqualified();
|
||||||
IIdType resourceId = isPlaceholder(nextResourceId) ? nextResourceId : nextResourceId.toUnqualifiedVersionless();
|
IIdType resourceId = isPlaceholder(nextResourceId) ? nextResourceId : nextResourceId.toUnqualifiedVersionless();
|
||||||
if (newId.equals(resourceId) == false) {
|
if (newId.equals(resourceId) == false) {
|
||||||
|
@ -652,8 +654,129 @@ public abstract class BaseTransactionProcessor {
|
||||||
myModelConfig = theModelConfig;
|
myModelConfig = theModelConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Map<IBase, IIdType> doTransactionWriteOperations(final RequestDetails theRequest, String theActionName, TransactionDetails theTransactionDetails, Set<IIdType> theAllIds,
|
/**
|
||||||
Map<IIdType, IIdType> theIdSubstitutions, Map<IIdType, DaoMethodOutcome> theIdToPersistedOutcome, IBaseBundle theResponse, IdentityHashMap<IBase, Integer> theOriginalRequestOrder, List<IBase> theEntries, StopWatch theTransactionStopWatch) {
|
* Searches for duplicate conditional creates and consolidates them.
|
||||||
|
*
|
||||||
|
* @param theEntries
|
||||||
|
*/
|
||||||
|
private void consolidateDuplicateConditionalCreates(List<IBase> theEntries) {
|
||||||
|
final HashMap<String, String> keyToUuid = new HashMap<>();
|
||||||
|
for (int index = 0, originalIndex = 0; index < theEntries.size(); index++, originalIndex++) {
|
||||||
|
IBase nextReqEntry = theEntries.get(index);
|
||||||
|
IBaseResource resource = myVersionAdapter.getResource(nextReqEntry);
|
||||||
|
if (resource != null) {
|
||||||
|
String verb = myVersionAdapter.getEntryRequestVerb(myContext, nextReqEntry);
|
||||||
|
String entryUrl = myVersionAdapter.getFullUrl(nextReqEntry);
|
||||||
|
String requestUrl = myVersionAdapter.getEntryRequestUrl(nextReqEntry);
|
||||||
|
String ifNoneExist = myVersionAdapter.getEntryRequestIfNoneExist(nextReqEntry);
|
||||||
|
String key = verb + "|" + requestUrl + "|" + ifNoneExist;
|
||||||
|
|
||||||
|
// Conditional UPDATE
|
||||||
|
boolean consolidateEntry = false;
|
||||||
|
if ("PUT".equals(verb)) {
|
||||||
|
if (isNotBlank(entryUrl) && isNotBlank(requestUrl)) {
|
||||||
|
int questionMarkIndex = requestUrl.indexOf('?');
|
||||||
|
if (questionMarkIndex >= 0 && requestUrl.length() > (questionMarkIndex + 1)) {
|
||||||
|
consolidateEntry = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Conditional CREATE
|
||||||
|
if ("POST".equals(verb)) {
|
||||||
|
if (isNotBlank(entryUrl) && isNotBlank(requestUrl) && isNotBlank(ifNoneExist)) {
|
||||||
|
if (!entryUrl.equals(requestUrl)) {
|
||||||
|
consolidateEntry = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (consolidateEntry) {
|
||||||
|
if (!keyToUuid.containsKey(key)) {
|
||||||
|
keyToUuid.put(key, entryUrl);
|
||||||
|
} else {
|
||||||
|
ourLog.info("Discarding transaction bundle entry {} as it contained a duplicate conditional {}", originalIndex, verb);
|
||||||
|
theEntries.remove(index);
|
||||||
|
index--;
|
||||||
|
String existingUuid = keyToUuid.get(key);
|
||||||
|
for (IBase nextEntry : theEntries) {
|
||||||
|
IBaseResource nextResource = myVersionAdapter.getResource(nextEntry);
|
||||||
|
for (IBaseReference nextReference : myContext.newTerser().getAllPopulatedChildElementsOfType(nextResource, IBaseReference.class)) {
|
||||||
|
// We're interested in any references directly to the placeholder ID, but also
|
||||||
|
// references that have a resource target that has the placeholder ID.
|
||||||
|
String nextReferenceId = nextReference.getReferenceElement().getValue();
|
||||||
|
if (isBlank(nextReferenceId) && nextReference.getResource() != null) {
|
||||||
|
nextReferenceId = nextReference.getResource().getIdElement().getValue();
|
||||||
|
}
|
||||||
|
if (entryUrl.equals(nextReferenceId)) {
|
||||||
|
nextReference.setReference(existingUuid);
|
||||||
|
nextReference.setResource(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves teh next resource id (IIdType) from the base resource and next request entry.
|
||||||
|
* @param theBaseResource - base resource
|
||||||
|
* @param theNextReqEntry - next request entry
|
||||||
|
* @param theAllIds - set of all IIdType values
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private IIdType getNextResourceIdFromBaseResource(IBaseResource theBaseResource,
|
||||||
|
IBase theNextReqEntry,
|
||||||
|
Set<IIdType> theAllIds) {
|
||||||
|
IIdType nextResourceId = null;
|
||||||
|
if (theBaseResource != null) {
|
||||||
|
nextResourceId = theBaseResource.getIdElement();
|
||||||
|
|
||||||
|
String fullUrl = myVersionAdapter.getFullUrl(theNextReqEntry);
|
||||||
|
if (isNotBlank(fullUrl)) {
|
||||||
|
IIdType fullUrlIdType = newIdType(fullUrl);
|
||||||
|
if (isPlaceholder(fullUrlIdType)) {
|
||||||
|
nextResourceId = fullUrlIdType;
|
||||||
|
} else if (!nextResourceId.hasIdPart()) {
|
||||||
|
nextResourceId = fullUrlIdType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextResourceId.hasIdPart() && nextResourceId.getIdPart().matches("[a-zA-Z]+:.*") && !isPlaceholder(nextResourceId)) {
|
||||||
|
throw new InvalidRequestException("Invalid placeholder ID found: " + nextResourceId.getIdPart() + " - Must be of the form 'urn:uuid:[uuid]' or 'urn:oid:[oid]'");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextResourceId.hasIdPart() && !nextResourceId.hasResourceType() && !isPlaceholder(nextResourceId)) {
|
||||||
|
nextResourceId = newIdType(toResourceName(theBaseResource.getClass()), nextResourceId.getIdPart());
|
||||||
|
theBaseResource.setId(nextResourceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ensure that the bundle doesn't have any duplicates, since this causes all kinds of weirdness
|
||||||
|
*/
|
||||||
|
if (isPlaceholder(nextResourceId)) {
|
||||||
|
if (!theAllIds.add(nextResourceId)) {
|
||||||
|
throw new InvalidRequestException(myContext.getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionContainsMultipleWithDuplicateId", nextResourceId));
|
||||||
|
}
|
||||||
|
} else if (nextResourceId.hasResourceType() && nextResourceId.hasIdPart()) {
|
||||||
|
IIdType nextId = nextResourceId.toUnqualifiedVersionless();
|
||||||
|
if (!theAllIds.add(nextId)) {
|
||||||
|
throw new InvalidRequestException(myContext.getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionContainsMultipleWithDuplicateId", nextId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return nextResourceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Map<IBase, IIdType> doTransactionWriteOperations(final RequestDetails theRequest, String theActionName,
|
||||||
|
TransactionDetails theTransactionDetails, Set<IIdType> theAllIds,
|
||||||
|
Map<IIdType, IIdType> theIdSubstitutions, Map<IIdType, DaoMethodOutcome> theIdToPersistedOutcome,
|
||||||
|
IBaseBundle theResponse, IdentityHashMap<IBase, Integer> theOriginalRequestOrder,
|
||||||
|
List<IBase> theEntries, StopWatch theTransactionStopWatch) {
|
||||||
|
|
||||||
theTransactionDetails.beginAcceptingDeferredInterceptorBroadcasts(
|
theTransactionDetails.beginAcceptingDeferredInterceptorBroadcasts(
|
||||||
Pointcut.STORAGE_PRECOMMIT_RESOURCE_CREATED,
|
Pointcut.STORAGE_PRECOMMIT_RESOURCE_CREATED,
|
||||||
|
@ -661,7 +784,6 @@ public abstract class BaseTransactionProcessor {
|
||||||
Pointcut.STORAGE_PRECOMMIT_RESOURCE_DELETED
|
Pointcut.STORAGE_PRECOMMIT_RESOURCE_DELETED
|
||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
|
|
||||||
Set<String> deletedResources = new HashSet<>();
|
Set<String> deletedResources = new HashSet<>();
|
||||||
DeleteConflictList deleteConflicts = new DeleteConflictList();
|
DeleteConflictList deleteConflicts = new DeleteConflictList();
|
||||||
Map<IBase, IIdType> entriesToProcess = new IdentityHashMap<>();
|
Map<IBase, IIdType> entriesToProcess = new IdentityHashMap<>();
|
||||||
|
@ -673,117 +795,20 @@ public abstract class BaseTransactionProcessor {
|
||||||
/*
|
/*
|
||||||
* Look for duplicate conditional creates and consolidate them
|
* Look for duplicate conditional creates and consolidate them
|
||||||
*/
|
*/
|
||||||
final HashMap<String, String> keyToUuid = new HashMap<>();
|
consolidateDuplicateConditionalCreates(theEntries);
|
||||||
for (int index = 0, originalIndex = 0; index < theEntries.size(); index++, originalIndex++) {
|
|
||||||
IBase nextReqEntry = theEntries.get(index);
|
|
||||||
IBaseResource resource = myVersionAdapter.getResource(nextReqEntry);
|
|
||||||
if (resource != null) {
|
|
||||||
String verb = myVersionAdapter.getEntryRequestVerb(myContext, nextReqEntry);
|
|
||||||
String entryUrl = myVersionAdapter.getFullUrl(nextReqEntry);
|
|
||||||
String requestUrl = myVersionAdapter.getEntryRequestUrl(nextReqEntry);
|
|
||||||
String ifNoneExist = myVersionAdapter.getEntryRequestIfNoneExist(nextReqEntry);
|
|
||||||
String key = verb + "|" + requestUrl + "|" + ifNoneExist;
|
|
||||||
|
|
||||||
// Conditional UPDATE
|
|
||||||
boolean consolidateEntry = false;
|
|
||||||
if ("PUT".equals(verb)) {
|
|
||||||
if (isNotBlank(entryUrl) && isNotBlank(requestUrl)) {
|
|
||||||
int questionMarkIndex = requestUrl.indexOf('?');
|
|
||||||
if (questionMarkIndex >= 0 && requestUrl.length() > (questionMarkIndex + 1)) {
|
|
||||||
consolidateEntry = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Conditional CREATE
|
|
||||||
if ("POST".equals(verb)) {
|
|
||||||
if (isNotBlank(entryUrl) && isNotBlank(requestUrl) && isNotBlank(ifNoneExist)) {
|
|
||||||
if (!entryUrl.equals(requestUrl)) {
|
|
||||||
consolidateEntry = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (consolidateEntry) {
|
|
||||||
if (!keyToUuid.containsKey(key)) {
|
|
||||||
keyToUuid.put(key, entryUrl);
|
|
||||||
} else {
|
|
||||||
ourLog.info("Discarding transaction bundle entry {} as it contained a duplicate conditional {}", originalIndex, verb);
|
|
||||||
theEntries.remove(index);
|
|
||||||
index--;
|
|
||||||
String existingUuid = keyToUuid.get(key);
|
|
||||||
for (IBase nextEntry : theEntries) {
|
|
||||||
IBaseResource nextResource = myVersionAdapter.getResource(nextEntry);
|
|
||||||
for (IBaseReference nextReference : myContext.newTerser().getAllPopulatedChildElementsOfType(nextResource, IBaseReference.class)) {
|
|
||||||
// We're interested in any references directly to the placeholder ID, but also
|
|
||||||
// references that have a resource target that has the placeholder ID.
|
|
||||||
String nextReferenceId = nextReference.getReferenceElement().getValue();
|
|
||||||
if (isBlank(nextReferenceId) && nextReference.getResource() != null) {
|
|
||||||
nextReferenceId = nextReference.getResource().getIdElement().getValue();
|
|
||||||
}
|
|
||||||
if (entryUrl.equals(nextReferenceId)) {
|
|
||||||
nextReference.setReference(existingUuid);
|
|
||||||
nextReference.setResource(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Loop through the request and process any entries of type
|
* Loop through the request and process any entries of type
|
||||||
* PUT, POST or DELETE
|
* PUT, POST or DELETE
|
||||||
*/
|
*/
|
||||||
for (int i = 0; i < theEntries.size(); i++) {
|
for (int i = 0; i < theEntries.size(); i++) {
|
||||||
|
|
||||||
if (i % 250 == 0) {
|
if (i % 250 == 0) {
|
||||||
ourLog.debug("Processed {} non-GET entries out of {} in transaction", i, theEntries.size());
|
ourLog.debug("Processed {} non-GET entries out of {} in transaction", i, theEntries.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
IBase nextReqEntry = theEntries.get(i);
|
IBase nextReqEntry = theEntries.get(i);
|
||||||
IBaseResource res = myVersionAdapter.getResource(nextReqEntry);
|
IBaseResource res = myVersionAdapter.getResource(nextReqEntry);
|
||||||
IIdType nextResourceId = null;
|
IIdType nextResourceId = getNextResourceIdFromBaseResource(res, nextReqEntry, theAllIds);
|
||||||
if (res != null) {
|
|
||||||
|
|
||||||
nextResourceId = res.getIdElement();
|
|
||||||
|
|
||||||
String fullUrl = myVersionAdapter.getFullUrl(nextReqEntry);
|
|
||||||
if (isNotBlank(fullUrl)) {
|
|
||||||
IIdType fullUrlIdType = newIdType(fullUrl);
|
|
||||||
if (isPlaceholder(fullUrlIdType)) {
|
|
||||||
nextResourceId = fullUrlIdType;
|
|
||||||
} else if (!nextResourceId.hasIdPart()) {
|
|
||||||
nextResourceId = fullUrlIdType;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nextResourceId.hasIdPart() && nextResourceId.getIdPart().matches("[a-zA-Z]+:.*") && !isPlaceholder(nextResourceId)) {
|
|
||||||
throw new InvalidRequestException("Invalid placeholder ID found: " + nextResourceId.getIdPart() + " - Must be of the form 'urn:uuid:[uuid]' or 'urn:oid:[oid]'");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nextResourceId.hasIdPart() && !nextResourceId.hasResourceType() && !isPlaceholder(nextResourceId)) {
|
|
||||||
nextResourceId = newIdType(toResourceName(res.getClass()), nextResourceId.getIdPart());
|
|
||||||
res.setId(nextResourceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Ensure that the bundle doesn't have any duplicates, since this causes all kinds of weirdness
|
|
||||||
*/
|
|
||||||
if (isPlaceholder(nextResourceId)) {
|
|
||||||
if (!theAllIds.add(nextResourceId)) {
|
|
||||||
throw new InvalidRequestException(myContext.getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionContainsMultipleWithDuplicateId", nextResourceId));
|
|
||||||
}
|
|
||||||
} else if (nextResourceId.hasResourceType() && nextResourceId.hasIdPart()) {
|
|
||||||
IIdType nextId = nextResourceId.toUnqualifiedVersionless();
|
|
||||||
if (!theAllIds.add(nextId)) {
|
|
||||||
throw new InvalidRequestException(myContext.getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionContainsMultipleWithDuplicateId", nextId));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
String verb = myVersionAdapter.getEntryRequestVerb(myContext, nextReqEntry);
|
String verb = myVersionAdapter.getEntryRequestVerb(myContext, nextReqEntry);
|
||||||
String resourceType = res != null ? myContext.getResourceType(res) : null;
|
String resourceType = res != null ? myContext.getResourceType(res) : null;
|
||||||
|
@ -891,7 +916,8 @@ public abstract class BaseTransactionProcessor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleTransactionCreateOrUpdateOutcome(theIdSubstitutions, theIdToPersistedOutcome, nextResourceId, outcome, nextRespEntry, resourceType, res, theRequest);
|
handleTransactionCreateOrUpdateOutcome(theIdSubstitutions, theIdToPersistedOutcome, nextResourceId,
|
||||||
|
outcome, nextRespEntry, resourceType, res, theRequest);
|
||||||
entriesToProcess.put(nextRespEntry, outcome.getId());
|
entriesToProcess.put(nextRespEntry, outcome.getId());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -958,52 +984,24 @@ public abstract class BaseTransactionProcessor {
|
||||||
* was also deleted as a part of this transaction, which is why we check this now at the
|
* was also deleted as a part of this transaction, which is why we check this now at the
|
||||||
* end.
|
* end.
|
||||||
*/
|
*/
|
||||||
for (Iterator<DeleteConflict> iter = deleteConflicts.iterator(); iter.hasNext(); ) {
|
checkForDeleteConflicts(deleteConflicts, deletedResources, updatedResources);
|
||||||
DeleteConflict nextDeleteConflict = iter.next();
|
|
||||||
|
|
||||||
/*
|
theIdToPersistedOutcome.entrySet().forEach(idAndOutcome -> {
|
||||||
* If we have a conflict, it means we can't delete Resource/A because
|
theTransactionDetails.addResolvedResourceId(idAndOutcome.getKey(), idAndOutcome.getValue().getPersistentId());
|
||||||
* Resource/B has a reference to it. We'll ignore that conflict though
|
});
|
||||||
* if it turns out we're also deleting Resource/B in this transaction.
|
|
||||||
*/
|
|
||||||
if (deletedResources.contains(nextDeleteConflict.getSourceId().toUnqualifiedVersionless().getValue())) {
|
|
||||||
iter.remove();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* And then, this is kind of a last ditch check. It's also ok to delete
|
|
||||||
* Resource/A if Resource/B isn't being deleted, but it is being UPDATED
|
|
||||||
* in this transaction, and the updated version of it has no references
|
|
||||||
* to Resource/A any more.
|
|
||||||
*/
|
|
||||||
String sourceId = nextDeleteConflict.getSourceId().toUnqualifiedVersionless().getValue();
|
|
||||||
String targetId = nextDeleteConflict.getTargetId().toUnqualifiedVersionless().getValue();
|
|
||||||
Optional<IBaseResource> updatedSource = updatedResources
|
|
||||||
.stream()
|
|
||||||
.filter(t -> sourceId.equals(t.getIdElement().toUnqualifiedVersionless().getValue()))
|
|
||||||
.findFirst();
|
|
||||||
if (updatedSource.isPresent()) {
|
|
||||||
List<ResourceReferenceInfo> referencesInSource = myContext.newTerser().getAllResourceReferences(updatedSource.get());
|
|
||||||
boolean sourceStillReferencesTarget = referencesInSource
|
|
||||||
.stream()
|
|
||||||
.anyMatch(t -> targetId.equals(t.getResourceReference().getReferenceElement().toUnqualifiedVersionless().getValue()));
|
|
||||||
if (!sourceStillReferencesTarget) {
|
|
||||||
iter.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DeleteConflictService.validateDeleteConflictsEmptyOrThrowException(myContext, deleteConflicts);
|
|
||||||
|
|
||||||
theIdToPersistedOutcome.entrySet().forEach(t -> theTransactionDetails.addResolvedResourceId(t.getKey(), t.getValue().getPersistentId()));
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Perform ID substitutions and then index each resource we have saved
|
* Perform ID substitutions and then index each resource we have saved
|
||||||
*/
|
*/
|
||||||
|
|
||||||
resolveReferencesThenSaveAndIndexResources(theRequest, theTransactionDetails, theIdSubstitutions, theIdToPersistedOutcome, theTransactionStopWatch, entriesToProcess, nonUpdatedEntities, updatedEntities);
|
resolveReferencesThenSaveAndIndexResources(theRequest, theTransactionDetails,
|
||||||
|
theIdSubstitutions, theIdToPersistedOutcome,
|
||||||
|
theTransactionStopWatch, entriesToProcess,
|
||||||
|
nonUpdatedEntities, updatedEntities);
|
||||||
|
|
||||||
theTransactionStopWatch.endCurrentTask();
|
theTransactionStopWatch.endCurrentTask();
|
||||||
|
|
||||||
|
// flush writes to db
|
||||||
theTransactionStopWatch.startTask("Flush writes to database");
|
theTransactionStopWatch.startTask("Flush writes to database");
|
||||||
|
|
||||||
flushSession(theIdToPersistedOutcome);
|
flushSession(theIdToPersistedOutcome);
|
||||||
|
@ -1061,6 +1059,53 @@ public abstract class BaseTransactionProcessor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks for any delete conflicts.
|
||||||
|
* @param theDeleteConflicts - set of delete conflicts
|
||||||
|
* @param theDeletedResources - set of deleted resources
|
||||||
|
* @param theUpdatedResources - list of updated resources
|
||||||
|
*/
|
||||||
|
private void checkForDeleteConflicts(DeleteConflictList theDeleteConflicts,
|
||||||
|
Set<String> theDeletedResources,
|
||||||
|
List<IBaseResource> theUpdatedResources) {
|
||||||
|
for (Iterator<DeleteConflict> iter = theDeleteConflicts.iterator(); iter.hasNext(); ) {
|
||||||
|
DeleteConflict nextDeleteConflict = iter.next();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If we have a conflict, it means we can't delete Resource/A because
|
||||||
|
* Resource/B has a reference to it. We'll ignore that conflict though
|
||||||
|
* if it turns out we're also deleting Resource/B in this transaction.
|
||||||
|
*/
|
||||||
|
if (theDeletedResources.contains(nextDeleteConflict.getSourceId().toUnqualifiedVersionless().getValue())) {
|
||||||
|
iter.remove();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* And then, this is kind of a last ditch check. It's also ok to delete
|
||||||
|
* Resource/A if Resource/B isn't being deleted, but it is being UPDATED
|
||||||
|
* in this transaction, and the updated version of it has no references
|
||||||
|
* to Resource/A any more.
|
||||||
|
*/
|
||||||
|
String sourceId = nextDeleteConflict.getSourceId().toUnqualifiedVersionless().getValue();
|
||||||
|
String targetId = nextDeleteConflict.getTargetId().toUnqualifiedVersionless().getValue();
|
||||||
|
Optional<IBaseResource> updatedSource = theUpdatedResources
|
||||||
|
.stream()
|
||||||
|
.filter(t -> sourceId.equals(t.getIdElement().toUnqualifiedVersionless().getValue()))
|
||||||
|
.findFirst();
|
||||||
|
if (updatedSource.isPresent()) {
|
||||||
|
List<ResourceReferenceInfo> referencesInSource = myContext.newTerser().getAllResourceReferences(updatedSource.get());
|
||||||
|
boolean sourceStillReferencesTarget = referencesInSource
|
||||||
|
.stream()
|
||||||
|
.anyMatch(t -> targetId.equals(t.getResourceReference().getReferenceElement().toUnqualifiedVersionless().getValue()));
|
||||||
|
if (!sourceStillReferencesTarget) {
|
||||||
|
iter.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DeleteConflictService.validateDeleteConflictsEmptyOrThrowException(myContext, theDeleteConflicts);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method replaces any placeholder references in the
|
* This method replaces any placeholder references in the
|
||||||
* source transaction Bundle with their actual targets, then stores the resource contents and indexes
|
* source transaction Bundle with their actual targets, then stores the resource contents and indexes
|
||||||
|
@ -1083,7 +1128,10 @@ public abstract class BaseTransactionProcessor {
|
||||||
* pass because it's too complex to try and insert the auto-versioned references and still
|
* pass because it's too complex to try and insert the auto-versioned references and still
|
||||||
* account for NOPs, so we block NOPs in that pass.
|
* account for NOPs, so we block NOPs in that pass.
|
||||||
*/
|
*/
|
||||||
private void resolveReferencesThenSaveAndIndexResources(RequestDetails theRequest, TransactionDetails theTransactionDetails, Map<IIdType, IIdType> theIdSubstitutions, Map<IIdType, DaoMethodOutcome> theIdToPersistedOutcome, StopWatch theTransactionStopWatch, Map<IBase, IIdType> entriesToProcess, Set<IIdType> nonUpdatedEntities, Set<IBasePersistedResource> updatedEntities) {
|
private void resolveReferencesThenSaveAndIndexResources(RequestDetails theRequest, TransactionDetails theTransactionDetails,
|
||||||
|
Map<IIdType, IIdType> theIdSubstitutions, Map<IIdType, DaoMethodOutcome> theIdToPersistedOutcome,
|
||||||
|
StopWatch theTransactionStopWatch, Map<IBase, IIdType> entriesToProcess,
|
||||||
|
Set<IIdType> nonUpdatedEntities, Set<IBasePersistedResource> updatedEntities) {
|
||||||
FhirTerser terser = myContext.newTerser();
|
FhirTerser terser = myContext.newTerser();
|
||||||
theTransactionStopWatch.startTask("Index " + theIdToPersistedOutcome.size() + " resources");
|
theTransactionStopWatch.startTask("Index " + theIdToPersistedOutcome.size() + " resources");
|
||||||
IdentityHashMap<DaoMethodOutcome, Set<IBaseReference>> deferredIndexesForAutoVersioning = null;
|
IdentityHashMap<DaoMethodOutcome, Set<IBaseReference>> deferredIndexesForAutoVersioning = null;
|
||||||
|
@ -1105,14 +1153,28 @@ public abstract class BaseTransactionProcessor {
|
||||||
|
|
||||||
Set<IBaseReference> referencesToAutoVersion = BaseStorageDao.extractReferencesToAutoVersion(myContext, myModelConfig, nextResource);
|
Set<IBaseReference> referencesToAutoVersion = BaseStorageDao.extractReferencesToAutoVersion(myContext, myModelConfig, nextResource);
|
||||||
if (referencesToAutoVersion.isEmpty()) {
|
if (referencesToAutoVersion.isEmpty()) {
|
||||||
resolveReferencesThenSaveAndIndexResource(theRequest, theTransactionDetails, theIdSubstitutions, theIdToPersistedOutcome, entriesToProcess, nonUpdatedEntities, updatedEntities, terser, nextOutcome, nextResource, referencesToAutoVersion);
|
// no references to autoversion - we can do the resolve and save now
|
||||||
|
resolveReferencesThenSaveAndIndexResource(theRequest, theTransactionDetails,
|
||||||
|
theIdSubstitutions, theIdToPersistedOutcome,
|
||||||
|
entriesToProcess, nonUpdatedEntities,
|
||||||
|
updatedEntities, terser,
|
||||||
|
nextOutcome, nextResource,
|
||||||
|
referencesToAutoVersion); // this is empty
|
||||||
} else {
|
} else {
|
||||||
if (deferredIndexesForAutoVersioning == null) {
|
if (deferredIndexesForAutoVersioning == null) {
|
||||||
deferredIndexesForAutoVersioning = new IdentityHashMap<>();
|
deferredIndexesForAutoVersioning = new IdentityHashMap<>();
|
||||||
}
|
}
|
||||||
deferredIndexesForAutoVersioning.put(nextOutcome, referencesToAutoVersion);
|
deferredIndexesForAutoVersioning.put(nextOutcome, referencesToAutoVersion);
|
||||||
}
|
|
||||||
|
|
||||||
|
// TODO - add the references to the
|
||||||
|
// idsToPersistedOutcomes
|
||||||
|
// for (IBaseReference autoVersion: referencesToAutoVersion) {
|
||||||
|
// IBaseResource resource = myVersionAdapter.getResource(autoVersion);
|
||||||
|
// IFhirResourceDao dao = getDaoOrThrowException(resource.getClass());
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
// theIdToPersistedOutcome.put()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we have any resources we'll be auto-versioning, index these next
|
// If we have any resources we'll be auto-versioning, index these next
|
||||||
|
@ -1121,12 +1183,22 @@ public abstract class BaseTransactionProcessor {
|
||||||
DaoMethodOutcome nextOutcome = nextEntry.getKey();
|
DaoMethodOutcome nextOutcome = nextEntry.getKey();
|
||||||
Set<IBaseReference> referencesToAutoVersion = nextEntry.getValue();
|
Set<IBaseReference> referencesToAutoVersion = nextEntry.getValue();
|
||||||
IBaseResource nextResource = nextOutcome.getResource();
|
IBaseResource nextResource = nextOutcome.getResource();
|
||||||
resolveReferencesThenSaveAndIndexResource(theRequest, theTransactionDetails, theIdSubstitutions, theIdToPersistedOutcome, entriesToProcess, nonUpdatedEntities, updatedEntities, terser, nextOutcome, nextResource, referencesToAutoVersion);
|
resolveReferencesThenSaveAndIndexResource(theRequest, theTransactionDetails,
|
||||||
|
theIdSubstitutions, theIdToPersistedOutcome,
|
||||||
|
entriesToProcess, nonUpdatedEntities,
|
||||||
|
updatedEntities, terser,
|
||||||
|
nextOutcome, nextResource,
|
||||||
|
referencesToAutoVersion);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void resolveReferencesThenSaveAndIndexResource(RequestDetails theRequest, TransactionDetails theTransactionDetails, Map<IIdType, IIdType> theIdSubstitutions, Map<IIdType, DaoMethodOutcome> theIdToPersistedOutcome, Map<IBase, IIdType> entriesToProcess, Set<IIdType> nonUpdatedEntities, Set<IBasePersistedResource> updatedEntities, FhirTerser terser, DaoMethodOutcome nextOutcome, IBaseResource nextResource, Set<IBaseReference> theReferencesToAutoVersion) {
|
private void resolveReferencesThenSaveAndIndexResource(RequestDetails theRequest, TransactionDetails theTransactionDetails,
|
||||||
|
Map<IIdType, IIdType> theIdSubstitutions, Map<IIdType, DaoMethodOutcome> theIdToPersistedOutcome,
|
||||||
|
Map<IBase, IIdType> entriesToProcess, Set<IIdType> nonUpdatedEntities,
|
||||||
|
Set<IBasePersistedResource> updatedEntities, FhirTerser terser,
|
||||||
|
DaoMethodOutcome nextOutcome, IBaseResource nextResource,
|
||||||
|
Set<IBaseReference> theReferencesToAutoVersion) {
|
||||||
// References
|
// References
|
||||||
List<ResourceReferenceInfo> allRefs = terser.getAllResourceReferences(nextResource);
|
List<ResourceReferenceInfo> allRefs = terser.getAllResourceReferences(nextResource);
|
||||||
for (ResourceReferenceInfo nextRef : allRefs) {
|
for (ResourceReferenceInfo nextRef : allRefs) {
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
package ca.uhn.fhir.jpa.dao.r4;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.context.ParserOptions;
|
||||||
|
import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
||||||
|
import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome;
|
||||||
|
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
|
||||||
|
import ca.uhn.fhir.jpa.partition.SystemRequestDetails;
|
||||||
|
import ca.uhn.fhir.jpa.provider.r4.ResourceProviderR4Test;
|
||||||
|
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||||
|
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||||
|
import ca.uhn.fhir.rest.param.TokenParam;
|
||||||
|
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.Condition;
|
||||||
|
import org.hl7.fhir.r4.model.Encounter;
|
||||||
|
import org.hl7.fhir.r4.model.ExplanationOfBenefit;
|
||||||
|
import org.hl7.fhir.r4.model.IdType;
|
||||||
|
import org.hl7.fhir.r4.model.Observation;
|
||||||
|
import org.hl7.fhir.r4.model.Organization;
|
||||||
|
import org.hl7.fhir.r4.model.Patient;
|
||||||
|
import org.hl7.fhir.r4.model.Reference;
|
||||||
|
import org.hl7.fhir.r4.model.Task;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static org.hamcrest.CoreMatchers.is;
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
|
import static org.hamcrest.Matchers.matchesPattern;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
public class AAAATests extends BaseJpaR4Test {
|
||||||
|
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4VersionedReferenceTest.class);
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
public void afterEach() {
|
||||||
|
myFhirCtx.getParserOptions().setStripVersionsFromReferences(true);
|
||||||
|
myFhirCtx.getParserOptions().getDontStripVersionsFromReferencesAtPaths().clear();
|
||||||
|
myDaoConfig.setDeleteEnabled(new DaoConfig().isDeleteEnabled());
|
||||||
|
myModelConfig.setRespectVersionsForSearchIncludes(new ModelConfig().isRespectVersionsForSearchIncludes());
|
||||||
|
myModelConfig.setAutoVersionReferenceAtPaths(new ModelConfig().getAutoVersionReferenceAtPaths());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("GH-2901 Test no NPE is thrown on autoversioned references")
|
||||||
|
public void testNoNpeMinimal() {
|
||||||
|
myDaoConfig.setAutoCreatePlaceholderReferenceTargets(false);
|
||||||
|
myModelConfig.setAutoVersionReferenceAtPaths("Observation.subject");
|
||||||
|
|
||||||
|
// ParserOptions options = new ParserOptions();
|
||||||
|
// options.setDontStripVersionsFromReferencesAtPaths("Observation.subject");
|
||||||
|
// myFhirCtx.setParserOptions(options);
|
||||||
|
|
||||||
|
Patient patient = new Patient();
|
||||||
|
patient.setId("Patient/RED");
|
||||||
|
myPatientDao.update(patient);
|
||||||
|
|
||||||
|
Observation obs = new Observation();
|
||||||
|
obs.setId("Observation/DEF");
|
||||||
|
obs.setSubject(new Reference("Patient/RED"));
|
||||||
|
BundleBuilder builder = new BundleBuilder(myFhirCtx);
|
||||||
|
builder.addTransactionUpdateEntry(obs);
|
||||||
|
|
||||||
|
mySystemDao.transaction(new SystemRequestDetails(), (Bundle) builder.getBundle());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
package ca.uhn.fhir.jpa.dao.r4;
|
package ca.uhn.fhir.jpa.dao.r4;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.context.ParserOptions;
|
||||||
import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
||||||
import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome;
|
import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
|
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
|
||||||
|
@ -802,7 +803,9 @@ public class FhirResourceDaoR4VersionedReferenceTest extends BaseJpaR4Test {
|
||||||
);
|
);
|
||||||
myModelConfig.setAutoVersionReferenceAtPaths(new HashSet<String>(strings));
|
myModelConfig.setAutoVersionReferenceAtPaths(new HashSet<String>(strings));
|
||||||
|
|
||||||
Bundle bundle = myFhirCtx.newJsonParser().parseResource(Bundle.class, new InputStreamReader(FhirResourceDaoR4VersionedReferenceTest.class.getResourceAsStream("/npe-causing-bundle.json")));
|
Bundle bundle = myFhirCtx.newJsonParser().parseResource(Bundle.class,
|
||||||
|
new InputStreamReader(
|
||||||
|
FhirResourceDaoR4VersionedReferenceTest.class.getResourceAsStream("/npe-causing-bundle.json")));
|
||||||
|
|
||||||
Bundle transaction = mySystemDao.transaction(new SystemRequestDetails(), bundle);
|
Bundle transaction = mySystemDao.transaction(new SystemRequestDetails(), bundle);
|
||||||
}
|
}
|
||||||
|
@ -834,9 +837,17 @@ public class FhirResourceDaoR4VersionedReferenceTest extends BaseJpaR4Test {
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("GH-2901 Test no NPE is thrown on autoversioned references")
|
@DisplayName("GH-2901 Test no NPE is thrown on autoversioned references")
|
||||||
public void testNoNpeMinimal() {
|
public void testNoNpeMinimal() {
|
||||||
myDaoConfig.setAutoCreatePlaceholderReferenceTargets(true);
|
myDaoConfig.setAutoCreatePlaceholderReferenceTargets(false);
|
||||||
myModelConfig.setAutoVersionReferenceAtPaths("Observation.subject");
|
myModelConfig.setAutoVersionReferenceAtPaths("Observation.subject");
|
||||||
|
|
||||||
|
// ParserOptions options = new ParserOptions();
|
||||||
|
// options.setDontStripVersionsFromReferencesAtPaths("Observation.subject");
|
||||||
|
// myFhirCtx.setParserOptions(options);
|
||||||
|
|
||||||
|
Patient patient = new Patient();
|
||||||
|
patient.setId("Patient/RED");
|
||||||
|
myPatientDao.update(patient);
|
||||||
|
|
||||||
Observation obs = new Observation();
|
Observation obs = new Observation();
|
||||||
obs.setId("Observation/DEF");
|
obs.setId("Observation/DEF");
|
||||||
obs.setSubject(new Reference("Patient/RED"));
|
obs.setSubject(new Reference("Patient/RED"));
|
||||||
|
|
|
@ -1,5 +1,25 @@
|
||||||
package ca.uhn.fhir.rest.server.mail;
|
package ca.uhn.fhir.rest.server.mail;
|
||||||
|
|
||||||
|
/*-
|
||||||
|
* #%L
|
||||||
|
* HAPI FHIR - Server Framework
|
||||||
|
* %%
|
||||||
|
* 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 org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.commons.lang3.builder.EqualsBuilder;
|
import org.apache.commons.lang3.builder.EqualsBuilder;
|
||||||
import org.apache.commons.lang3.builder.HashCodeBuilder;
|
import org.apache.commons.lang3.builder.HashCodeBuilder;
|
||||||
|
|
|
@ -1,5 +1,25 @@
|
||||||
package ca.uhn.fhir.rest.server.mail;
|
package ca.uhn.fhir.rest.server.mail;
|
||||||
|
|
||||||
|
/*-
|
||||||
|
* #%L
|
||||||
|
* HAPI FHIR - Server Framework
|
||||||
|
* %%
|
||||||
|
* 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 org.apache.commons.lang3.Validate;
|
import org.apache.commons.lang3.Validate;
|
||||||
import org.simplejavamail.MailException;
|
import org.simplejavamail.MailException;
|
||||||
import org.simplejavamail.api.email.Email;
|
import org.simplejavamail.api.email.Email;
|
||||||
|
|
|
@ -1,19 +1,3 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
package ca.uhn.fhir.model.dstu2.composite;
|
package ca.uhn.fhir.model.dstu2.composite;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
Loading…
Reference in New Issue