JPA server is now able to handle placeholder IDs (e.g. urn:uuid:00....000) being used in Bundle.entry.request.url as a part of the conditional URL within transactions.

This commit is contained in:
James 2017-07-14 05:52:33 -04:00
parent 2e60ff7521
commit b13333c3c0
4 changed files with 522 additions and 262 deletions

View File

@ -63,6 +63,7 @@ import ca.uhn.fhir.util.UrlUtil;
import ca.uhn.fhir.util.UrlUtil.UrlParts;
public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirSystemDaoDstu3.class);
@Autowired
@ -136,115 +137,6 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
return resp;
}
private String extractTransactionUrlOrThrowException(BundleEntryComponent nextEntry, HTTPVerb verb) {
String url = nextEntry.getRequest().getUrl();
if (isBlank(url)) {
throw new InvalidRequestException(getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionMissingUrl", verb.name()));
}
return url;
}
/**
* This method is called for nested bundles (e.g. if we received a transaction with an entry that
* was a GET search, this method is called on the bundle for the search result, that will be placed in the
* outer bundle). This method applies the _summary and _content parameters to the output of
* that bundle.
*
* TODO: This isn't the most efficient way of doing this.. hopefully we can come up with something better in the future.
*/
private IBaseResource filterNestedBundle(RequestDetails theRequestDetails, IBaseResource theResource) {
IParser p = getContext().newJsonParser();
RestfulServerUtils.configureResponseParser(theRequestDetails, p);
return p.parseResource(theResource.getClass(), p.encodeResourceToString(theResource));
}
private IFhirResourceDao<?> getDaoOrThrowException(Class<? extends IBaseResource> theClass) {
IFhirResourceDao<? extends IBaseResource> retVal = getDao(theClass);
if (retVal == null) {
throw new InvalidRequestException("Unable to process request, this server does not know how to handle resources of type " + getContext().getResourceDefinition(theClass).getName());
}
return retVal;
}
@Override
public Meta metaGetOperation(RequestDetails theRequestDetails) {
// Notify interceptors
ActionRequestDetails requestDetails = new ActionRequestDetails(theRequestDetails);
notifyInterceptors(RestOperationTypeEnum.META, requestDetails);
String sql = "SELECT d FROM TagDefinition d WHERE d.myId IN (SELECT DISTINCT t.myTagId FROM ResourceTag t)";
TypedQuery<TagDefinition> q = myEntityManager.createQuery(sql, TagDefinition.class);
List<TagDefinition> tagDefinitions = q.getResultList();
Meta retVal = toMeta(tagDefinitions);
return retVal;
}
protected Meta toMeta(Collection<TagDefinition> tagDefinitions) {
Meta retVal = new Meta();
for (TagDefinition next : tagDefinitions) {
switch (next.getTagType()) {
case PROFILE:
retVal.addProfile(next.getCode());
break;
case SECURITY_LABEL:
retVal.addSecurity().setSystem(next.getSystem()).setCode(next.getCode()).setDisplay(next.getDisplay());
break;
case TAG:
retVal.addTag().setSystem(next.getSystem()).setCode(next.getCode()).setDisplay(next.getDisplay());
break;
}
}
return retVal;
}
private ca.uhn.fhir.jpa.dao.IFhirResourceDao<? extends IBaseResource> toDao(UrlParts theParts, String theVerb, String theUrl) {
RuntimeResourceDefinition resType;
try {
resType = getContext().getResourceDefinition(theParts.getResourceType());
} catch (DataFormatException e) {
String msg = getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionInvalidUrl", theVerb, theUrl);
throw new InvalidRequestException(msg);
}
IFhirResourceDao<? extends IBaseResource> dao = null;
if (resType != null) {
dao = getDao(resType.getImplementingClass());
}
if (dao == null) {
String msg = getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionInvalidUrl", theVerb, theUrl);
throw new InvalidRequestException(msg);
}
// if (theParts.getResourceId() == null && theParts.getParams() == null) {
// String msg = getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionInvalidUrl", theVerb, theUrl);
// throw new InvalidRequestException(msg);
// }
return dao;
}
@Transactional(propagation = Propagation.REQUIRED)
@Override
public Bundle transaction(RequestDetails theRequestDetails, Bundle theRequest) {
if (theRequestDetails != null) {
ActionRequestDetails requestDetails = new ActionRequestDetails(theRequestDetails, theRequest, "Bundle", null);
notifyInterceptors(RestOperationTypeEnum.TRANSACTION, requestDetails);
}
String actionName = "Transaction";
return transaction((ServletRequestDetails) theRequestDetails, theRequest, actionName);
}
private Bundle transaction(ServletRequestDetails theRequestDetails, Bundle theRequest, String theActionName) {
super.markRequestAsProcessingSubRequest(theRequestDetails);
try {
return doTransaction(theRequestDetails, theRequest, theActionName);
} finally {
super.clearRequestAsProcessingSubRequest(theRequestDetails);
}
}
@SuppressWarnings("unchecked")
private Bundle doTransaction(ServletRequestDetails theRequestDetails, Bundle theRequest, String theActionName) {
BundleType transactionType = theRequest.getTypeElement().getValue();
@ -299,7 +191,6 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
getEntries.add(theRequest.getEntry().get(i));
}
}
Collections.sort(theRequest.getEntry(), new TransactionSorter());
Set<String> deletedResources = new HashSet<String>();
List<DeleteConflict> deleteConflicts = new ArrayList<DeleteConflict>();
@ -307,17 +198,32 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
Set<ResourceTable> nonUpdatedEntities = new HashSet<ResourceTable>();
Map<String, Class<? extends IBaseResource>> conditionalRequestUrls = new HashMap<String, Class<? extends IBaseResource>>();
List<BundleEntryComponent> entries = new ArrayList<BundleEntryComponent>(theRequest.getEntry());
/*
* See FhirSystemDaoDstu3Test#testTransactionWithPlaceholderIdInMatchUrl
* Basically if the resource has a match URL that references a placeholder,
* we try to handle the resource with the placeholder first.
*/
Set<String> placeholderIds = new HashSet<String>();
for (BundleEntryComponent nextEntry : entries) {
if (isNotBlank(nextEntry.getFullUrl()) && nextEntry.getFullUrl().startsWith(IdType.URN_PREFIX)) {
placeholderIds.add(nextEntry.getFullUrl());
}
}
Collections.sort(entries, new TransactionSorter(placeholderIds));
/*
* Loop through the request and process any entries of type
* PUT, POST or DELETE
*/
for (int i = 0; i < theRequest.getEntry().size(); i++) {
for (int i = 0; i < entries.size(); i++) {
if (i % 100 == 0) {
ourLog.info("Processed {} non-GET entries out of {}", i, theRequest.getEntry().size());
ourLog.info("Processed {} non-GET entries out of {}", i, entries.size());
}
BundleEntryComponent nextReqEntry = theRequest.getEntry().get(i);
BundleEntryComponent nextReqEntry = entries.get(i);
Resource res = nextReqEntry.getResource();
IdType nextResourceId = null;
if (res != null) {
@ -368,6 +274,7 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
res.setId((String) null);
DaoMethodOutcome outcome;
String matchUrl = nextReqEntry.getRequest().getIfNoneExist();
matchUrl = performIdSubstitutionsInMatchUrl(idSubstitutions, matchUrl);
outcome = resourceDao.create(res, matchUrl, false, theRequestDetails);
if (nextResourceId != null) {
handleTransactionCreateOrUpdateOutcome(idSubstitutions, idToPersistedOutcome, nextResourceId, outcome, nextRespEntry, resourceType, res, theRequestDetails);
@ -399,7 +306,9 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
}
}
} else {
DeleteMethodOutcome deleteOutcome = dao.deleteByUrl(parts.getResourceType() + '?' + parts.getParams(), deleteConflicts, theRequestDetails);
String matchUrl = parts.getResourceType() + '?' + parts.getParams();
matchUrl = performIdSubstitutionsInMatchUrl(idSubstitutions, matchUrl);
DeleteMethodOutcome deleteOutcome = dao.deleteByUrl(matchUrl, deleteConflicts, theRequestDetails);
List<ResourceTable> allDeleted = deleteOutcome.getDeletedEntities();
for (ResourceTable deleted : allDeleted) {
deletedResources.add(deleted.getIdDt().toUnqualifiedVersionless().getValueAsString());
@ -430,6 +339,7 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
} else {
res.setId((String) null);
String matchUrl = parts.getResourceType() + '?' + parts.getParams();
matchUrl = performIdSubstitutionsInMatchUrl(idSubstitutions, matchUrl);
outcome = resourceDao.update(res, matchUrl, false, theRequestDetails);
if (Boolean.TRUE.equals(outcome.getCreated())) {
conditionalRequestUrls.put(matchUrl, res.getClass());
@ -607,6 +517,131 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
return response;
}
private String extractTransactionUrlOrThrowException(BundleEntryComponent nextEntry, HTTPVerb verb) {
String url = nextEntry.getRequest().getUrl();
if (isBlank(url)) {
throw new InvalidRequestException(getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionMissingUrl", verb.name()));
}
return url;
}
/**
* This method is called for nested bundles (e.g. if we received a transaction with an entry that
* was a GET search, this method is called on the bundle for the search result, that will be placed in the
* outer bundle). This method applies the _summary and _content parameters to the output of
* that bundle.
*
* TODO: This isn't the most efficient way of doing this.. hopefully we can come up with something better in the future.
*/
private IBaseResource filterNestedBundle(RequestDetails theRequestDetails, IBaseResource theResource) {
IParser p = getContext().newJsonParser();
RestfulServerUtils.configureResponseParser(theRequestDetails, p);
return p.parseResource(theResource.getClass(), p.encodeResourceToString(theResource));
}
private IFhirResourceDao<?> getDaoOrThrowException(Class<? extends IBaseResource> theClass) {
IFhirResourceDao<? extends IBaseResource> retVal = getDao(theClass);
if (retVal == null) {
throw new InvalidRequestException("Unable to process request, this server does not know how to handle resources of type " + getContext().getResourceDefinition(theClass).getName());
}
return retVal;
}
@Override
public Meta metaGetOperation(RequestDetails theRequestDetails) {
// Notify interceptors
ActionRequestDetails requestDetails = new ActionRequestDetails(theRequestDetails);
notifyInterceptors(RestOperationTypeEnum.META, requestDetails);
String sql = "SELECT d FROM TagDefinition d WHERE d.myId IN (SELECT DISTINCT t.myTagId FROM ResourceTag t)";
TypedQuery<TagDefinition> q = myEntityManager.createQuery(sql, TagDefinition.class);
List<TagDefinition> tagDefinitions = q.getResultList();
Meta retVal = toMeta(tagDefinitions);
return retVal;
}
private String performIdSubstitutionsInMatchUrl(Map<IdType, IdType> theIdSubstitutions, String theMatchUrl) {
String matchUrl = theMatchUrl;
if (isNotBlank(matchUrl)) {
for (Entry<IdType, IdType> nextSubstitutionEntry : theIdSubstitutions.entrySet()) {
IdType nextTemporaryId = nextSubstitutionEntry.getKey();
IdType nextReplacementId = nextSubstitutionEntry.getValue();
String nextTemporaryIdPart = nextTemporaryId.getIdPart();
String nextReplacementIdPart = nextReplacementId.getValueAsString();
if (nextTemporaryId.isUrn() && nextTemporaryIdPart.length() > IdType.URN_PREFIX.length()) {
matchUrl = matchUrl.replace(nextTemporaryIdPart, nextReplacementIdPart);
}
}
}
return matchUrl;
}
private ca.uhn.fhir.jpa.dao.IFhirResourceDao<? extends IBaseResource> toDao(UrlParts theParts, String theVerb, String theUrl) {
RuntimeResourceDefinition resType;
try {
resType = getContext().getResourceDefinition(theParts.getResourceType());
} catch (DataFormatException e) {
String msg = getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionInvalidUrl", theVerb, theUrl);
throw new InvalidRequestException(msg);
}
IFhirResourceDao<? extends IBaseResource> dao = null;
if (resType != null) {
dao = getDao(resType.getImplementingClass());
}
if (dao == null) {
String msg = getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionInvalidUrl", theVerb, theUrl);
throw new InvalidRequestException(msg);
}
// if (theParts.getResourceId() == null && theParts.getParams() == null) {
// String msg = getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionInvalidUrl", theVerb, theUrl);
// throw new InvalidRequestException(msg);
// }
return dao;
}
protected Meta toMeta(Collection<TagDefinition> tagDefinitions) {
Meta retVal = new Meta();
for (TagDefinition next : tagDefinitions) {
switch (next.getTagType()) {
case PROFILE:
retVal.addProfile(next.getCode());
break;
case SECURITY_LABEL:
retVal.addSecurity().setSystem(next.getSystem()).setCode(next.getCode()).setDisplay(next.getDisplay());
break;
case TAG:
retVal.addTag().setSystem(next.getSystem()).setCode(next.getCode()).setDisplay(next.getDisplay());
break;
}
}
return retVal;
}
@Transactional(propagation = Propagation.REQUIRED)
@Override
public Bundle transaction(RequestDetails theRequestDetails, Bundle theRequest) {
if (theRequestDetails != null) {
ActionRequestDetails requestDetails = new ActionRequestDetails(theRequestDetails, theRequest, "Bundle", null);
notifyInterceptors(RestOperationTypeEnum.TRANSACTION, requestDetails);
}
String actionName = "Transaction";
return transaction((ServletRequestDetails) theRequestDetails, theRequest, actionName);
}
private Bundle transaction(ServletRequestDetails theRequestDetails, Bundle theRequest, String theActionName) {
super.markRequestAsProcessingSubRequest(theRequestDetails);
try {
return doTransaction(theRequestDetails, theRequest, theActionName);
} finally {
super.clearRequestAsProcessingSubRequest(theRequestDetails);
}
}
private static void handleTransactionCreateOrUpdateOutcome(Map<IdType, IdType> idSubstitutions, Map<IdType, DaoMethodOutcome> idToPersistedOutcome, IdType nextResourceId, DaoMethodOutcome outcome,
BundleEntryComponent newEntry, String theResourceType, IBaseResource theRes, ServletRequestDetails theRequestDetails) {
IdType newId = (IdType) outcome.getId().toUnqualifiedVersionless();
@ -655,7 +690,6 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
return Integer.toString(theStatusCode) + " " + defaultString(Constants.HTTP_STATUS_NAMES.get(theStatusCode));
}
//@formatter:off
/**
* Transaction Order, per the spec:
*
@ -664,37 +698,94 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
* Process any PUT interactions
* Process any GET interactions
*/
//@formatter:off
public class TransactionSorter implements Comparator<BundleEntryComponent> {
private Set<String> myPlaceholderIds;
public TransactionSorter(Set<String> thePlaceholderIds) {
myPlaceholderIds = thePlaceholderIds;
}
@Override
public int compare(BundleEntryComponent theO1, BundleEntryComponent theO2) {
public int compare(BundleEntryComponent theO1, BundleEntryComponent theO2) {
int o1 = toOrder(theO1);
int o2 = toOrder(theO2);
if (o1 == o2) {
String matchUrl1 = toMatchUrl(theO1);
String matchUrl2 = toMatchUrl(theO2);
if (isBlank(matchUrl1) && isBlank(matchUrl2)) {
return 0;
}
if (isBlank(matchUrl1)) {
return -1;
}
if (isBlank(matchUrl2)) {
return 1;
}
boolean match1containsSubstitutions = false;
boolean match2containsSubstitutions = false;
for (String nextPlaceholder : myPlaceholderIds) {
if (matchUrl1.contains(nextPlaceholder)) {
match1containsSubstitutions = true;
}
if (matchUrl2.contains(nextPlaceholder)) {
match2containsSubstitutions = true;
}
}
if (match1containsSubstitutions && match2containsSubstitutions) {
return 0;
}
if (!match1containsSubstitutions && !match2containsSubstitutions) {
return 0;
}
if (match1containsSubstitutions) {
return 1;
} else {
return -1;
}
}
return o1 - o2;
}
private String toMatchUrl(BundleEntryComponent theEntry) {
HTTPVerb verb = theEntry.getRequest().getMethod();
if (verb == HTTPVerb.POST) {
return theEntry.getRequest().getIfNoneExist();
}
if (verb == HTTPVerb.PUT || verb == HTTPVerb.DELETE) {
String url = extractTransactionUrlOrThrowException(theEntry, verb);
UrlParts parts = UrlUtil.parseUrl(url);
if (isBlank(parts.getResourceId())) {
return parts.getResourceType() + '?' + parts.getParams();
}
}
return null;
}
private int toOrder(BundleEntryComponent theO1) {
int o1 = 0;
if (theO1.getRequest().getMethodElement().getValue() != null) {
switch (theO1.getRequest().getMethodElement().getValue()) {
case DELETE:
o1 = 1;
break;
case POST:
o1 = 2;
break;
case PUT:
o1 = 3;
break;
case GET:
o1 = 4;
break;
case NULL:
o1 = 0;
break;
}
switch (theO1.getRequest().getMethodElement().getValue()) {
case DELETE:
o1 = 1;
break;
case POST:
o1 = 2;
break;
case PUT:
o1 = 3;
break;
case GET:
o1 = 4;
break;
case NULL:
o1 = 0;
break;
}
}
return o1;
}

View File

@ -7,12 +7,21 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import java.io.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.*;
import org.apache.commons.io.IOUtils;
import org.hl7.fhir.dstu3.model.*;
import org.hl7.fhir.dstu3.model.Bundle.*;
import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent;
import org.hl7.fhir.dstu3.model.Bundle.BundleEntryRequestComponent;
import org.hl7.fhir.dstu3.model.Bundle.BundleEntryResponseComponent;
import org.hl7.fhir.dstu3.model.Bundle.BundleType;
import org.hl7.fhir.dstu3.model.Bundle.HTTPVerb;
import org.hl7.fhir.dstu3.model.Observation.ObservationStatus;
import org.hl7.fhir.dstu3.model.OperationOutcome.IssueSeverity;
import org.hl7.fhir.instance.model.api.IAnyResource;
@ -51,6 +60,100 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest {
myDaoConfig.setReuseCachedSearchResultsForMillis(null);
}
private Bundle createInputTransactionWithPlaceholderIdInMatchUrl(HTTPVerb theVerb) {
Patient pat = new Patient();
pat
.addIdentifier()
.setSystem("http://acme.org")
.setValue("ID1");
Observation obs = new Observation();
obs
.getCode()
.addCoding()
.setSystem("http://loinc.org")
.setCode("29463-7");
obs.setEffective(new DateTimeType("2011-09-03T11:13:00-04:00"));
obs.setValue(new Quantity()
.setValue(new BigDecimal("123.4"))
.setCode("kg")
.setSystem("http://unitsofmeasure.org")
.setUnit("kg"));
obs.getSubject().setReference("urn:uuid:0001");
Observation obs2 = new Observation();
obs2
.getCode()
.addCoding()
.setSystem("http://loinc.org")
.setCode("29463-7");
obs2.setEffective(new DateTimeType("2017-09-03T11:13:00-04:00"));
obs2.setValue(new Quantity()
.setValue(new BigDecimal("123.4"))
.setCode("kg")
.setSystem("http://unitsofmeasure.org")
.setUnit("kg"));
obs2.getSubject().setReference("urn:uuid:0001");
/*
* Put one observation before the patient it references, and
* one after it just to make sure that order doesn't matter
*/
Bundle input = new Bundle();
input.setType(BundleType.TRANSACTION);
if (theVerb == HTTPVerb.PUT) {
input
.addEntry()
.setFullUrl("urn:uuid:0002")
.setResource(obs)
.getRequest()
.setMethod(HTTPVerb.PUT)
.setUrl("Observation?subject=urn:uuid:0001&code=http%3A%2F%2Floinc.org|29463-7&date=2011-09-03T11:13:00-04:00");
input
.addEntry()
.setFullUrl("urn:uuid:0001")
.setResource(pat)
.getRequest()
.setMethod(HTTPVerb.PUT)
.setUrl("Patient?identifier=http%3A%2F%2Facme.org|ID1");
input
.addEntry()
.setFullUrl("urn:uuid:0003")
.setResource(obs2)
.getRequest()
.setMethod(HTTPVerb.PUT)
.setUrl("Observation?subject=urn:uuid:0001&code=http%3A%2F%2Floinc.org|29463-7&date=2017-09-03T11:13:00-04:00");
} else if (theVerb == HTTPVerb.POST) {
input
.addEntry()
.setFullUrl("urn:uuid:0002")
.setResource(obs)
.getRequest()
.setMethod(HTTPVerb.POST)
.setUrl("Observation")
.setIfNoneExist("Observation?subject=urn:uuid:0001&code=http%3A%2F%2Floinc.org|29463-7&date=2011-09-03T11:13:00-04:00");
input
.addEntry()
.setFullUrl("urn:uuid:0001")
.setResource(pat)
.getRequest()
.setMethod(HTTPVerb.POST)
.setUrl("Patient")
.setIfNoneExist("Patient?identifier=http%3A%2F%2Facme.org|ID1");
input
.addEntry()
.setFullUrl("urn:uuid:0003")
.setResource(obs2)
.getRequest()
.setMethod(HTTPVerb.POST)
.setUrl("Observation")
.setIfNoneExist("Observation?subject=urn:uuid:0001&code=http%3A%2F%2Floinc.org|29463-7&date=2017-09-03T11:13:00-04:00");
}
return input;
}
@SuppressWarnings("unchecked")
private <T extends org.hl7.fhir.dstu3.model.Resource> T find(Bundle theBundle, Class<T> theType, int theIndex) {
int count = 0;
@ -200,36 +303,36 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest {
rpt = myDiagnosticReportDao.read(rptId);
assertThat(rpt.getResult(), empty());
}
@Test
public void testMultipleUpdatesWithNoChangesDoesNotResultInAnUpdateForTransaction() {
Bundle bundle;
// First time
Patient p = new Patient();
p.setActive(true);
bundle = new Bundle();
bundle.setType(BundleType.TRANSACTION);
bundle
.addEntry()
.setResource(p)
.setFullUrl("Patient/A")
.getRequest()
.addEntry()
.setResource(p)
.setFullUrl("Patient/A")
.getRequest()
.setMethod(HTTPVerb.PUT)
.setUrl("Patient/A");
Bundle resp = mySystemDao.transaction(mySrd, bundle);
assertThat(resp.getEntry().get(0).getResponse().getLocation(), endsWith("Patient/A/_history/1"));
// Second time should not result in an update
p = new Patient();
p.setActive(true);
bundle = new Bundle();
bundle.setType(BundleType.TRANSACTION);
bundle
.addEntry()
.setResource(p)
.setFullUrl("Patient/A")
.getRequest()
.addEntry()
.setResource(p)
.setFullUrl("Patient/A")
.getRequest()
.setMethod(HTTPVerb.PUT)
.setUrl("Patient/A");
resp = mySystemDao.transaction(mySrd, bundle);
@ -241,10 +344,10 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest {
bundle = new Bundle();
bundle.setType(BundleType.TRANSACTION);
bundle
.addEntry()
.setResource(p)
.setFullUrl("Patient/A")
.getRequest()
.addEntry()
.setResource(p)
.setFullUrl("Patient/A")
.getRequest()
.setMethod(HTTPVerb.PUT)
.setUrl("Patient/A");
resp = mySystemDao.transaction(mySrd, bundle);
@ -256,13 +359,13 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest {
myPatientDao.read(new IdType("Patient/A/_history/2"));
fail();
} catch (ResourceNotFoundException e) {
//good
// good
}
try {
myPatientDao.read(new IdType("Patient/A/_history/3"));
fail();
} catch (ResourceNotFoundException e) {
//good
// good
}
}
@ -487,60 +590,6 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest {
}
}
@Test
public void testTransactionWithReferenceUuid() {
Bundle request = new Bundle();
Patient p = new Patient();
p.setActive(true);
p.setId(IdType.newRandomUuid());
request.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.POST).setUrl(p.getId());
Observation o = new Observation();
o.getCode().setText("Some Observation");
o.getSubject().setReference(p.getId());
request.addEntry().setResource(o).getRequest().setMethod(HTTPVerb.POST);
Bundle resp = mySystemDao.transaction(mySrd, request);
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(resp));
String patientId = new IdType(resp.getEntry().get(0).getResponse().getLocation()).toUnqualifiedVersionless().getValue();
assertThat(patientId, startsWith("Patient/"));
SearchParameterMap params = new SearchParameterMap();
params.setLoadSynchronous(true);
params.add("subject", new ReferenceParam(patientId));
IBundleProvider found = myObservationDao.search(params);
assertEquals(1, found.size().intValue());
}
@Test
public void testTransactionWithReferenceResource() {
Bundle request = new Bundle();
Patient p = new Patient();
p.setActive(true);
p.setId(IdType.newRandomUuid());
request.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.POST).setUrl(p.getId());
Observation o = new Observation();
o.getCode().setText("Some Observation");
o.getSubject().setResource(p);
request.addEntry().setResource(o).getRequest().setMethod(HTTPVerb.POST);
Bundle resp = mySystemDao.transaction(mySrd, request);
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(resp));
String patientId = new IdType(resp.getEntry().get(0).getResponse().getLocation()).toUnqualifiedVersionless().getValue();
assertThat(patientId, startsWith("Patient/"));
SearchParameterMap params = new SearchParameterMap();
params.setLoadSynchronous(true);
params.add("subject", new ReferenceParam(patientId));
IBundleProvider found = myObservationDao.search(params);
assertEquals(1, found.size().intValue());
}
@Test
public void testTransactionCreateInlineMatchUrlWithOneMatch() {
String methodName = "testTransactionCreateInlineMatchUrlWithOneMatch";
@ -1335,7 +1384,8 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest {
mySystemDao.transaction(mySrd, inputBundle);
fail();
} catch (InvalidRequestException e) {
assertEquals("Unable to process Transaction - Request would cause multiple resources to match URL: \"Encounter?identifier=urn:foo|12345\". Does transaction request contain duplicates?", e.getMessage());
assertEquals("Unable to process Transaction - Request would cause multiple resources to match URL: \"Encounter?identifier=urn:foo|12345\". Does transaction request contain duplicates?",
e.getMessage());
}
}
@ -1365,9 +1415,10 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest {
mySystemDao.transaction(mySrd, inputBundle);
fail();
} catch (InvalidRequestException e) {
assertEquals("Unable to process Transaction - Request would cause multiple resources to match URL: \"Encounter?identifier=urn:foo|12345\". Does transaction request contain duplicates?", e.getMessage());
assertEquals("Unable to process Transaction - Request would cause multiple resources to match URL: \"Encounter?identifier=urn:foo|12345\". Does transaction request contain duplicates?",
e.getMessage());
}
}
@Test(expected = InvalidRequestException.class)
@ -2026,8 +2077,7 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest {
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(resp));
assertEquals("201 Created", resp.getEntry().get(0).getResponse().getStatus());
new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus theStatus) {
@ -2040,7 +2090,7 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest {
}
}
});
}
@Test
@ -2101,6 +2151,66 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest {
assertEquals(id0.toUnqualifiedVersionless().getValue(), app2.getParticipant().get(1).getActor().getReference());
}
/*
* Make sure we are able to handle placeholder IDs in match URLs, e.g.
*
* "request": {
* "method": "PUT",
* "url": "Observation?subject=urn:uuid:8dba64a8-2aca-48fe-8b4e-8c7bf2ab695a&code=http%3A%2F%2Floinc.org|29463-7&date=2011-09-03T11:13:00-04:00"
* }
* </pre>
*/
@Test
public void testTransactionWithPlaceholderIdInMatchUrlPut() {
Bundle input = createInputTransactionWithPlaceholderIdInMatchUrl(HTTPVerb.PUT);
Bundle output = mySystemDao.transaction(null, input);
assertEquals("201 Created", output.getEntry().get(0).getResponse().getStatus());
assertEquals("201 Created", output.getEntry().get(1).getResponse().getStatus());
assertEquals("201 Created", output.getEntry().get(2).getResponse().getStatus());
Bundle input2 = createInputTransactionWithPlaceholderIdInMatchUrl(HTTPVerb.PUT);
Bundle output2 = mySystemDao.transaction(null, input2);
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(output2));
assertEquals("200 OK", output2.getEntry().get(0).getResponse().getStatus());
assertEquals("200 OK", output2.getEntry().get(1).getResponse().getStatus());
assertEquals("200 OK", output2.getEntry().get(2).getResponse().getStatus());
}
/*
* Make sure we are able to handle placeholder IDs in match URLs, e.g.
*
* "request": {
* "method": "PUT",
* "url": "Observation?subject=urn:uuid:8dba64a8-2aca-48fe-8b4e-8c7bf2ab695a&code=http%3A%2F%2Floinc.org|29463-7&date=2011-09-03T11:13:00-04:00"
* }
* </pre>
*/
@Test
public void testTransactionWithPlaceholderIdInMatchUrlPost() {
Bundle input = createInputTransactionWithPlaceholderIdInMatchUrl(HTTPVerb.POST);
Bundle output = mySystemDao.transaction(null, input);
assertEquals("201 Created", output.getEntry().get(0).getResponse().getStatus());
assertEquals("201 Created", output.getEntry().get(1).getResponse().getStatus());
assertEquals("201 Created", output.getEntry().get(2).getResponse().getStatus());
Bundle input2 = createInputTransactionWithPlaceholderIdInMatchUrl(HTTPVerb.POST);
Bundle output2 = mySystemDao.transaction(null, input2);
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(output2));
assertEquals("200 OK", output2.getEntry().get(0).getResponse().getStatus());
assertEquals("200 OK", output2.getEntry().get(1).getResponse().getStatus());
assertEquals("200 OK", output2.getEntry().get(2).getResponse().getStatus());
}
/**
* Per a message on the mailing list
*/
@ -2137,6 +2247,86 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest {
assertEquals("Joshua", patient.getNameFirstRep().getGivenAsSingleString());
}
@Test
public void testTransactionWithReferenceResource() {
Bundle request = new Bundle();
Patient p = new Patient();
p.setActive(true);
p.setId(IdType.newRandomUuid());
request.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.POST).setUrl(p.getId());
Observation o = new Observation();
o.getCode().setText("Some Observation");
o.getSubject().setResource(p);
request.addEntry().setResource(o).getRequest().setMethod(HTTPVerb.POST);
Bundle resp = mySystemDao.transaction(mySrd, request);
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(resp));
String patientId = new IdType(resp.getEntry().get(0).getResponse().getLocation()).toUnqualifiedVersionless().getValue();
assertThat(patientId, startsWith("Patient/"));
SearchParameterMap params = new SearchParameterMap();
params.setLoadSynchronous(true);
params.add("subject", new ReferenceParam(patientId));
IBundleProvider found = myObservationDao.search(params);
assertEquals(1, found.size().intValue());
}
@Test
public void testTransactionWithReferenceToCreateIfNoneExist() {
Bundle bundle = new Bundle();
bundle.setType(BundleType.TRANSACTION);
Medication med = new Medication();
IdType medId = IdType.newRandomUuid();
med.setId(medId);
med.getCode().addCoding().setSystem("billscodes").setCode("theCode");
bundle.addEntry().setResource(med).setFullUrl(medId.getValue()).getRequest().setMethod(HTTPVerb.POST).setIfNoneExist("Medication?code=billscodes|theCode");
MedicationRequest mo = new MedicationRequest();
mo.setMedication(new Reference(medId));
bundle.addEntry().setResource(mo).setFullUrl(mo.getIdElement().getValue()).getRequest().setMethod(HTTPVerb.POST);
ourLog.info("Request:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(bundle));
Bundle outcome = mySystemDao.transaction(mySrd, bundle);
ourLog.info("Response:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
IdType medId1 = new IdType(outcome.getEntry().get(0).getResponse().getLocation());
IdType medOrderId1 = new IdType(outcome.getEntry().get(1).getResponse().getLocation());
/*
* Again!
*/
bundle = new Bundle();
bundle.setType(BundleType.TRANSACTION);
med = new Medication();
medId = IdType.newRandomUuid();
med.getCode().addCoding().setSystem("billscodes").setCode("theCode");
bundle.addEntry().setResource(med).setFullUrl(medId.getValue()).getRequest().setMethod(HTTPVerb.POST).setIfNoneExist("Medication?code=billscodes|theCode");
mo = new MedicationRequest();
mo.setMedication(new Reference(medId));
bundle.addEntry().setResource(mo).setFullUrl(mo.getIdElement().getValue()).getRequest().setMethod(HTTPVerb.POST);
outcome = mySystemDao.transaction(mySrd, bundle);
IdType medId2 = new IdType(outcome.getEntry().get(0).getResponse().getLocation());
IdType medOrderId2 = new IdType(outcome.getEntry().get(1).getResponse().getLocation());
assertTrue(medId1.isIdPartValidLong());
assertTrue(medId2.isIdPartValidLong());
assertTrue(medOrderId1.isIdPartValidLong());
assertTrue(medOrderId2.isIdPartValidLong());
assertEquals(medId1, medId2);
assertNotEquals(medOrderId1, medOrderId2);
}
//
//
// /**
@ -2240,59 +2430,32 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest {
// }
@Test
public void testTransactionWithReferenceToCreateIfNoneExist() {
Bundle bundle = new Bundle();
bundle.setType(BundleType.TRANSACTION);
public void testTransactionWithReferenceUuid() {
Bundle request = new Bundle();
Medication med = new Medication();
IdType medId = IdType.newRandomUuid();
med.setId(medId);
med.getCode().addCoding().setSystem("billscodes").setCode("theCode");
bundle.addEntry().setResource(med).setFullUrl(medId.getValue()).getRequest().setMethod(HTTPVerb.POST).setIfNoneExist("Medication?code=billscodes|theCode");
Patient p = new Patient();
p.setActive(true);
p.setId(IdType.newRandomUuid());
request.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.POST).setUrl(p.getId());
MedicationRequest mo = new MedicationRequest();
mo.setMedication(new Reference(medId));
bundle.addEntry().setResource(mo).setFullUrl(mo.getIdElement().getValue()).getRequest().setMethod(HTTPVerb.POST);
Observation o = new Observation();
o.getCode().setText("Some Observation");
o.getSubject().setReference(p.getId());
request.addEntry().setResource(o).getRequest().setMethod(HTTPVerb.POST);
ourLog.info("Request:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(bundle));
Bundle resp = mySystemDao.transaction(mySrd, request);
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(resp));
Bundle outcome = mySystemDao.transaction(mySrd, bundle);
ourLog.info("Response:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
String patientId = new IdType(resp.getEntry().get(0).getResponse().getLocation()).toUnqualifiedVersionless().getValue();
assertThat(patientId, startsWith("Patient/"));
IdType medId1 = new IdType(outcome.getEntry().get(0).getResponse().getLocation());
IdType medOrderId1 = new IdType(outcome.getEntry().get(1).getResponse().getLocation());
/*
* Again!
*/
bundle = new Bundle();
bundle.setType(BundleType.TRANSACTION);
med = new Medication();
medId = IdType.newRandomUuid();
med.getCode().addCoding().setSystem("billscodes").setCode("theCode");
bundle.addEntry().setResource(med).setFullUrl(medId.getValue()).getRequest().setMethod(HTTPVerb.POST).setIfNoneExist("Medication?code=billscodes|theCode");
mo = new MedicationRequest();
mo.setMedication(new Reference(medId));
bundle.addEntry().setResource(mo).setFullUrl(mo.getIdElement().getValue()).getRequest().setMethod(HTTPVerb.POST);
outcome = mySystemDao.transaction(mySrd, bundle);
IdType medId2 = new IdType(outcome.getEntry().get(0).getResponse().getLocation());
IdType medOrderId2 = new IdType(outcome.getEntry().get(1).getResponse().getLocation());
assertTrue(medId1.isIdPartValidLong());
assertTrue(medId2.isIdPartValidLong());
assertTrue(medOrderId1.isIdPartValidLong());
assertTrue(medOrderId2.isIdPartValidLong());
assertEquals(medId1, medId2);
assertNotEquals(medOrderId1, medOrderId2);
SearchParameterMap params = new SearchParameterMap();
params.setLoadSynchronous(true);
params.add("subject", new ReferenceParam(patientId));
IBundleProvider found = myObservationDao.search(params);
assertEquals(1, found.size().intValue());
}
@Test
public void testTransactionWithRelativeOidIds() throws Exception {
Bundle res = new Bundle();
@ -2330,7 +2493,6 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest {
assertThat(o2.getSubject().getReferenceElement().getValue(), endsWith("Patient/" + p1.getIdElement().getIdPart()));
}
/**
* This is not the correct way to do it, but we'll allow it to be lenient

View File

@ -86,6 +86,8 @@ import ca.uhn.fhir.model.api.annotation.DatatypeDef;
*/
@DatatypeDef(name = "id", profileOf=StringType.class)
public final class IdType extends UriType implements IPrimitiveType<String>, IIdType {
public static final String URN_PREFIX = "urn:";
/**
* This is the maximum length for the ID
*/
@ -486,8 +488,8 @@ public final class IdType extends UriType implements IPrimitiveType<String>, IId
return defaultString(myUnqualifiedId).startsWith("#");
}
private boolean isUrn() {
return defaultString(myUnqualifiedId).startsWith("urn:");
public boolean isUrn() {
return defaultString(myUnqualifiedId).startsWith(URN_PREFIX);
}
@Override
@ -526,7 +528,7 @@ public final class IdType extends UriType implements IPrimitiveType<String>, IId
myUnqualifiedVersionId = null;
myResourceType = null;
myHaveComponentParts = true;
} else if (theValue.startsWith("urn:")) {
} else if (theValue.startsWith(URN_PREFIX)) {
myBaseUrl = null;
myUnqualifiedId = theValue;
myUnqualifiedVersionId = null;

View File

@ -13,6 +13,11 @@
meant that any project using ookhttp would import both structures
JARs. This has been removed.
</action>
<action type="add">
JPA server is now able to handle placeholder IDs (e.g. urn:uuid:00....000)
being used in Bundle.entry.request.url as a part of the conditional URL
within transactions.
</action>
</release
<release version="2.6" date="TBD">
<action type="add">