Allow patching in tranactions
This commit is contained in:
parent
87ab796309
commit
5bf4fa22e7
|
@ -21,13 +21,16 @@ package ca.uhn.fhir.rest.api;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import ca.uhn.fhir.rest.annotation.Patch;
|
import ca.uhn.fhir.rest.annotation.Patch;
|
||||||
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
|
import ca.uhn.fhir.util.UrlUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parameter type for methods annotated with {@link Patch}
|
* Parameter type for methods annotated with {@link Patch}
|
||||||
*/
|
*/
|
||||||
public enum PatchTypeEnum {
|
public enum PatchTypeEnum {
|
||||||
|
|
||||||
JSON_PATCH(Constants.CT_JSON_PATCH), XML_PATCH(Constants.CT_XML_PATCH);
|
JSON_PATCH(Constants.CT_JSON_PATCH),
|
||||||
|
XML_PATCH(Constants.CT_XML_PATCH);
|
||||||
|
|
||||||
private final String myContentType;
|
private final String myContentType;
|
||||||
|
|
||||||
|
@ -39,4 +42,19 @@ public enum PatchTypeEnum {
|
||||||
return myContentType;
|
return myContentType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static PatchTypeEnum forContentTypeOrThrowInvalidRequestException(String theContentType) {
|
||||||
|
String contentType = theContentType;
|
||||||
|
int semiColonIdx = contentType.indexOf(';');
|
||||||
|
if (semiColonIdx != -1) {
|
||||||
|
contentType = theContentType.substring(0, semiColonIdx);
|
||||||
|
}
|
||||||
|
contentType = contentType.trim();
|
||||||
|
if (Constants.CT_JSON_PATCH.equals(contentType)) {
|
||||||
|
return JSON_PATCH;
|
||||||
|
} else if (Constants.CT_XML_PATCH.equals(contentType)) {
|
||||||
|
return XML_PATCH;
|
||||||
|
} else {
|
||||||
|
throw new InvalidRequestException("Invalid Content-Type for PATCH operation: " + UrlUtil.sanitizeUrlPart(theContentType));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -97,6 +97,8 @@ ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.successfulDeletes=Successfully delet
|
||||||
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.invalidSearchParameter=Unknown search parameter "{0}". Value search parameters for this search are: {1}
|
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.invalidSearchParameter=Unknown search parameter "{0}". Value search parameters for this search are: {1}
|
||||||
|
|
||||||
ca.uhn.fhir.jpa.dao.TransactionProcessor.missingMandatoryResource=Missing required resource in Bundle.entry[{1}].resource for operation {0}
|
ca.uhn.fhir.jpa.dao.TransactionProcessor.missingMandatoryResource=Missing required resource in Bundle.entry[{1}].resource for operation {0}
|
||||||
|
ca.uhn.fhir.jpa.dao.TransactionProcessor.missingPatchContentType=Missing or invalid content type for PATCH operation
|
||||||
|
ca.uhn.fhir.jpa.dao.TransactionProcessor.missingPatchBody=Unable to determine PATCH body from request
|
||||||
|
|
||||||
ca.uhn.fhir.jpa.searchparam.extractor.BaseSearchParamExtractor.externalReferenceNotAllowed=Resource contains external reference to URL "{0}" but this server is not configured to allow external references
|
ca.uhn.fhir.jpa.searchparam.extractor.BaseSearchParamExtractor.externalReferenceNotAllowed=Resource contains external reference to URL "{0}" but this server is not configured to allow external references
|
||||||
ca.uhn.fhir.jpa.searchparam.extractor.BaseSearchParamExtractor.failedToExtractPaths=Failed to extract values from resource using FHIRPath "{0}": {1}
|
ca.uhn.fhir.jpa.searchparam.extractor.BaseSearchParamExtractor.failedToExtractPaths=Failed to extract values from resource using FHIRPath "{0}": {1}
|
||||||
|
|
|
@ -37,10 +37,7 @@ import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster;
|
||||||
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
|
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
|
||||||
import ca.uhn.fhir.parser.DataFormatException;
|
import ca.uhn.fhir.parser.DataFormatException;
|
||||||
import ca.uhn.fhir.parser.IParser;
|
import ca.uhn.fhir.parser.IParser;
|
||||||
import ca.uhn.fhir.rest.api.Constants;
|
import ca.uhn.fhir.rest.api.*;
|
||||||
import ca.uhn.fhir.rest.api.PreferReturnEnum;
|
|
||||||
import ca.uhn.fhir.rest.api.RequestTypeEnum;
|
|
||||||
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
|
||||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
import ca.uhn.fhir.rest.param.ParameterUtil;
|
import ca.uhn.fhir.rest.param.ParameterUtil;
|
||||||
import ca.uhn.fhir.rest.server.RestfulServerUtils;
|
import ca.uhn.fhir.rest.server.RestfulServerUtils;
|
||||||
|
@ -53,6 +50,7 @@ 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.*;
|
import ca.uhn.fhir.util.*;
|
||||||
|
import com.google.common.base.Charsets;
|
||||||
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;
|
||||||
|
@ -755,9 +753,50 @@ public class TransactionProcessor<BUNDLE extends IBaseBundle, BUNDLEENTRY> {
|
||||||
entriesToProcess.put(nextRespEntry, outcome.getEntity());
|
entriesToProcess.put(nextRespEntry, outcome.getEntity());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "GET":
|
case "PATCH": {
|
||||||
default:
|
// PATCH
|
||||||
|
validateResourcePresent(res, order, verb);
|
||||||
|
|
||||||
|
String url = extractTransactionUrlOrThrowException(nextReqEntry, verb);
|
||||||
|
UrlUtil.UrlParts parts = UrlUtil.parseUrl(url);
|
||||||
|
|
||||||
|
String matchUrl = toMatchUrl(nextReqEntry);
|
||||||
|
matchUrl = performIdSubstitutionsInMatchUrl(theIdSubstitutions, matchUrl);
|
||||||
|
String patchBody = null;
|
||||||
|
String contentType = null;
|
||||||
|
|
||||||
|
if (res instanceof IBaseBinary) {
|
||||||
|
IBaseBinary binary = (IBaseBinary) res;
|
||||||
|
if (binary.getContent() != null && binary.getContent().length > 0) {
|
||||||
|
patchBody = new String(binary.getContent(), Charsets.UTF_8);
|
||||||
|
}
|
||||||
|
contentType = binary.getContentType();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isBlank(patchBody)) {
|
||||||
|
String msg = myContext.getLocalizer().getMessage(TransactionProcessor.class, "missingPatchBody");
|
||||||
|
throw new InvalidRequestException(msg);
|
||||||
|
}
|
||||||
|
if (isBlank(contentType)) {
|
||||||
|
String msg = myContext.getLocalizer().getMessage(TransactionProcessor.class, "missingPatchContentType");
|
||||||
|
throw new InvalidRequestException(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
ca.uhn.fhir.jpa.dao.IFhirResourceDao<? extends IBaseResource> dao = toDao(parts, verb, url);
|
||||||
|
PatchTypeEnum patchType = PatchTypeEnum.forContentTypeOrThrowInvalidRequestException(contentType);
|
||||||
|
IIdType patchId = myContext.getVersion().newIdType().setValue(parts.getResourceId());
|
||||||
|
DaoMethodOutcome outcome = dao.patch(patchId, matchUrl, patchType, patchBody, theRequest);
|
||||||
|
updatedEntities.add(outcome.getEntity());
|
||||||
|
if (outcome.getResource() != null) {
|
||||||
|
updatedResources.add(outcome.getResource());
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
case "GET":
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new InvalidRequestException("Unable to handle verb in transaction: " + verb);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1051,6 +1090,7 @@ public class TransactionProcessor<BUNDLE extends IBaseBundle, BUNDLEENTRY> {
|
||||||
* Process any DELETE interactions
|
* Process any DELETE interactions
|
||||||
* Process any POST interactions
|
* Process any POST interactions
|
||||||
* Process any PUT interactions
|
* Process any PUT interactions
|
||||||
|
* Process any PATCH interactions
|
||||||
* Process any GET interactions
|
* Process any GET interactions
|
||||||
*/
|
*/
|
||||||
//@formatter:off
|
//@formatter:off
|
||||||
|
@ -1107,21 +1147,6 @@ public class TransactionProcessor<BUNDLE extends IBaseBundle, BUNDLEENTRY> {
|
||||||
return o1 - o2;
|
return o1 - o2;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String toMatchUrl(BUNDLEENTRY theEntry) {
|
|
||||||
String verb = myVersionAdapter.getEntryRequestVerb(theEntry);
|
|
||||||
if (verb.equals("POST")) {
|
|
||||||
return myVersionAdapter.getEntryIfNoneExist(theEntry);
|
|
||||||
}
|
|
||||||
if (verb.equals("PUT") || verb.equals("DELETE")) {
|
|
||||||
String url = extractTransactionUrlOrThrowException(theEntry, verb);
|
|
||||||
UrlUtil.UrlParts parts = UrlUtil.parseUrl(url);
|
|
||||||
if (isBlank(parts.getResourceId())) {
|
|
||||||
return parts.getResourceType() + '?' + parts.getParams();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int toOrder(BUNDLEENTRY theO1) {
|
private int toOrder(BUNDLEENTRY theO1) {
|
||||||
int o1 = 0;
|
int o1 = 0;
|
||||||
if (myVersionAdapter.getEntryRequestVerb(theO1) != null) {
|
if (myVersionAdapter.getEntryRequestVerb(theO1) != null) {
|
||||||
|
@ -1135,9 +1160,12 @@ public class TransactionProcessor<BUNDLE extends IBaseBundle, BUNDLEENTRY> {
|
||||||
case "PUT":
|
case "PUT":
|
||||||
o1 = 3;
|
o1 = 3;
|
||||||
break;
|
break;
|
||||||
case "GET":
|
case "PATCH":
|
||||||
o1 = 4;
|
o1 = 4;
|
||||||
break;
|
break;
|
||||||
|
case "GET":
|
||||||
|
o1 = 5;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
o1 = 0;
|
o1 = 0;
|
||||||
break;
|
break;
|
||||||
|
@ -1171,4 +1199,22 @@ public class TransactionProcessor<BUNDLE extends IBaseBundle, BUNDLEENTRY> {
|
||||||
return Integer.toString(theStatusCode) + " " + defaultString(Constants.HTTP_STATUS_NAMES.get(theStatusCode));
|
return Integer.toString(theStatusCode) + " " + defaultString(Constants.HTTP_STATUS_NAMES.get(theStatusCode));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String toMatchUrl(BUNDLEENTRY theEntry) {
|
||||||
|
String verb = myVersionAdapter.getEntryRequestVerb(theEntry);
|
||||||
|
if (verb.equals("POST")) {
|
||||||
|
return myVersionAdapter.getEntryIfNoneExist(theEntry);
|
||||||
|
}
|
||||||
|
if (verb.equals("PATCH")) {
|
||||||
|
return myVersionAdapter.getEntryRequestIfMatch(theEntry);
|
||||||
|
}
|
||||||
|
if (verb.equals("PUT") || verb.equals("DELETE")) {
|
||||||
|
String url = extractTransactionUrlOrThrowException(theEntry, verb);
|
||||||
|
UrlUtil.UrlParts parts = UrlUtil.parseUrl(url);
|
||||||
|
if (isBlank(parts.getResourceId())) {
|
||||||
|
return parts.getResourceType() + '?' + parts.getParams();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -130,10 +130,12 @@ public class JpaValidationSupportR4 implements IJpaValidationSupportR4, Applicat
|
||||||
}
|
}
|
||||||
} else if ("StructureDefinition".equals(resourceName)) {
|
} else if ("StructureDefinition".equals(resourceName)) {
|
||||||
// Don't allow the core FHIR definitions to be overwritten
|
// Don't allow the core FHIR definitions to be overwritten
|
||||||
|
if (theUri.startsWith("http://hl7.org/fhir/StructureDefinition/")) {
|
||||||
String typeName = theUri.substring("http://hl7.org/fhir/StructureDefinition/".length());
|
String typeName = theUri.substring("http://hl7.org/fhir/StructureDefinition/".length());
|
||||||
if (myR4Ctx.getElementDefinition(typeName) != null) {
|
if (myR4Ctx.getElementDefinition(typeName) != null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
SearchParameterMap params = new SearchParameterMap();
|
SearchParameterMap params = new SearchParameterMap();
|
||||||
params.setLoadSynchronousUpTo(1);
|
params.setLoadSynchronousUpTo(1);
|
||||||
params.add(StructureDefinition.SP_URL, new UriParam(theUri));
|
params.add(StructureDefinition.SP_URL, new UriParam(theUri));
|
||||||
|
|
|
@ -308,6 +308,9 @@ public class BinaryAccessProviderR4Test extends BaseResourceProviderR4Test {
|
||||||
if (theSetData) {
|
if (theSetData) {
|
||||||
attachment.setData(SOME_BYTES_2);
|
attachment.setData(SOME_BYTES_2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(documentReference));
|
||||||
|
|
||||||
return ourClient.create().resource(documentReference).execute().getId().toUnqualifiedVersionless();
|
return ourClient.create().resource(documentReference).execute().getId().toUnqualifiedVersionless();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
package ca.uhn.fhir.jpa.provider.r4;
|
package ca.uhn.fhir.jpa.provider.r4;
|
||||||
|
|
||||||
import ca.uhn.fhir.rest.api.Constants;
|
import ca.uhn.fhir.rest.api.Constants;
|
||||||
|
import com.google.common.base.Charsets;
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||||
import org.apache.http.client.methods.HttpPatch;
|
import org.apache.http.client.methods.HttpPatch;
|
||||||
|
import org.apache.http.client.methods.HttpPost;
|
||||||
import org.apache.http.entity.ContentType;
|
import org.apache.http.entity.ContentType;
|
||||||
import org.apache.http.entity.StringEntity;
|
import org.apache.http.entity.StringEntity;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
import org.hl7.fhir.r4.model.Media;
|
import org.hl7.fhir.r4.model.*;
|
||||||
import org.hl7.fhir.r4.model.Observation;
|
|
||||||
import org.hl7.fhir.r4.model.Patient;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
@ -93,6 +93,48 @@ public class PatchProviderR4Test extends BaseResourceProviderR4Test {
|
||||||
assertEquals(false, newPt.getActive());
|
assertEquals(false, newPt.getActive());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPatchUsingJsonPatch_Transaction() throws Exception {
|
||||||
|
String methodName = "testPatchUsingJsonPatch_Transaction";
|
||||||
|
IIdType pid1;
|
||||||
|
{
|
||||||
|
Patient patient = new Patient();
|
||||||
|
patient.setActive(true);
|
||||||
|
patient.addIdentifier().setSystem("urn:system").setValue("0");
|
||||||
|
patient.addName().setFamily(methodName).addGiven("Joe");
|
||||||
|
pid1 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
|
||||||
|
}
|
||||||
|
|
||||||
|
String patchString = "[ { \"op\":\"replace\", \"path\":\"/active\", \"value\":false } ]";
|
||||||
|
Binary patch = new Binary();
|
||||||
|
patch.setContentType(Constants.CT_JSON_PATCH);
|
||||||
|
patch.setContent(patchString.getBytes(Charsets.UTF_8));
|
||||||
|
|
||||||
|
Bundle input = new Bundle();
|
||||||
|
input.setType(Bundle.BundleType.TRANSACTION);
|
||||||
|
input.addEntry()
|
||||||
|
.setFullUrl(pid1.getValue())
|
||||||
|
.setResource(patch)
|
||||||
|
.getRequest().setUrl(pid1.getValue())
|
||||||
|
.setMethod(Bundle.HTTPVerb.PATCH);
|
||||||
|
|
||||||
|
HttpPost post = new HttpPost(ourServerBase);
|
||||||
|
String encodedRequest = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(input);
|
||||||
|
ourLog.info("Requet:\n{}", encodedRequest);
|
||||||
|
post.setEntity(new StringEntity(encodedRequest, ContentType.parse(Constants.CT_FHIR_JSON_NEW+ Constants.CHARSET_UTF8_CTSUFFIX)));
|
||||||
|
try (CloseableHttpResponse response = ourHttpClient.execute(post)) {
|
||||||
|
assertEquals(200, response.getStatusLine().getStatusCode());
|
||||||
|
String responseString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
|
||||||
|
assertThat(responseString, containsString("\"resourceType\":\"Bundle\""));
|
||||||
|
}
|
||||||
|
|
||||||
|
Patient newPt = ourClient.read().resource(Patient.class).withId(pid1.getIdPart()).execute();
|
||||||
|
assertEquals("2", newPt.getIdElement().getVersionIdPart());
|
||||||
|
assertEquals(false, newPt.getActive());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testPatchUsingJsonPatch_Conditional_Success() throws Exception {
|
public void testPatchUsingJsonPatch_Conditional_Success() throws Exception {
|
||||||
String methodName = "testPatchUsingJsonPatch";
|
String methodName = "testPatchUsingJsonPatch";
|
||||||
|
@ -303,4 +345,158 @@ public class PatchProviderR4Test extends BaseResourceProviderR4Test {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPatchUsingXmlPatch_Transaction() throws Exception {
|
||||||
|
String methodName = "testPatchUsingXmlPatch_Transaction";
|
||||||
|
IIdType pid1;
|
||||||
|
{
|
||||||
|
Patient patient = new Patient();
|
||||||
|
patient.setActive(true);
|
||||||
|
patient.addIdentifier().setSystem("urn:system").setValue("0");
|
||||||
|
patient.addName().setFamily(methodName).addGiven("Joe");
|
||||||
|
pid1 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
|
||||||
|
}
|
||||||
|
|
||||||
|
String patchString = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><diff xmlns:fhir=\"http://hl7.org/fhir\"><replace sel=\"fhir:Patient/fhir:active/@value\">false</replace></diff>";
|
||||||
|
Binary patch = new Binary();
|
||||||
|
patch.setContentType(Constants.CT_XML_PATCH);
|
||||||
|
patch.setContent(patchString.getBytes(Charsets.UTF_8));
|
||||||
|
|
||||||
|
Bundle input = new Bundle();
|
||||||
|
input.setType(Bundle.BundleType.TRANSACTION);
|
||||||
|
input.addEntry()
|
||||||
|
.setFullUrl(pid1.getValue())
|
||||||
|
.setResource(patch)
|
||||||
|
.getRequest().setUrl(pid1.getValue())
|
||||||
|
.setMethod(Bundle.HTTPVerb.PATCH);
|
||||||
|
|
||||||
|
HttpPost post = new HttpPost(ourServerBase);
|
||||||
|
post.setEntity(new StringEntity(myFhirCtx.newJsonParser().encodeResourceToString(input), ContentType.parse(Constants.CT_FHIR_JSON_NEW+ Constants.CHARSET_UTF8_CTSUFFIX)));
|
||||||
|
try (CloseableHttpResponse response = ourHttpClient.execute(post)) {
|
||||||
|
assertEquals(200, response.getStatusLine().getStatusCode());
|
||||||
|
String responseString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
|
||||||
|
assertThat(responseString, containsString("\"resourceType\":\"Bundle\""));
|
||||||
|
}
|
||||||
|
|
||||||
|
Patient newPt = ourClient.read().resource(Patient.class).withId(pid1.getIdPart()).execute();
|
||||||
|
assertEquals("2", newPt.getIdElement().getVersionIdPart());
|
||||||
|
assertEquals(false, newPt.getActive());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPatchInTransaction_MissingContentType() throws Exception {
|
||||||
|
String methodName = "testPatchUsingJsonPatch_Transaction";
|
||||||
|
IIdType pid1;
|
||||||
|
{
|
||||||
|
Patient patient = new Patient();
|
||||||
|
patient.setActive(true);
|
||||||
|
patient.addIdentifier().setSystem("urn:system").setValue("0");
|
||||||
|
patient.addName().setFamily(methodName).addGiven("Joe");
|
||||||
|
pid1 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
|
||||||
|
}
|
||||||
|
|
||||||
|
String patchString = "[ { \"op\":\"replace\", \"path\":\"/active\", \"value\":false } ]";
|
||||||
|
Binary patch = new Binary();
|
||||||
|
patch.setContent(patchString.getBytes(Charsets.UTF_8));
|
||||||
|
|
||||||
|
Bundle input = new Bundle();
|
||||||
|
input.setType(Bundle.BundleType.TRANSACTION);
|
||||||
|
input.addEntry()
|
||||||
|
.setFullUrl(pid1.getValue())
|
||||||
|
.setResource(patch)
|
||||||
|
.getRequest().setUrl(pid1.getValue())
|
||||||
|
.setMethod(Bundle.HTTPVerb.PATCH);
|
||||||
|
|
||||||
|
HttpPost post = new HttpPost(ourServerBase);
|
||||||
|
post.setEntity(new StringEntity(myFhirCtx.newJsonParser().encodeResourceToString(input), ContentType.parse(Constants.CT_FHIR_JSON_NEW+ Constants.CHARSET_UTF8_CTSUFFIX)));
|
||||||
|
try (CloseableHttpResponse response = ourHttpClient.execute(post)) {
|
||||||
|
assertEquals(400, response.getStatusLine().getStatusCode());
|
||||||
|
String responseString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
|
||||||
|
assertThat(responseString, containsString("Missing or invalid content type for PATCH operation"));
|
||||||
|
}
|
||||||
|
|
||||||
|
Patient newPt = ourClient.read().resource(Patient.class).withId(pid1.getIdPart()).execute();
|
||||||
|
assertEquals("1", newPt.getIdElement().getVersionIdPart());
|
||||||
|
assertEquals(true, newPt.getActive());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPatchInTransaction_MissingBody() throws Exception {
|
||||||
|
String methodName = "testPatchUsingJsonPatch_Transaction";
|
||||||
|
IIdType pid1;
|
||||||
|
{
|
||||||
|
Patient patient = new Patient();
|
||||||
|
patient.setActive(true);
|
||||||
|
patient.addIdentifier().setSystem("urn:system").setValue("0");
|
||||||
|
patient.addName().setFamily(methodName).addGiven("Joe");
|
||||||
|
pid1 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
|
||||||
|
}
|
||||||
|
|
||||||
|
String patchString = "[ { \"op\":\"replace\", \"path\":\"/active\", \"value\":false } ]";
|
||||||
|
Binary patch = new Binary();
|
||||||
|
patch.setContentType(Constants.CT_JSON_PATCH);
|
||||||
|
|
||||||
|
Bundle input = new Bundle();
|
||||||
|
input.setType(Bundle.BundleType.TRANSACTION);
|
||||||
|
input.addEntry()
|
||||||
|
.setFullUrl(pid1.getValue())
|
||||||
|
.setResource(patch)
|
||||||
|
.getRequest().setUrl(pid1.getValue())
|
||||||
|
.setMethod(Bundle.HTTPVerb.PATCH);
|
||||||
|
|
||||||
|
HttpPost post = new HttpPost(ourServerBase);
|
||||||
|
post.setEntity(new StringEntity(myFhirCtx.newJsonParser().encodeResourceToString(input), ContentType.parse(Constants.CT_FHIR_JSON_NEW+ Constants.CHARSET_UTF8_CTSUFFIX)));
|
||||||
|
try (CloseableHttpResponse response = ourHttpClient.execute(post)) {
|
||||||
|
assertEquals(400, response.getStatusLine().getStatusCode());
|
||||||
|
String responseString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
|
||||||
|
assertThat(responseString, containsString("Unable to determine PATCH body from request"));
|
||||||
|
}
|
||||||
|
|
||||||
|
Patient newPt = ourClient.read().resource(Patient.class).withId(pid1.getIdPart()).execute();
|
||||||
|
assertEquals("1", newPt.getIdElement().getVersionIdPart());
|
||||||
|
assertEquals(true, newPt.getActive());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPatchInTransaction_InvalidContentType() throws Exception {
|
||||||
|
String methodName = "testPatchUsingJsonPatch_Transaction";
|
||||||
|
IIdType pid1;
|
||||||
|
{
|
||||||
|
Patient patient = new Patient();
|
||||||
|
patient.setActive(true);
|
||||||
|
patient.addIdentifier().setSystem("urn:system").setValue("0");
|
||||||
|
patient.addName().setFamily(methodName).addGiven("Joe");
|
||||||
|
pid1 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
|
||||||
|
}
|
||||||
|
|
||||||
|
String patchString = "[ { \"op\":\"replace\", \"path\":\"/active\", \"value\":false } ]";
|
||||||
|
Binary patch = new Binary();
|
||||||
|
patch.setContentType(Constants.CT_FHIR_JSON_NEW);
|
||||||
|
patch.setContent(patchString.getBytes(Charsets.UTF_8));
|
||||||
|
|
||||||
|
Bundle input = new Bundle();
|
||||||
|
input.setType(Bundle.BundleType.TRANSACTION);
|
||||||
|
input.addEntry()
|
||||||
|
.setFullUrl(pid1.getValue())
|
||||||
|
.setResource(patch)
|
||||||
|
.getRequest().setUrl(pid1.getValue())
|
||||||
|
.setMethod(Bundle.HTTPVerb.PATCH);
|
||||||
|
|
||||||
|
HttpPost post = new HttpPost(ourServerBase);
|
||||||
|
post.setEntity(new StringEntity(myFhirCtx.newJsonParser().encodeResourceToString(input), ContentType.parse(Constants.CT_FHIR_JSON_NEW+ Constants.CHARSET_UTF8_CTSUFFIX)));
|
||||||
|
try (CloseableHttpResponse response = ourHttpClient.execute(post)) {
|
||||||
|
assertEquals(400, response.getStatusLine().getStatusCode());
|
||||||
|
String responseString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
|
||||||
|
assertThat(responseString, containsString("Invalid Content-Type for PATCH operation: application/fhir+json"));
|
||||||
|
}
|
||||||
|
|
||||||
|
Patient newPt = ourClient.read().resource(Patient.class).withId(pid1.getIdPart()).execute();
|
||||||
|
assertEquals("1", newPt.getIdElement().getVersionIdPart());
|
||||||
|
assertEquals(true, newPt.getActive());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ import ca.uhn.fhir.rest.client.api.IGenericClient;
|
||||||
import ca.uhn.fhir.rest.client.api.IHttpRequest;
|
import ca.uhn.fhir.rest.client.api.IHttpRequest;
|
||||||
import ca.uhn.fhir.rest.client.api.IHttpResponse;
|
import ca.uhn.fhir.rest.client.api.IHttpResponse;
|
||||||
import ca.uhn.fhir.rest.client.interceptor.CapturingInterceptor;
|
import ca.uhn.fhir.rest.client.interceptor.CapturingInterceptor;
|
||||||
|
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
|
||||||
import ca.uhn.fhir.rest.gclient.StringClientParam;
|
import ca.uhn.fhir.rest.gclient.StringClientParam;
|
||||||
import ca.uhn.fhir.rest.param.*;
|
import ca.uhn.fhir.rest.param.*;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
|
@ -3184,6 +3185,19 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEncounterWithReason() {
|
||||||
|
Encounter enc = new Encounter();
|
||||||
|
enc.addReasonCode()
|
||||||
|
.addCoding().setSystem("http://myorg").setCode("hugs").setDisplay("Hugs for better wellness");
|
||||||
|
enc.getPeriod().setStartElement(new DateTimeType("2012"));
|
||||||
|
IIdType id = ourClient.create().resource(enc).execute().getId().toUnqualifiedVersionless();
|
||||||
|
|
||||||
|
enc = ourClient.read().resource(Encounter.class).withId(id).execute();
|
||||||
|
assertEquals("hugs", enc.getReasonCodeFirstRep().getCodingFirstRep().getCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testTerminologyWithCompleteCs_SearchForConceptIn() throws Exception {
|
public void testTerminologyWithCompleteCs_SearchForConceptIn() throws Exception {
|
||||||
|
|
||||||
|
|
|
@ -45,19 +45,7 @@ class PatchTypeParameter implements IParameter {
|
||||||
|
|
||||||
public static PatchTypeEnum getTypeForRequestOrThrowInvalidRequestException(RequestDetails theRequest) {
|
public static PatchTypeEnum getTypeForRequestOrThrowInvalidRequestException(RequestDetails theRequest) {
|
||||||
String contentTypeAll = defaultString(theRequest.getHeader(Constants.HEADER_CONTENT_TYPE));
|
String contentTypeAll = defaultString(theRequest.getHeader(Constants.HEADER_CONTENT_TYPE));
|
||||||
String contentType = contentTypeAll;
|
return PatchTypeEnum.forContentTypeOrThrowInvalidRequestException(contentTypeAll);
|
||||||
int semiColonIdx = contentType.indexOf(';');
|
|
||||||
if (semiColonIdx != -1) {
|
|
||||||
contentType = contentTypeAll.substring(0, semiColonIdx);
|
|
||||||
}
|
|
||||||
contentType = contentType.trim();
|
|
||||||
if (Constants.CT_JSON_PATCH.equals(contentType)) {
|
|
||||||
return PatchTypeEnum.JSON_PATCH;
|
|
||||||
} else if (Constants.CT_XML_PATCH.equals(contentType)) {
|
|
||||||
return PatchTypeEnum.XML_PATCH;
|
|
||||||
} else {
|
|
||||||
throw new InvalidRequestException("Invalid Content-Type for PATCH operation: " + contentTypeAll);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -322,6 +322,11 @@
|
||||||
words in MySQL. The database migrator tool has been updated to handle this
|
words in MySQL. The database migrator tool has been updated to handle this
|
||||||
change.
|
change.
|
||||||
</action>
|
</action>
|
||||||
|
<action type="add">
|
||||||
|
Support for PATCH operations performed within a transaction (using a Binary
|
||||||
|
resource as the resource type in order to hold a JSONPatch or XMLPatch body)
|
||||||
|
has been added to the JPA server.
|
||||||
|
</action>
|
||||||
</release>
|
</release>
|
||||||
<release version="3.8.0" date="2019-05-30" description="Hippo">
|
<release version="3.8.0" date="2019-05-30" description="Hippo">
|
||||||
<action type="fix">
|
<action type="fix">
|
||||||
|
|
Loading…
Reference in New Issue