Automatically remove duplicate conditional creates in JPA

This commit is contained in:
James Agnew 2019-02-22 12:57:07 -05:00
parent 89b08cd627
commit 43c07077be
3 changed files with 112 additions and 4 deletions

View File

@ -44,10 +44,7 @@ import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import ca.uhn.fhir.rest.server.method.BaseMethodBinding; import ca.uhn.fhir.rest.server.method.BaseMethodBinding;
import ca.uhn.fhir.rest.server.method.BaseResourceReturningMethodBinding; import ca.uhn.fhir.rest.server.method.BaseResourceReturningMethodBinding;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.util.FhirTerser; import ca.uhn.fhir.util.*;
import ca.uhn.fhir.util.ResourceReferenceInfo;
import ca.uhn.fhir.util.StopWatch;
import ca.uhn.fhir.util.UrlUtil;
import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ArrayListMultimap;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
import org.apache.http.NameValuePair; import org.apache.http.NameValuePair;
@ -98,6 +95,14 @@ public class TransactionProcessor<BUNDLE extends IBaseBundle, BUNDLEENTRY> {
String actionName = "Transaction"; String actionName = "Transaction";
BUNDLE response = processTransactionAsSubRequest((ServletRequestDetails) theRequestDetails, theRequest, actionName); BUNDLE response = processTransactionAsSubRequest((ServletRequestDetails) theRequestDetails, theRequest, actionName);
List<BUNDLEENTRY> entries = myVersionAdapter.getEntries(response);
for (int i = 0; i < entries.size(); i++) {
if (ElementUtil.isEmpty(entries.get(i))) {
entries.remove(i);
i--;
}
}
return response; return response;
} }
@ -500,6 +505,42 @@ public class TransactionProcessor<BUNDLE extends IBaseBundle, BUNDLEENTRY> {
Set<ResourceTable> updatedEntities = new HashSet<>(); Set<ResourceTable> updatedEntities = new HashSet<>();
Map<String, Class<? extends IBaseResource>> conditionalRequestUrls = new HashMap<>(); Map<String, Class<? extends IBaseResource>> conditionalRequestUrls = new HashMap<>();
/*
* Look for duplicate conditional creates and consolidate them
*/
Map<String, String> ifNoneExistToUuid = new HashMap<>();
for (int index = 0, originalIndex = 0; index < theEntries.size(); index++, originalIndex++) {
BUNDLEENTRY nextReqEntry = theEntries.get(index);
String verb = myVersionAdapter.getEntryRequestVerb(nextReqEntry);
String entryUrl = myVersionAdapter.getFullUrl(nextReqEntry);
String requestUrl = myVersionAdapter.getEntryRequestUrl(nextReqEntry);
String ifNoneExist = myVersionAdapter.getEntryRequestIfNoneExist(nextReqEntry);
if ("POST".equals(verb)) {
if (isNotBlank(entryUrl) && isNotBlank(requestUrl) && isNotBlank(ifNoneExist)) {
if (!entryUrl.equals(requestUrl)) {
String key = requestUrl + "|" + ifNoneExist; // just in case the ifNoneExist doesn't include the resource type
if (!ifNoneExistToUuid.containsKey(key)) {
ifNoneExistToUuid.put(key, entryUrl);
} else {
ourLog.info("Discarding transaction bundle entry {} as it contained a duplicate conditional create: {}", originalIndex, ifNoneExist);
theEntries.remove(index);
index--;
String existingUuid = ifNoneExistToUuid.get(key);
for (BUNDLEENTRY nextEntry : theEntries) {
IBaseResource nextResource = myVersionAdapter.getResource(nextEntry);
for (ResourceReferenceInfo nextReference : myContext.newTerser().getAllResourceReferences(nextResource)) {
if (entryUrl.equals(nextReference.getResourceReference().getReferenceElement().getValue())) {
nextReference.getResourceReference().setReference(existingUuid);
}
}
}
}
}
}
}
}
/* /*
* 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

View File

@ -38,6 +38,7 @@ import java.io.UnsupportedEncodingException;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.*; import java.util.*;
import java.util.stream.Collectors;
import static org.hamcrest.Matchers.*; import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*; import static org.junit.Assert.*;
@ -1044,6 +1045,66 @@ public class FhirSystemDaoR4Test extends BaseJpaR4SystemTest {
} }
@Test
public void testTransactionWithDuplicateConditionalCreates() {
Bundle request = new Bundle();
request.setType(BundleType.TRANSACTION);
Practitioner p = new Practitioner();
p.setId(IdType.newRandomUuid());
p.addIdentifier().setSystem("http://foo").setValue("bar");
request.addEntry()
.setFullUrl(p.getId())
.setResource(p)
.getRequest()
.setMethod(HTTPVerb.POST)
.setUrl("Practitioner/")
.setIfNoneExist("Practitioner?identifier=http://foo|bar");
Observation o = new Observation();
o.setId(IdType.newRandomUuid());
o.getPerformerFirstRep().setReference(p.getId());
request.addEntry()
.setFullUrl(o.getId())
.setResource(o)
.getRequest()
.setMethod(HTTPVerb.POST)
.setUrl("Observation/");
p = new Practitioner();
p.setId(IdType.newRandomUuid());
p.addIdentifier().setSystem("http://foo").setValue("bar");
request.addEntry()
.setFullUrl(p.getId())
.setResource(p)
.getRequest()
.setMethod(HTTPVerb.POST)
.setUrl("Practitioner/")
.setIfNoneExist("Practitioner?identifier=http://foo|bar");
o = new Observation();
o.setId(IdType.newRandomUuid());
o.getPerformerFirstRep().setReference(p.getId());
request.addEntry()
.setFullUrl(o.getId())
.setResource(o)
.getRequest()
.setMethod(HTTPVerb.POST)
.setUrl("Observation/");
Bundle response = mySystemDao.transaction(null, request);
ourLog.info("Response:\n{}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(response));
List<String> responseTypes = response
.getEntry()
.stream()
.map(t -> new IdType(t.getResponse().getLocation()).getResourceType())
.collect(Collectors.toList());
assertThat(responseTypes.toString(), responseTypes, contains("Practitioner", "Observation", "Observation"));
}
@Test @Test
public void testTransactionCreateInlineMatchUrlWithTwoMatches() { public void testTransactionCreateInlineMatchUrlWithTwoMatches() {
String methodName = "testTransactionCreateInlineMatchUrlWithTwoMatches"; String methodName = "testTransactionCreateInlineMatchUrlWithTwoMatches";

View File

@ -52,6 +52,12 @@
The JPA query builder has been optimized to take better advantage of SQL IN (..) expressions The JPA query builder has been optimized to take better advantage of SQL IN (..) expressions
when performing token searches with multiple OR values. when performing token searches with multiple OR values.
</action> </action>
<action type="add">
The JPA server transaction processor will now automatically detect if the request
Bundle contains multiple entries having identical conditional create operations, and
collapse these into a single operation. This is done as a convenience, since many
conversion algorithms can accidentally generate such duplicates.
</action>
</release> </release>
<release version="3.7.0" date="2019-02-06" description="Gale"> <release version="3.7.0" date="2019-02-06" description="Gale">
<action type="add"> <action type="add">