From 5bf4fa22e78b3505b0762053372e161458d0d8cc Mon Sep 17 00:00:00 2001 From: James Agnew Date: Thu, 18 Jul 2019 16:41:07 -0400 Subject: [PATCH] Allow patching in tranactions --- .../ca/uhn/fhir/rest/api/PatchTypeEnum.java | 20 +- .../ca/uhn/fhir/i18n/hapi-messages.properties | 2 + .../fhir/jpa/dao/TransactionProcessor.java | 90 ++++++-- .../jpa/dao/r4/JpaValidationSupportR4.java | 8 +- .../r4/BinaryAccessProviderR4Test.java | 3 + .../jpa/provider/r4/PatchProviderR4Test.java | 202 +++++++++++++++++- .../provider/r4/ResourceProviderR4Test.java | 14 ++ .../server/method/PatchTypeParameter.java | 14 +- src/changes/changes.xml | 5 + 9 files changed, 316 insertions(+), 42 deletions(-) diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/PatchTypeEnum.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/PatchTypeEnum.java index 97ad3fad50e..3452087c776 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/PatchTypeEnum.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/PatchTypeEnum.java @@ -21,13 +21,16 @@ package ca.uhn.fhir.rest.api; */ 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} */ 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; @@ -39,4 +42,19 @@ public enum PatchTypeEnum { 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)); + } + } } diff --git a/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties index 6c8b8758f71..b455c3b5b51 100644 --- a/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties +++ b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties @@ -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.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.failedToExtractPaths=Failed to extract values from resource using FHIRPath "{0}": {1} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/TransactionProcessor.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/TransactionProcessor.java index c552bef72fc..0d94a2083c5 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/TransactionProcessor.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/TransactionProcessor.java @@ -37,10 +37,7 @@ import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster; import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.parser.IParser; -import ca.uhn.fhir.rest.api.Constants; -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.*; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.param.ParameterUtil; 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.servlet.ServletRequestDetails; import ca.uhn.fhir.util.*; +import com.google.common.base.Charsets; import com.google.common.collect.ArrayListMultimap; import org.apache.commons.lang3.Validate; import org.apache.http.NameValuePair; @@ -755,9 +753,50 @@ public class TransactionProcessor { entriesToProcess.put(nextRespEntry, outcome.getEntity()); break; } - case "GET": - default: + case "PATCH": { + // 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 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; + } + case "GET": + break; + default: + throw new InvalidRequestException("Unable to handle verb in transaction: " + verb); } @@ -1051,6 +1090,7 @@ public class TransactionProcessor { * Process any DELETE interactions * Process any POST interactions * Process any PUT interactions + * Process any PATCH interactions * Process any GET interactions */ //@formatter:off @@ -1107,21 +1147,6 @@ public class TransactionProcessor { 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) { int o1 = 0; if (myVersionAdapter.getEntryRequestVerb(theO1) != null) { @@ -1135,9 +1160,12 @@ public class TransactionProcessor { case "PUT": o1 = 3; break; - case "GET": + case "PATCH": o1 = 4; break; + case "GET": + o1 = 5; + break; default: o1 = 0; break; @@ -1171,4 +1199,22 @@ public class TransactionProcessor { 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; + } + } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/JpaValidationSupportR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/JpaValidationSupportR4.java index 1f9c483565d..f1076beb810 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/JpaValidationSupportR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/JpaValidationSupportR4.java @@ -130,9 +130,11 @@ public class JpaValidationSupportR4 implements IJpaValidationSupportR4, Applicat } } else if ("StructureDefinition".equals(resourceName)) { // Don't allow the core FHIR definitions to be overwritten - String typeName = theUri.substring("http://hl7.org/fhir/StructureDefinition/".length()); - if (myR4Ctx.getElementDefinition(typeName) != null) { - return null; + if (theUri.startsWith("http://hl7.org/fhir/StructureDefinition/")) { + String typeName = theUri.substring("http://hl7.org/fhir/StructureDefinition/".length()); + if (myR4Ctx.getElementDefinition(typeName) != null) { + return null; + } } SearchParameterMap params = new SearchParameterMap(); params.setLoadSynchronousUpTo(1); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/BinaryAccessProviderR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/BinaryAccessProviderR4Test.java index 00364e26f59..3f7e6809635 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/BinaryAccessProviderR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/BinaryAccessProviderR4Test.java @@ -308,6 +308,9 @@ public class BinaryAccessProviderR4Test extends BaseResourceProviderR4Test { if (theSetData) { attachment.setData(SOME_BYTES_2); } + + ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(documentReference)); + return ourClient.create().resource(documentReference).execute().getId().toUnqualifiedVersionless(); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/PatchProviderR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/PatchProviderR4Test.java index 8e2510d40b0..140e7d0603e 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/PatchProviderR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/PatchProviderR4Test.java @@ -1,15 +1,15 @@ package ca.uhn.fhir.jpa.provider.r4; import ca.uhn.fhir.rest.api.Constants; +import com.google.common.base.Charsets; import org.apache.commons.io.IOUtils; import org.apache.http.client.methods.CloseableHttpResponse; 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.StringEntity; import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.r4.model.Media; -import org.hl7.fhir.r4.model.Observation; -import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.*; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -93,6 +93,48 @@ public class PatchProviderR4Test extends BaseResourceProviderR4Test { 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 public void testPatchUsingJsonPatch_Conditional_Success() throws Exception { 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 = "false"; + 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()); + } + } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java index ee2be4fc725..ce94e2b4232 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java @@ -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.IHttpResponse; 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.param.*; 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 public void testTerminologyWithCompleteCs_SearchForConceptIn() throws Exception { diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/PatchTypeParameter.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/PatchTypeParameter.java index 743dcf21fff..6308c8c1da5 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/PatchTypeParameter.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/PatchTypeParameter.java @@ -45,19 +45,7 @@ class PatchTypeParameter implements IParameter { public static PatchTypeEnum getTypeForRequestOrThrowInvalidRequestException(RequestDetails theRequest) { String contentTypeAll = defaultString(theRequest.getHeader(Constants.HEADER_CONTENT_TYPE)); - String contentType = 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); - } + return PatchTypeEnum.forContentTypeOrThrowInvalidRequestException(contentTypeAll); } } diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 4b8dd99f5ab..5ea1bc7a439 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -322,6 +322,11 @@ words in MySQL. The database migrator tool has been updated to handle this change. + + 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. +