From 7e3d062d157f052809423604420cd617baec3b64 Mon Sep 17 00:00:00 2001 From: James Agnew Date: Mon, 22 Jul 2019 21:27:10 -0400 Subject: [PATCH] Switch default client to JSON (#1392) * Start work on changing over * Update tests * More test fixes * Work on generalizing binary access provider a bit * Add expunge capability to binary storage, and also support actual binary resources * Work on binary provider * Get the DB bound binary storage service working * Test fixes * Compile fix * Fix compile error * Test fix * Test tweak * Trying to figure out an intermittent failure * Work on tests * More work on tests * Another test fix --- .../ca/uhn/fhir/interceptor/api/Pointcut.java | 37 +- .../java/ca/uhn/fhir/parser/BaseParser.java | 16 +- .../main/java/ca/uhn/fhir/parser/IParser.java | 4 +- .../java/ca/uhn/fhir/util/AttachmentUtil.java | 12 +- .../java/ca/uhn/fhir/util/BinaryUtil.java | 34 +- .../util/CountingAndLimitingInputStream.java | 49 +++ .../fhir/instance/model/api/IBaseBinary.java | 3 + .../ca/uhn/fhir/i18n/hapi-messages.properties | 7 +- .../okhttp/GenericOkHttpClientDstu2Test.java | 49 ++- .../uhn/fhir/rest/client/impl/BaseClient.java | 2 +- .../BaseHttpClientInvocationWithContents.java | 6 +- .../client/GenericJaxRsClientDstu2Test.java | 55 +-- .../client/GenericJaxRsClientDstu3Test.java | 59 ++- .../binstore/BaseBinaryStorageSvcImpl.java | 42 +- .../jpa/binstore/BinaryAccessProvider.java | 358 ++++++++++++++++++ .../binstore/BinaryStorageInterceptor.java | 44 +++ .../DatabaseBlobBinaryStorageSvcImpl.java | 124 ++++++ .../FilesystemBinaryStorageSvcImpl.java | 24 +- .../fhir/jpa/binstore/IBinaryStorageSvc.java | 60 ++- .../binstore/MemoryBinaryStorageSvcImpl.java | 12 +- .../binstore/NullBinaryStorageSvcImpl.java | 29 +- .../ca/uhn/fhir/jpa/config/BaseConfig.java | 11 +- .../jpa/dao/data/IBinaryStorageEntityDao.java | 23 ++ .../uhn/fhir/jpa/dao/expunge/ExpungeRun.java | 6 +- .../dao/expunge/IResourceExpungeService.java | 7 +- .../dao/expunge/ResourceExpungeService.java | 52 ++- .../jpa/provider/BinaryAccessProvider.java | 234 +----------- .../CircularQueueCaptureQueriesListener.java | 18 + .../DatabaseBlobBinaryStorageSvcImplTest.java | 133 +++++++ .../FilesystemBinaryStorageSvcImplTest.java | 24 +- .../NullBinaryStorageSvcImplTest.java | 6 + .../ca/uhn/fhir/jpa/config/TestR4Config.java | 6 + .../ca/uhn/fhir/jpa/dao/r4/BaseJpaR4Test.java | 6 +- .../FhirResourceDaoR4SearchOptimizedTest.java | 5 +- ...urceDaoR4SearchWithLuceneDisabledTest.java | 2 + .../r4/BaseResourceProviderR4Test.java | 1 + .../r4/BinaryAccessProviderR4Test.java | 202 +++++++++- .../fhir/jpa/provider/r4/ExpungeR4Test.java | 8 +- .../ca/uhn/fhir/jpa/migrate/JdbcUtils.java | 2 + .../taskdef/BaseTableColumnTypeTask.java | 17 + .../tasks/HapiFhirJpaMigrationTasks.java | 10 + .../jpa/model/entity/BinaryStorageEntity.java | 87 +++++ .../uhn/fhir/jpa/model/util/JpaConstants.java | 2 +- .../rest/client/GenericClientDstu2_1Test.java | 21 +- .../client/NonGenericClientDstu2_1Test.java | 4 +- .../client/OperationClientDstu2_1Test.java | 4 +- .../fhir/rest/client/BundleTypeDstu2Test.java | 2 +- .../client/ClientWithProfileDstu2Test.java | 2 +- .../rest/client/GenericClientDstu2Test.java | 82 ++-- .../rest/client/OperationClientDstu2Test.java | 24 +- .../rest/client/ClientMimetypeDstu3Test.java | 4 +- .../rest/client/GenericClientDstu3Test.java | 21 +- .../client/NonGenericClientDstu3Test.java | 4 +- .../rest/client/OperationClientDstu3Test.java | 4 +- .../client/GenericClientDstu2Hl7OrgTest.java | 47 ++- .../fhir/rest/client/OperationClientTest.java | 18 +- .../ca/uhn/fhir/parser/JsonParserR4Test.java | 12 + .../fhir/rest/client/BinaryClientTest.java | 32 +- .../fhir/rest/client/ClientHeadersR4Test.java | 2 +- .../rest/client/ClientMimetypeR4Test.java | 4 +- .../ca/uhn/fhir/rest/client/ClientR4Test.java | 16 +- .../fhir/rest/client/GenericClientR4Test.java | 45 +-- .../fhir/rest/client/GenericClientTest.java | 13 +- .../rest/client/NonGenericClientR4Test.java | 4 +- .../rest/client/OperationClientR4Test.java | 4 +- .../rest/client/TransactionClientTest.java | 4 +- .../src/main/resources/vm/resource.vm | 6 + src/changes/changes.xml | 12 +- 68 files changed, 1749 insertions(+), 530 deletions(-) create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/util/CountingAndLimitingInputStream.java create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/binstore/BinaryAccessProvider.java create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/binstore/BinaryStorageInterceptor.java create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/binstore/DatabaseBlobBinaryStorageSvcImpl.java create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IBinaryStorageEntityDao.java create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/binstore/DatabaseBlobBinaryStorageSvcImplTest.java create mode 100644 hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BinaryStorageEntity.java diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Pointcut.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Pointcut.java index bd248475101..fcac3de9935 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Pointcut.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Pointcut.java @@ -1163,6 +1163,42 @@ public enum Pointcut { "ca.uhn.fhir.rest.server.servlet.ServletRequestDetails" ), + /** + * Invoked before a resource is about to be expunged via the $expunge operation. + *

+ * Hooks may accept the following parameters: + *

+ * + *

+ * Hooks should return void. + *

+ */ + STORAGE_PRESTORAGE_EXPUNGE_RESOURCE( + // Return type + void.class, + // Params + "org.hl7.fhir.instance.model.api.IIdType", + "org.hl7.fhir.instance.model.api.IBaseResource", + "ca.uhn.fhir.rest.api.server.RequestDetails", + "ca.uhn.fhir.rest.server.servlet.ServletRequestDetails" + ), + /** * Invoked before expungeEverything is called. *

@@ -1190,7 +1226,6 @@ public enum Pointcut { * Hooks should return void. *

*/ - STORAGE_PRESTORAGE_EXPUNGE_EVERYTHING( // Return type void.class, diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java index ba8b7754c2f..9934bdade7e 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java @@ -80,7 +80,7 @@ public abstract class BaseParser implements IParser { } @Override - public void setDontEncodeElements(Set theDontEncodeElements) { + public IParser setDontEncodeElements(Set theDontEncodeElements) { if (theDontEncodeElements == null || theDontEncodeElements.isEmpty()) { myDontEncodeElements = null; } else { @@ -89,6 +89,7 @@ public abstract class BaseParser implements IParser { .map(ElementsPath::new) .collect(Collectors.toList()); } + return this; } List getEncodeElements() { @@ -96,7 +97,7 @@ public abstract class BaseParser implements IParser { } @Override - public void setEncodeElements(Set theEncodeElements) { + public IParser setEncodeElements(Set theEncodeElements) { if (theEncodeElements == null || theEncodeElements.isEmpty()) { myEncodeElements = null; @@ -122,6 +123,8 @@ public abstract class BaseParser implements IParser { } } + + return this; } protected Iterable compositeChildIterator(IBase theCompositeElement, final boolean theContainedResource, final CompositeChildElement theParent, EncodeContext theEncodeContext) { @@ -1112,7 +1115,9 @@ public abstract class BaseParser implements IParser { private boolean checkIfPathMatchesForEncoding(List theElements, boolean theCheckingForEncodeElements) { boolean retVal = false; - myEncodeContext.pushPath(myDef.getElementName(), false); + if (myDef != null) { + myEncodeContext.pushPath(myDef.getElementName(), false); + } if (theCheckingForEncodeElements && isEncodeElementsAppliesToChildResourcesOnly() && myEncodeContext.getResourcePath().size() < 2) { retVal = true; @@ -1144,7 +1149,10 @@ public abstract class BaseParser implements IParser { } } - myEncodeContext.popPath(); + if (myDef != null) { + myEncodeContext.popPath(); + } + return retVal; } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/IParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/IParser.java index 7bbf39f7653..ffa0eed714a 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/IParser.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/IParser.java @@ -212,7 +212,7 @@ public interface IParser { * The elements to encode * @see #setEncodeElements(Set) */ - void setDontEncodeElements(Set theDontEncodeElements); + IParser setDontEncodeElements(Set theDontEncodeElements); /** * If provided, specifies the elements which should be encoded, to the exclusion of all others. Valid values for this @@ -230,7 +230,7 @@ public interface IParser { * The elements to encode * @see #setDontEncodeElements(Set) */ - void setEncodeElements(Set theEncodeElements); + IParser setEncodeElements(Set theEncodeElements); /** * If set to true (default is false), the values supplied diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/AttachmentUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/AttachmentUtil.java index 9bedd26a7d6..33847cd1ef8 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/AttachmentUtil.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/AttachmentUtil.java @@ -84,15 +84,21 @@ public class AttachmentUtil { } } + /** + * This is internal API- Use with caution as it may change + */ @SuppressWarnings("unchecked") - private static IPrimitiveType newPrimitive(FhirContext theContext, String theType, T theValue) { + static IPrimitiveType newPrimitive(FhirContext theContext, String theType, T theValue) { IPrimitiveType primitive = (IPrimitiveType) theContext.getElementDefinition(theType).newInstance(); primitive.setValue(theValue); return primitive; } - private static BaseRuntimeChildDefinition getChild(FhirContext theContext, ICompositeType theAttachment, String theName) { - BaseRuntimeElementCompositeDefinition def = (BaseRuntimeElementCompositeDefinition) theContext.getElementDefinition(theAttachment.getClass()); + /** + * This is internal API- Use with caution as it may change + */ + static BaseRuntimeChildDefinition getChild(FhirContext theContext, IBase theElement, String theName) { + BaseRuntimeElementCompositeDefinition def = (BaseRuntimeElementCompositeDefinition) theContext.getElementDefinition(theElement.getClass()); return def.getChildByName(theName); } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/BinaryUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/BinaryUtil.java index 2dfdf12b9a6..e0e252dfa1a 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/BinaryUtil.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/BinaryUtil.java @@ -20,13 +20,8 @@ package ca.uhn.fhir.util; * #L% */ -import ca.uhn.fhir.context.BaseRuntimeChildDefinition; -import ca.uhn.fhir.context.BaseRuntimeElementDefinition; -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.context.RuntimeResourceDefinition; -import org.hl7.fhir.instance.model.api.IBase; -import org.hl7.fhir.instance.model.api.IBaseBinary; -import org.hl7.fhir.instance.model.api.IBaseReference; +import ca.uhn.fhir.context.*; +import org.hl7.fhir.instance.model.api.*; import java.util.List; @@ -36,6 +31,31 @@ public class BinaryUtil { // non instantiable } + /** + * Fetches the base64Binary value of Binary.data (or Binary.content on versions of + * FHIR before R4), creating it if it does not already exist. + */ + @SuppressWarnings("unchecked") + public static IPrimitiveType getOrCreateData(FhirContext theContext, IBaseBinary theBinary) { + String elementName = "content"; + if (theContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.R4)) { + elementName = "data"; + } + + BaseRuntimeChildDefinition entryChild = AttachmentUtil.getChild(theContext, theBinary, elementName); + List entries = entryChild.getAccessor().getValues(theBinary); + return entries + .stream() + .map(t -> (IPrimitiveType) t) + .findFirst() + .orElseGet(() -> { + IPrimitiveType binary = AttachmentUtil.newPrimitive(theContext, "base64Binary", null); + entryChild.getMutator().setValue(theBinary, binary); + return binary; + }); + } + + public static IBaseReference getSecurityContext(FhirContext theCtx, IBaseBinary theBinary) { RuntimeResourceDefinition def = theCtx.getResourceDefinition("Binary"); BaseRuntimeChildDefinition child = def.getChildByName("securityContext"); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/CountingAndLimitingInputStream.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/CountingAndLimitingInputStream.java new file mode 100644 index 00000000000..ecd1b0aba9f --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/CountingAndLimitingInputStream.java @@ -0,0 +1,49 @@ +package ca.uhn.fhir.util; + +import com.google.common.io.CountingInputStream; + +import java.io.IOException; +import java.io.InputStream; + +public class CountingAndLimitingInputStream extends InputStream { + private final int myMaxBytes; + private final CountingInputStream myWrap; + + @Override + public int read() throws IOException { + int retVal = myWrap.read(); + validateCount(); + return retVal; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + int retVal = myWrap.read(b, off, len); + validateCount(); + return retVal; + } + + @Override + public int read(byte[] theRead) throws IOException { + int retVal = myWrap.read(theRead); + validateCount(); + return retVal; + } + + private void validateCount() throws IOException { + if (myWrap.getCount() > myMaxBytes) { + throw new IOException("Stream exceeds maximum allowable size: " + myMaxBytes); + } + } + + + /** + * Wraps another input stream, counting the number of bytes read. + * + * @param theWrap the input stream to be wrapped + */ + public CountingAndLimitingInputStream(InputStream theWrap, int theMaxBytes) { + myWrap = new CountingInputStream(theWrap); + myMaxBytes = theMaxBytes; + } +} diff --git a/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IBaseBinary.java b/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IBaseBinary.java index 1b7c8eab822..0b9de30d524 100644 --- a/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IBaseBinary.java +++ b/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IBaseBinary.java @@ -34,4 +34,7 @@ public interface IBaseBinary extends IBaseResource { IBaseBinary setContentAsBase64(String theContent); IBaseBinary setContentType(String theContentType); + + default boolean hasData() { return getContent() != null && getContent().length > 0; }; + } 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 b455c3b5b51..5bd59f27bf1 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 @@ -116,8 +116,11 @@ ca.uhn.fhir.jpa.interceptor.CascadingDeleteInterceptor.successMsg=Cascaded delet ca.uhn.fhir.jpa.interceptor.CascadingDeleteInterceptor.noParam=Note that cascading deletes are not active for this request. You can enable cascading deletes by using the "_cascade=true" URL parameter. ca.uhn.fhir.jpa.provider.BaseJpaProvider.cantCombintAtAndSince=Unable to combine _at and _since parameters for history operation -ca.uhn.fhir.jpa.provider.BinaryAccessProvider.noAttachmentDataPresent=The resource with ID {0} has no data at path: {1} -ca.uhn.fhir.jpa.provider.BinaryAccessProvider.unknownBlobId=Can not find the requested binary content. It may have been deleted. +ca.uhn.fhir.jpa.binstore.BinaryAccessProvider.noAttachmentDataPresent=The resource with ID {0} has no data at path: {1} +ca.uhn.fhir.jpa.binstore.BinaryAccessProvider.unknownBlobId=Can not find the requested binary content. It may have been deleted. +ca.uhn.fhir.jpa.binstore.BinaryAccessProvider.unknownPath=Unable to find content in resource of type {0} at path: {1} +ca.uhn.fhir.jpa.binstore.BinaryAccessProvider.unknownType=Content in resource of type {0} at path {1} is not appropriate for binary storage: {2} + ca.uhn.fhir.jpa.term.BaseHapiTerminologySvcImpl.cannotCreateDuplicateCodeSystemUrl=Can not create multiple CodeSystem resources with CodeSystem.url "{0}", already have one with resource ID: {1} ca.uhn.fhir.jpa.term.BaseHapiTerminologySvcImpl.cannotCreateDuplicateConceptMapUrl=Can not create multiple ConceptMap resources with ConceptMap.url "{0}", already have one with resource ID: {1} diff --git a/hapi-fhir-client-okhttp/src/test/java/ca/uhn/fhir/okhttp/GenericOkHttpClientDstu2Test.java b/hapi-fhir-client-okhttp/src/test/java/ca/uhn/fhir/okhttp/GenericOkHttpClientDstu2Test.java index 8077f77453e..1c460b63a1a 100644 --- a/hapi-fhir-client-okhttp/src/test/java/ca/uhn/fhir/okhttp/GenericOkHttpClientDstu2Test.java +++ b/hapi-fhir-client-okhttp/src/test/java/ca/uhn/fhir/okhttp/GenericOkHttpClientDstu2Test.java @@ -294,7 +294,7 @@ public class GenericOkHttpClientDstu2Test { Patient p = new Patient(); p.addName().addFamily("FOOFAMILY"); - client.create().resource(p).execute(); + client.create().resource(p).encodedXml().execute(); assertEquals(1, ourRequestHeaders.get(Constants.HEADER_CONTENT_TYPE).size()); assertContentTypeEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8); @@ -304,7 +304,7 @@ public class GenericOkHttpClientDstu2Test { p.setId("123"); - client.create().resource(p).execute(); + client.create().resource(p).encodedXml().execute(); assertEquals(1, ourRequestHeaders.get(Constants.HEADER_CONTENT_TYPE).size()); assertContentTypeEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8); String body = ourRequestBodyString; @@ -323,7 +323,7 @@ public class GenericOkHttpClientDstu2Test { Patient p = new Patient(); p.addName().addFamily("FOOFAMILY"); - client.create().resource(p).conditionalByUrl("Patient?name=foo").execute(); + client.create().resource(p).conditionalByUrl("Patient?name=foo").encodedXml().execute(); assertEquals(1, ourRequestHeaders.get(Constants.HEADER_CONTENT_TYPE).size()); String expectedContentTypeHeader = EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8; assertContentTypeEquals(expectedContentTypeHeader); @@ -332,7 +332,7 @@ public class GenericOkHttpClientDstu2Test { assertEquals("http://localhost:" + ourPort + "/fhir/Patient?name=foo", ourRequestFirstHeaders.get(Constants.HEADER_IF_NONE_EXIST).getValue()); assertEquals("POST", ourRequestMethod); - client.create().resource(p).conditionalByUrl("Patient?name=http://foo|bar").execute(); + client.create().resource(p).conditionalByUrl("Patient?name=http://foo|bar").encodedXml().execute(); assertEquals(1, ourRequestHeaders.get(Constants.HEADER_CONTENT_TYPE).size()); assertContentTypeEquals(expectedContentTypeHeader); assertThat(ourRequestBodyString, containsString("")); @@ -340,7 +340,7 @@ public class GenericOkHttpClientDstu2Test { assertEquals("http://localhost:" + ourPort + "/fhir/Patient?name=http%3A//foo%7Cbar", ourRequestFirstHeaders.get(Constants.HEADER_IF_NONE_EXIST).getValue()); assertEquals("POST", ourRequestMethod); - client.create().resource(p).conditional().where(Patient.NAME.matches().value("foo")).execute(); + client.create().resource(p).conditional().where(Patient.NAME.matches().value("foo")).encodedXml().execute(); assertEquals(1, ourRequestHeaders.get(Constants.HEADER_CONTENT_TYPE).size()); assertContentTypeEquals(expectedContentTypeHeader); assertThat(ourRequestBodyString, containsString("")); @@ -528,6 +528,7 @@ public class GenericOkHttpClientDstu2Test { .add() .onResource(new IdDt("Patient/123")) .meta(inMeta) + .encodedXml() .execute(); assertEquals("http://localhost:" + ourPort + "/fhir/Patient/123/$meta-add", ourRequestUri); @@ -761,7 +762,7 @@ public class GenericOkHttpClientDstu2Test { .operation() .onServer() .named("$SOMEOPERATION") - .withParameters(inParams).execute(); + .withParameters(inParams).encodedXml().execute(); assertEquals("http://localhost:" + ourPort + "/fhir/$SOMEOPERATION", ourRequestUri); assertEquals(1, ourRequestHeaders.get(Constants.HEADER_CONTENT_TYPE).size()); @@ -792,6 +793,7 @@ public class GenericOkHttpClientDstu2Test { .named("$SOMEOPERATION") .withParameter(Parameters.class, "name1", new StringDt("value1")) .andParameter("name2", new StringDt("value1")) + .encodedXml() .execute(); assertEquals("http://localhost:" + ourPort + "/fhir/$SOMEOPERATION", ourRequestUri); @@ -813,6 +815,7 @@ public class GenericOkHttpClientDstu2Test { .named("$SOMEOPERATION") .withParameter(Parameters.class, "name1", new IdentifierDt("system1", "value1")) .andParameter("name2", new StringDt("value1")) + .encodedXml() .execute(); assertEquals("http://localhost:" + ourPort + "/fhir/$SOMEOPERATION", ourRequestUri); @@ -834,6 +837,7 @@ public class GenericOkHttpClientDstu2Test { .named("$SOMEOPERATION") .withParameter(Parameters.class, "name1", new IdentifierDt("system1", "value1")) .andParameter("name2", new Patient().setActive(true)) + .encodedXml() .execute(); assertEquals("http://localhost:" + ourPort + "/fhir/$SOMEOPERATION", ourRequestUri); @@ -915,6 +919,7 @@ public class GenericOkHttpClientDstu2Test { .named("validate-code") .withParameter(Parameters.class, "code", new CodeDt("8495-4")) .andParameter("system", new UriDt("http://loinc.org")) + .encodedXml() .execute(); assertEquals("http://localhost:" + ourPort + "/fhir/Patient/1/$validate-code", ourRequestUri); @@ -947,7 +952,7 @@ public class GenericOkHttpClientDstu2Test { .operation() .onServer() .named("$SOMEOPERATION") - .withParameters(inParams).execute(); + .withParameters(inParams).encodedXml().execute(); assertEquals("http://localhost:" + ourPort + "/fhir/$SOMEOPERATION", ourRequestUri); assertEquals(respString, p.encodeResourceToString(resp)); @@ -960,7 +965,7 @@ public class GenericOkHttpClientDstu2Test { .operation() .onType(Patient.class) .named("$SOMEOPERATION") - .withParameters(inParams).execute(); + .withParameters(inParams).encodedXml().execute(); assertEquals("http://localhost:" + ourPort + "/fhir/Patient/$SOMEOPERATION", ourRequestUri); assertEquals(respString, p.encodeResourceToString(resp)); @@ -973,7 +978,7 @@ public class GenericOkHttpClientDstu2Test { .operation() .onInstance(new IdDt("Patient", "123")) .named("$SOMEOPERATION") - .withParameters(inParams).execute(); + .withParameters(inParams).encodedXml().execute(); assertEquals("http://localhost:" + ourPort + "/fhir/Patient/123/$SOMEOPERATION", ourRequestUri); assertEquals(respString, p.encodeResourceToString(resp)); @@ -1008,7 +1013,7 @@ public class GenericOkHttpClientDstu2Test { .operation() .onServer() .named("$SOMEOPERATION") - .withNoParameters(Parameters.class).execute(); + .withNoParameters(Parameters.class).encodedXml().execute(); assertEquals("http://localhost:" + ourPort + "/fhir/$SOMEOPERATION", ourRequestUri); assertEquals(respString, p.encodeResourceToString(resp)); @@ -1021,7 +1026,7 @@ public class GenericOkHttpClientDstu2Test { .operation() .onType(Patient.class) .named("$SOMEOPERATION") - .withNoParameters(Parameters.class).execute(); + .withNoParameters(Parameters.class).encodedXml().execute(); assertEquals("http://localhost:" + ourPort + "/fhir/Patient/$SOMEOPERATION", ourRequestUri); assertEquals(respString, p.encodeResourceToString(resp)); @@ -1034,7 +1039,7 @@ public class GenericOkHttpClientDstu2Test { .operation() .onInstance(new IdDt("Patient", "123")) .named("$SOMEOPERATION") - .withNoParameters(Parameters.class).execute(); + .withNoParameters(Parameters.class).encodedXml().execute(); assertEquals("http://localhost:" + ourPort + "/fhir/Patient/123/$SOMEOPERATION", ourRequestUri); assertEquals(respString, p.encodeResourceToString(resp)); @@ -1653,35 +1658,35 @@ public class GenericOkHttpClientDstu2Test { Patient p = new Patient(); p.addName().addFamily("FOOFAMILY"); - client.update().resource(p).conditionalByUrl("Patient?name=foo").execute(); + client.update().resource(p).conditionalByUrl("Patient?name=foo").encodedXml().execute(); assertEquals(1, ourRequestHeaders.get(Constants.HEADER_CONTENT_TYPE).size()); assertContentTypeEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8); assertThat(ourRequestBodyString, containsString("")); assertEquals("PUT", ourRequestMethod); assertEquals("http://localhost:" + ourPort + "/fhir/Patient?name=foo", ourRequestUri); - client.update().resource(p).conditionalByUrl("Patient?name=http://foo|bar").execute(); + client.update().resource(p).conditionalByUrl("Patient?name=http://foo|bar").encodedXml().execute(); assertEquals(1, ourRequestHeaders.get(Constants.HEADER_CONTENT_TYPE).size()); assertContentTypeEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8); assertThat(ourRequestBodyString, containsString("")); assertEquals("PUT", ourRequestMethod); assertEquals("http://localhost:" + ourPort + "/fhir/Patient?name=http%3A//foo%7Cbar", ourRequestUri); - client.update().resource(ourCtx.newXmlParser().encodeResourceToString(p)).conditionalByUrl("Patient?name=foo").execute(); + client.update().resource(ourCtx.newXmlParser().encodeResourceToString(p)).conditionalByUrl("Patient?name=foo").encodedXml().execute(); assertEquals(1, ourRequestHeaders.get(Constants.HEADER_CONTENT_TYPE).size()); assertContentTypeEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8); assertThat(ourRequestBodyString, containsString("")); assertEquals("PUT", ourRequestMethod); assertEquals("http://localhost:" + ourPort + "/fhir/Patient?name=foo", ourRequestUri); - client.update().resource(p).conditional().where(Patient.NAME.matches().value("foo")).and(Patient.ADDRESS.matches().value("AAA|BBB")).execute(); + client.update().resource(p).conditional().where(Patient.NAME.matches().value("foo")).and(Patient.ADDRESS.matches().value("AAA|BBB")).encodedXml().execute(); assertEquals(1, ourRequestHeaders.get(Constants.HEADER_CONTENT_TYPE).size()); assertContentTypeEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8); assertThat(ourRequestBodyString, containsString("")); assertEquals("PUT", ourRequestMethod); assertEquals("http://localhost:" + ourPort + "/fhir/Patient?name=foo&address=AAA%5C%7CBBB", ourRequestUri); - client.update().resource(ourCtx.newXmlParser().encodeResourceToString(p)).conditional().where(Patient.NAME.matches().value("foo")).and(Patient.ADDRESS.matches().value("AAA|BBB")).execute(); + client.update().resource(ourCtx.newXmlParser().encodeResourceToString(p)).conditional().where(Patient.NAME.matches().value("foo")).and(Patient.ADDRESS.matches().value("AAA|BBB")).encodedXml().execute(); assertEquals(1, ourRequestHeaders.get(Constants.HEADER_CONTENT_TYPE).size()); assertContentTypeEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8); assertThat(ourRequestBodyString, containsString("")); @@ -1698,6 +1703,7 @@ public class GenericOkHttpClientDstu2Test { ourResponseStatus = Constants.STATUS_HTTP_204_NO_CONTENT; IGenericClient client = ourCtx.newRestfulGenericClient("http://localhost:" + ourPort + "/fhir"); + client.setEncoding(EncodingEnum.XML); Patient p = new Patient(); p.addName().addFamily("FOOFAMILY"); @@ -1708,14 +1714,14 @@ public class GenericOkHttpClientDstu2Test { String expectedContentTypeHeader = EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8; assertThat(getActualContentTypeHeader(), equalToIgnoringCase(expectedContentTypeHeader)); assertThat(ourRequestBodyString, containsString("")); - assertEquals("http://localhost:" + ourPort + "/fhir/Patient/123", ourRequestUri); + assertEquals("http://localhost:" + ourPort + "/fhir/Patient/123?_format=xml", ourRequestUri); assertEquals("PUT", ourRequestMethod); client.update("123", p); assertEquals(1, ourRequestHeaders.get(Constants.HEADER_CONTENT_TYPE).size()); assertThat(getActualContentTypeHeader(), equalToIgnoringCase(expectedContentTypeHeader)); assertThat(ourRequestBodyString, containsString("")); - assertEquals("http://localhost:" + ourPort + "/fhir/Patient/123", ourRequestUri); + assertEquals("http://localhost:" + ourPort + "/fhir/Patient/123?_format=xml", ourRequestUri); assertEquals("PUT", ourRequestMethod); } @@ -1775,7 +1781,7 @@ public class GenericOkHttpClientDstu2Test { MethodOutcome response; - response = client.validate().resource(p).execute(); + response = client.validate().resource(p).encodedXml().execute(); assertEquals("http://localhost:" + ourPort + "/fhir/Patient/$validate", ourRequestUri); assertEquals("POST", ourRequestMethod); assertEquals( @@ -1818,6 +1824,7 @@ public class GenericOkHttpClientDstu2Test { ourResponseBody = msg; IGenericClient client = ourCtx.newRestfulGenericClient("http://localhost:" + ourPort + "/fhir"); + client.setEncoding(EncodingEnum.XML); Patient p = new Patient(); p.addName().addGiven("GIVEN"); @@ -1826,7 +1833,7 @@ public class GenericOkHttpClientDstu2Test { response = client.validate(p); - assertEquals("http://localhost:" + ourPort + "/fhir/Patient/$validate", ourRequestUri); + assertEquals("http://localhost:" + ourPort + "/fhir/Patient/$validate?_format=xml", ourRequestUri); assertEquals("POST", ourRequestMethod); assertEquals( "", diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/BaseClient.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/BaseClient.java index ff52c670e60..d7e19a0491a 100644 --- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/BaseClient.java +++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/BaseClient.java @@ -72,7 +72,7 @@ public abstract class BaseClient implements IRestfulClient { private final RestfulClientFactory myFactory; private final String myUrlBase; private boolean myDontValidateConformance; - private EncodingEnum myEncoding = null; // default unspecified (will be XML) + private EncodingEnum myEncoding = null; // default unspecified (will be JSON) private boolean myKeepResponses = false; private IHttpResponse myLastResponse; private String myLastResponseBody; diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/BaseHttpClientInvocationWithContents.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/BaseHttpClientInvocationWithContents.java index ea961e8780f..56e7365bc38 100644 --- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/BaseHttpClientInvocationWithContents.java +++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/BaseHttpClientInvocationWithContents.java @@ -116,7 +116,9 @@ abstract class BaseHttpClientInvocationWithContents extends BaseHttpClientInvoca if (myResource != null && IBaseBinary.class.isAssignableFrom(myResource.getClass())) { IBaseBinary binary = (IBaseBinary) myResource; if (isNotBlank(binary.getContentType()) && EncodingEnum.forContentTypeStrict(binary.getContentType()) == null) { - return httpClient.createBinaryRequest(getContext(), binary); + if (binary.hasData()) { + return httpClient.createBinaryRequest(getContext(), binary); + } } } @@ -129,7 +131,7 @@ abstract class BaseHttpClientInvocationWithContents extends BaseHttpClientInvoca if (myParams != null) { return httpClient.createParamRequest(getContext(), myParams, encoding); } - encoding = ObjectUtils.defaultIfNull(encoding, EncodingEnum.XML); + encoding = ObjectUtils.defaultIfNull(encoding, EncodingEnum.JSON); String contents = encodeContents(thePrettyPrint, encoding); String contentType = getContentType(encoding); return httpClient.createByteRequest(getContext(), contents, contentType, encoding); diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/client/GenericJaxRsClientDstu2Test.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/client/GenericJaxRsClientDstu2Test.java index d80c05bb915..a91c7d798e4 100644 --- a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/client/GenericJaxRsClientDstu2Test.java +++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/client/GenericJaxRsClientDstu2Test.java @@ -221,7 +221,7 @@ public class GenericJaxRsClientDstu2Test { Patient p = new Patient(); p.addName().addFamily("FOOFAMILY"); - client.create().resource(p).execute(); + client.create().resource(p).encodedXml().execute(); assertEquals(1, ourRequestHeaders.get(Constants.HEADER_CONTENT_TYPE).size()); assertEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, ourRequestFirstHeaders.get(Constants.HEADER_CONTENT_TYPE).getValue().replace(";char", "; char")); @@ -232,7 +232,7 @@ public class GenericJaxRsClientDstu2Test { p.setId("123"); - client.create().resource(p).execute(); + client.create().resource(p).encodedXml().execute(); assertEquals(1, ourRequestHeaders.get(Constants.HEADER_CONTENT_TYPE).size()); assertEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, ourRequestFirstHeaders.get(Constants.HEADER_CONTENT_TYPE).getValue().replace(";char", "; char")); String body = ourRequestBodyString; @@ -257,7 +257,7 @@ public class GenericJaxRsClientDstu2Test { Patient p = new Patient(); p.addName().addFamily("FOOFAMILY"); - client.create().resource(p).conditionalByUrl("Patient?name=foo").execute(); + client.create().resource(p).conditionalByUrl("Patient?name=foo").encodedXml().execute(); assertEquals(1, ourRequestHeaders.get(Constants.HEADER_CONTENT_TYPE).size()); assertEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, ourRequestFirstHeaders.get(Constants.HEADER_CONTENT_TYPE).getValue().replace(";char", "; char")); assertThat(ourRequestBodyString, containsString("")); @@ -266,7 +266,7 @@ public class GenericJaxRsClientDstu2Test { assertEquals("POST", ourRequestMethod); - client.create().resource(p).conditionalByUrl("Patient?name=http://foo|bar").execute(); + client.create().resource(p).conditionalByUrl("Patient?name=http://foo|bar").encodedXml().execute(); assertEquals(1, ourRequestHeaders.get(Constants.HEADER_CONTENT_TYPE).size()); assertEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, ourRequestFirstHeaders.get(Constants.HEADER_CONTENT_TYPE).getValue().replace(";char", "; char")); assertThat(ourRequestBodyString, containsString("")); @@ -275,7 +275,7 @@ public class GenericJaxRsClientDstu2Test { assertEquals("POST", ourRequestMethod); - client.create().resource(p).conditional().where(Patient.NAME.matches().value("foo")).execute(); + client.create().resource(p).conditional().where(Patient.NAME.matches().value("foo")).encodedXml().execute(); assertEquals(1, ourRequestHeaders.get(Constants.HEADER_CONTENT_TYPE).size()); assertEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, ourRequestFirstHeaders.get(Constants.HEADER_CONTENT_TYPE).getValue().replace(";char", "; char")); assertThat(ourRequestBodyString, containsString("")); @@ -495,6 +495,7 @@ public class GenericJaxRsClientDstu2Test { .add() .onResource(new IdDt("Patient/123")) .meta(inMeta) + .encodedXml() .execute(); assertEquals("http://localhost:" + ourPort + "/fhir/Patient/123/$meta-add", ourRequestUri); @@ -766,7 +767,7 @@ public class GenericJaxRsClientDstu2Test { .operation() .onServer() .named("$SOMEOPERATION") - .withParameters(inParams).execute(); + .withParameters(inParams).encodedXml().execute(); assertEquals("http://localhost:" + ourPort + "/fhir/$SOMEOPERATION", ourRequestUri); assertEquals(1, ourRequestHeaders.get(Constants.HEADER_CONTENT_TYPE).size()); @@ -801,6 +802,7 @@ public class GenericJaxRsClientDstu2Test { .named("$SOMEOPERATION") .withParameter(Parameters.class, "name1", new StringDt("value1")) .andParameter("name2", new StringDt("value1")) + .encodedXml() .execute(); assertEquals("http://localhost:" + ourPort + "/fhir/$SOMEOPERATION", ourRequestUri); @@ -822,6 +824,7 @@ public class GenericJaxRsClientDstu2Test { .named("$SOMEOPERATION") .withParameter(Parameters.class, "name1", new IdentifierDt("system1", "value1")) .andParameter("name2", new StringDt("value1")) + .encodedXml() .execute(); assertEquals("http://localhost:" + ourPort + "/fhir/$SOMEOPERATION", ourRequestUri); @@ -844,6 +847,7 @@ public class GenericJaxRsClientDstu2Test { .named("$SOMEOPERATION") .withParameter(Parameters.class, "name1", new IdentifierDt("system1", "value1")) .andParameter("name2", new Patient().setActive(true)) + .encodedXml() .execute(); assertEquals("http://localhost:" + ourPort + "/fhir/$SOMEOPERATION", ourRequestUri); @@ -937,6 +941,8 @@ public class GenericJaxRsClientDstu2Test { .named("validate-code") .withParameter(Parameters.class, "code", new CodeDt("8495-4")) .andParameter("system", new UriDt("http://loinc.org")) + .encodedXml() + .encodedXml() .execute(); @@ -975,7 +981,7 @@ public class GenericJaxRsClientDstu2Test { .operation() .onServer() .named("$SOMEOPERATION") - .withParameters(inParams).execute(); + .withParameters(inParams).encodedXml().execute(); assertEquals("http://localhost:" + ourPort + "/fhir/$SOMEOPERATION", ourRequestUri); assertEquals(respString, p.encodeResourceToString(resp)); @@ -990,7 +996,7 @@ public class GenericJaxRsClientDstu2Test { .operation() .onType(Patient.class) .named("$SOMEOPERATION") - .withParameters(inParams).execute(); + .withParameters(inParams).encodedXml().execute(); assertEquals("http://localhost:" + ourPort + "/fhir/Patient/$SOMEOPERATION", ourRequestUri); assertEquals(respString, p.encodeResourceToString(resp)); @@ -1005,7 +1011,9 @@ public class GenericJaxRsClientDstu2Test { .operation() .onInstance(new IdDt("Patient", "123")) .named("$SOMEOPERATION") - .withParameters(inParams).execute(); + .withParameters(inParams) + .encodedXml() + .execute(); assertEquals("http://localhost:" + ourPort + "/fhir/Patient/123/$SOMEOPERATION", ourRequestUri); assertEquals(respString, p.encodeResourceToString(resp)); @@ -1048,7 +1056,7 @@ public class GenericJaxRsClientDstu2Test { .operation() .onServer() .named("$SOMEOPERATION") - .withNoParameters(Parameters.class).execute(); + .withNoParameters(Parameters.class).encodedXml().execute(); assertEquals("http://localhost:" + ourPort + "/fhir/$SOMEOPERATION", ourRequestUri); assertEquals(respString, p.encodeResourceToString(resp)); @@ -1063,7 +1071,7 @@ public class GenericJaxRsClientDstu2Test { .operation() .onType(Patient.class) .named("$SOMEOPERATION") - .withNoParameters(Parameters.class).execute(); + .withNoParameters(Parameters.class).encodedXml().execute(); assertEquals("http://localhost:" + ourPort + "/fhir/Patient/$SOMEOPERATION", ourRequestUri); assertEquals(respString, p.encodeResourceToString(resp)); @@ -1078,7 +1086,7 @@ public class GenericJaxRsClientDstu2Test { .operation() .onInstance(new IdDt("Patient", "123")) .named("$SOMEOPERATION") - .withNoParameters(Parameters.class).execute(); + .withNoParameters(Parameters.class).encodedXml().execute(); assertEquals("http://localhost:" + ourPort + "/fhir/Patient/123/$SOMEOPERATION", ourRequestUri); assertEquals(respString, p.encodeResourceToString(resp)); @@ -1833,7 +1841,7 @@ public class GenericJaxRsClientDstu2Test { Patient p = new Patient(); p.addName().addFamily("FOOFAMILY"); - client.update().resource(p).conditionalByUrl("Patient?name=foo").execute(); + client.update().resource(p).conditionalByUrl("Patient?name=foo").encodedXml().execute(); assertEquals(1, ourRequestHeaders.get(Constants.HEADER_CONTENT_TYPE).size()); assertEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, ourRequestFirstHeaders.get(Constants.HEADER_CONTENT_TYPE).getValue().replace(";char", "; char")); assertThat(ourRequestBodyString, containsString("")); @@ -1841,7 +1849,7 @@ public class GenericJaxRsClientDstu2Test { assertEquals("http://localhost:" + ourPort + "/fhir/Patient?name=foo", ourRequestUri); - client.update().resource(p).conditionalByUrl("Patient?name=http://foo|bar").execute(); + client.update().resource(p).conditionalByUrl("Patient?name=http://foo|bar").encodedXml().execute(); assertEquals(1, ourRequestHeaders.get(Constants.HEADER_CONTENT_TYPE).size()); assertEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, ourRequestFirstHeaders.get(Constants.HEADER_CONTENT_TYPE).getValue().replace(";char", "; char")); assertThat(ourRequestBodyString, containsString("")); @@ -1849,7 +1857,7 @@ public class GenericJaxRsClientDstu2Test { assertEquals("http://localhost:" + ourPort + "/fhir/Patient?name=http%3A//foo%7Cbar", ourRequestUri); - client.update().resource(ourCtx.newXmlParser().encodeResourceToString(p)).conditionalByUrl("Patient?name=foo").execute(); + client.update().resource(ourCtx.newXmlParser().encodeResourceToString(p)).conditionalByUrl("Patient?name=foo").encodedXml().execute(); assertEquals(1, ourRequestHeaders.get(Constants.HEADER_CONTENT_TYPE).size()); assertEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, ourRequestFirstHeaders.get(Constants.HEADER_CONTENT_TYPE).getValue().replace(";char", "; char")); assertThat(ourRequestBodyString, containsString("")); @@ -1857,7 +1865,7 @@ public class GenericJaxRsClientDstu2Test { assertEquals("http://localhost:" + ourPort + "/fhir/Patient?name=foo", ourRequestUri); - client.update().resource(p).conditional().where(Patient.NAME.matches().value("foo")).and(Patient.ADDRESS.matches().value("AAA|BBB")).execute(); + client.update().resource(p).conditional().where(Patient.NAME.matches().value("foo")).and(Patient.ADDRESS.matches().value("AAA|BBB")).encodedXml().execute(); assertEquals(1, ourRequestHeaders.get(Constants.HEADER_CONTENT_TYPE).size()); assertEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, ourRequestFirstHeaders.get(Constants.HEADER_CONTENT_TYPE).getValue().replace(";char", "; char")); assertThat(ourRequestBodyString, containsString("")); @@ -1865,7 +1873,7 @@ public class GenericJaxRsClientDstu2Test { assertEquals("http://localhost:" + ourPort + "/fhir/Patient?name=foo&address=AAA%5C%7CBBB", ourRequestUri); - client.update().resource(ourCtx.newXmlParser().encodeResourceToString(p)).conditional().where(Patient.NAME.matches().value("foo")).and(Patient.ADDRESS.matches().value("AAA|BBB")).execute(); + client.update().resource(ourCtx.newXmlParser().encodeResourceToString(p)).conditional().where(Patient.NAME.matches().value("foo")).and(Patient.ADDRESS.matches().value("AAA|BBB")).encodedXml().execute(); assertEquals(1, ourRequestHeaders.get(Constants.HEADER_CONTENT_TYPE).size()); assertEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, ourRequestFirstHeaders.get(Constants.HEADER_CONTENT_TYPE).getValue().replace(";char", "; char")); assertThat(ourRequestBodyString, containsString("")); @@ -1882,7 +1890,7 @@ public class GenericJaxRsClientDstu2Test { ourResponseStatus = Constants.STATUS_HTTP_204_NO_CONTENT; IGenericClient client = ourCtx.newRestfulGenericClient("http://localhost:" + ourPort + "/fhir"); - + client.setEncoding(EncodingEnum.XML); Patient p = new Patient(); @@ -1892,7 +1900,7 @@ public class GenericJaxRsClientDstu2Test { assertEquals(1, ourRequestHeaders.get(Constants.HEADER_CONTENT_TYPE).size()); assertEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, ourRequestFirstHeaders.get(Constants.HEADER_CONTENT_TYPE).getValue().replace(";char", "; char")); assertThat(ourRequestBodyString, containsString("")); - assertEquals("http://localhost:" + ourPort + "/fhir/Patient/123", ourRequestUri); + assertEquals("http://localhost:" + ourPort + "/fhir/Patient/123?_format=xml", ourRequestUri); assertEquals("PUT", ourRequestMethod); @@ -1900,7 +1908,7 @@ public class GenericJaxRsClientDstu2Test { assertEquals(1, ourRequestHeaders.get(Constants.HEADER_CONTENT_TYPE).size()); assertEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, ourRequestFirstHeaders.get(Constants.HEADER_CONTENT_TYPE).getValue().replace(";char", "; char")); assertThat(ourRequestBodyString, containsString("")); - assertEquals("http://localhost:" + ourPort + "/fhir/Patient/123", ourRequestUri); + assertEquals("http://localhost:" + ourPort + "/fhir/Patient/123?_format=xml", ourRequestUri); assertEquals("PUT", ourRequestMethod); } @@ -1975,7 +1983,7 @@ public class GenericJaxRsClientDstu2Test { MethodOutcome response; - response = client.validate().resource(p).execute(); + response = client.validate().resource(p).encodedXml().execute(); assertEquals("http://localhost:" + ourPort + "/fhir/Patient/$validate", ourRequestUri); assertEquals("POST", ourRequestMethod); assertEquals("", ourRequestBodyString); @@ -1983,7 +1991,7 @@ public class GenericJaxRsClientDstu2Test { assertEquals("FOOBAR", toOo(response.getOperationOutcome()).getIssueFirstRep().getDiagnosticsElement().getValue()); - response = client.validate().resource(ourCtx.newXmlParser().encodeResourceToString(p)).execute(); + response = client.validate().resource(ourCtx.newXmlParser().encodeResourceToString(p)).encodedXml().execute(); assertEquals("http://localhost:" + ourPort + "/fhir/Patient/$validate", ourRequestUri); assertEquals("POST", ourRequestMethod); assertEquals("", ourRequestBodyString); @@ -2018,6 +2026,7 @@ public class GenericJaxRsClientDstu2Test { ourResponseBody = msg; IGenericClient client = ourCtx.newRestfulGenericClient("http://localhost:" + ourPort + "/fhir"); + client.setEncoding(EncodingEnum.XML); Patient p = new Patient(); p.addName().addGiven("GIVEN"); @@ -2029,7 +2038,7 @@ public class GenericJaxRsClientDstu2Test { response = client.validate(p); - assertEquals("http://localhost:" + ourPort + "/fhir/Patient/$validate", ourRequestUri); + assertEquals("http://localhost:" + ourPort + "/fhir/Patient/$validate?_format=xml", ourRequestUri); assertEquals("POST", ourRequestMethod); assertEquals("", ourRequestBodyString); assertNotNull(response.getOperationOutcome()); diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/client/GenericJaxRsClientDstu3Test.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/client/GenericJaxRsClientDstu3Test.java index 6315e607d69..2a6a2733ea9 100644 --- a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/client/GenericJaxRsClientDstu3Test.java +++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/client/GenericJaxRsClientDstu3Test.java @@ -222,7 +222,7 @@ public class GenericJaxRsClientDstu3Test { Patient p = new Patient(); p.addName().setFamily("FOOFAMILY"); - client.create().resource(p).execute(); + client.create().resource(p).encodedXml().execute(); assertEquals(1, ourRequestHeaders.get(Constants.HEADER_CONTENT_TYPE).size()); assertEquals(EncodingEnum.XML.getResourceContentTypeNonLegacy() + Constants.HEADER_SUFFIX_CT_UTF_8, ourRequestFirstHeaders.get(Constants.HEADER_CONTENT_TYPE).getValue().replace(";char", "; char")); @@ -233,7 +233,7 @@ public class GenericJaxRsClientDstu3Test { p.setId("123"); - client.create().resource(p).execute(); + client.create().resource(p).encodedXml().execute(); assertEquals(1, ourRequestHeaders.get(Constants.HEADER_CONTENT_TYPE).size()); assertEquals(EncodingEnum.XML.getResourceContentTypeNonLegacy() + Constants.HEADER_SUFFIX_CT_UTF_8, ourRequestFirstHeaders.get(Constants.HEADER_CONTENT_TYPE).getValue().replace(";char", "; char")); String body = ourRequestBodyString; @@ -258,7 +258,7 @@ public class GenericJaxRsClientDstu3Test { Patient p = new Patient(); p.addName().setFamily("FOOFAMILY"); - client.create().resource(p).conditionalByUrl("Patient?name=foo").execute(); + client.create().resource(p).conditionalByUrl("Patient?name=foo").encodedXml().execute(); assertEquals(1, ourRequestHeaders.get(Constants.HEADER_CONTENT_TYPE).size()); assertEquals(EncodingEnum.XML.getResourceContentTypeNonLegacy() + Constants.HEADER_SUFFIX_CT_UTF_8, ourRequestFirstHeaders.get(Constants.HEADER_CONTENT_TYPE).getValue().replace(";char", "; char")); assertThat(ourRequestBodyString, containsString("")); @@ -267,7 +267,7 @@ public class GenericJaxRsClientDstu3Test { assertEquals("POST", ourRequestMethod); - client.create().resource(p).conditionalByUrl("Patient?name=http://foo|bar").execute(); + client.create().resource(p).conditionalByUrl("Patient?name=http://foo|bar").encodedXml().execute(); assertEquals(1, ourRequestHeaders.get(Constants.HEADER_CONTENT_TYPE).size()); assertEquals(EncodingEnum.XML.getResourceContentTypeNonLegacy() + Constants.HEADER_SUFFIX_CT_UTF_8, ourRequestFirstHeaders.get(Constants.HEADER_CONTENT_TYPE).getValue().replace(";char", "; char")); assertThat(ourRequestBodyString, containsString("")); @@ -276,7 +276,7 @@ public class GenericJaxRsClientDstu3Test { assertEquals("POST", ourRequestMethod); - client.create().resource(p).conditional().where(Patient.NAME.matches().value("foo")).execute(); + client.create().resource(p).conditional().where(Patient.NAME.matches().value("foo")).encodedXml().execute(); assertEquals(1, ourRequestHeaders.get(Constants.HEADER_CONTENT_TYPE).size()); assertEquals(EncodingEnum.XML.getResourceContentTypeNonLegacy() + Constants.HEADER_SUFFIX_CT_UTF_8, ourRequestFirstHeaders.get(Constants.HEADER_CONTENT_TYPE).getValue().replace(";char", "; char")); assertThat(ourRequestBodyString, containsString("")); @@ -298,7 +298,7 @@ public class GenericJaxRsClientDstu3Test { Patient p = new Patient(); p.addName().setFamily("FOOFAMILY"); - client.create().resource(p).execute(); + client.create().resource(p).encodedXml().execute(); assertEquals(1, ourRequestHeaders.get(Constants.HEADER_CONTENT_TYPE).size()); assertEquals(EncodingEnum.XML.getResourceContentTypeNonLegacy() + Constants.HEADER_SUFFIX_CT_UTF_8, ourRequestFirstHeaders.get(Constants.HEADER_CONTENT_TYPE).getValue().replace(";char", "; char")); @@ -515,6 +515,7 @@ public class GenericJaxRsClientDstu3Test { .add() .onResource(new IdType("Patient/123")) .meta(inMeta) + .encodedXml() .execute(); //@formatter:on assertEquals("http://localhost:" + ourPort + "/fhir/Patient/123/$meta-add", ourRequestUri); @@ -786,7 +787,9 @@ public class GenericJaxRsClientDstu3Test { .operation() .onServer() .named("$SOMEOPERATION") - .withParameters(inParams).execute(); + .withParameters(inParams) + .encodedXml() + .execute(); //@formatter:on assertEquals("http://localhost:" + ourPort + "/fhir/$SOMEOPERATION", ourRequestUri); assertEquals(1, ourRequestHeaders.get(Constants.HEADER_CONTENT_TYPE).size()); @@ -821,6 +824,7 @@ public class GenericJaxRsClientDstu3Test { .named("$SOMEOPERATION") .withParameter(Parameters.class, "name1", new StringType("value1")) .andParameter("name2", new StringType("value1")) + .encodedXml() .execute(); //@formatter:on assertEquals("http://localhost:" + ourPort + "/fhir/$SOMEOPERATION", ourRequestUri); @@ -842,6 +846,7 @@ public class GenericJaxRsClientDstu3Test { .named("$SOMEOPERATION") .withParameter(Parameters.class, "name1", new Identifier().setSystem("system1").setValue("value1")) .andParameter("name2", new StringType("value1")) + .encodedXml() .execute(); //@formatter:on assertEquals("http://localhost:" + ourPort + "/fhir/$SOMEOPERATION", ourRequestUri); @@ -864,6 +869,7 @@ public class GenericJaxRsClientDstu3Test { .named("$SOMEOPERATION") .withParameter(Parameters.class, "name1", new Identifier().setSystem("system1").setValue("value1")) .andParameter("name2", new Patient().setActive(true)) + .encodedXml() .execute(); //@formatter:on assertEquals("http://localhost:" + ourPort + "/fhir/$SOMEOPERATION", ourRequestUri); @@ -957,6 +963,7 @@ public class GenericJaxRsClientDstu3Test { .named("validate-code") .withParameter(Parameters.class, "code", new CodeType("8495-4")) .andParameter("system", new UriType("http://loinc.org")) + .encodedXml() .execute(); //@formatter:off @@ -995,7 +1002,9 @@ public class GenericJaxRsClientDstu3Test { .operation() .onServer() .named("$SOMEOPERATION") - .withParameters(inParams).execute(); + .withParameters(inParams) + .encodedXml() + .execute(); //@formatter:on assertEquals("http://localhost:" + ourPort + "/fhir/$SOMEOPERATION", ourRequestUri); assertEquals(respString, p.encodeResourceToString(resp)); @@ -1010,7 +1019,9 @@ public class GenericJaxRsClientDstu3Test { .operation() .onType(Patient.class) .named("$SOMEOPERATION") - .withParameters(inParams).execute(); + .withParameters(inParams) + .encodedXml() + .execute(); //@formatter:on assertEquals("http://localhost:" + ourPort + "/fhir/Patient/$SOMEOPERATION", ourRequestUri); assertEquals(respString, p.encodeResourceToString(resp)); @@ -1025,7 +1036,9 @@ public class GenericJaxRsClientDstu3Test { .operation() .onInstance(new IdType("Patient", "123")) .named("$SOMEOPERATION") - .withParameters(inParams).execute(); + .withParameters(inParams) + .encodedXml() + .execute(); //@formatter:on assertEquals("http://localhost:" + ourPort + "/fhir/Patient/123/$SOMEOPERATION", ourRequestUri); assertEquals(respString, p.encodeResourceToString(resp)); @@ -1068,7 +1081,7 @@ public class GenericJaxRsClientDstu3Test { .operation() .onServer() .named("$SOMEOPERATION") - .withNoParameters(Parameters.class).execute(); + .withNoParameters(Parameters.class).encodedXml().execute(); //@formatter:on assertEquals("http://localhost:" + ourPort + "/fhir/$SOMEOPERATION", ourRequestUri); assertEquals(respString, p.encodeResourceToString(resp)); @@ -1083,7 +1096,7 @@ public class GenericJaxRsClientDstu3Test { .operation() .onType(Patient.class) .named("$SOMEOPERATION") - .withNoParameters(Parameters.class).execute(); + .withNoParameters(Parameters.class).encodedXml().execute(); //@formatter:on assertEquals("http://localhost:" + ourPort + "/fhir/Patient/$SOMEOPERATION", ourRequestUri); assertEquals(respString, p.encodeResourceToString(resp)); @@ -1098,7 +1111,9 @@ public class GenericJaxRsClientDstu3Test { .operation() .onInstance(new IdType("Patient", "123")) .named("$SOMEOPERATION") - .withNoParameters(Parameters.class).execute(); + .withNoParameters(Parameters.class) + .encodedXml() + .execute(); //@formatter:on assertEquals("http://localhost:" + ourPort + "/fhir/Patient/123/$SOMEOPERATION", ourRequestUri); assertEquals(respString, p.encodeResourceToString(resp)); @@ -1878,7 +1893,7 @@ public class GenericJaxRsClientDstu3Test { Patient p = new Patient(); p.addName().setFamily("FOOFAMILY"); - client.update().resource(p).conditionalByUrl("Patient?name=foo").execute(); + client.update().resource(p).conditionalByUrl("Patient?name=foo").encodedXml().execute(); assertEquals(1, ourRequestHeaders.get(Constants.HEADER_CONTENT_TYPE).size()); assertEquals(EncodingEnum.XML.getResourceContentTypeNonLegacy() + Constants.HEADER_SUFFIX_CT_UTF_8, ourRequestFirstHeaders.get(Constants.HEADER_CONTENT_TYPE).getValue().replace(";char", "; char")); assertThat(ourRequestBodyString, containsString("")); @@ -1886,7 +1901,7 @@ public class GenericJaxRsClientDstu3Test { assertEquals("http://localhost:" + ourPort + "/fhir/Patient?name=foo", ourRequestUri); - client.update().resource(p).conditionalByUrl("Patient?name=http://foo|bar").execute(); + client.update().resource(p).conditionalByUrl("Patient?name=http://foo|bar").encodedXml().execute(); assertEquals(1, ourRequestHeaders.get(Constants.HEADER_CONTENT_TYPE).size()); assertEquals(EncodingEnum.XML.getResourceContentTypeNonLegacy() + Constants.HEADER_SUFFIX_CT_UTF_8, ourRequestFirstHeaders.get(Constants.HEADER_CONTENT_TYPE).getValue().replace(";char", "; char")); assertThat(ourRequestBodyString, containsString("")); @@ -1902,7 +1917,7 @@ public class GenericJaxRsClientDstu3Test { assertEquals("http://localhost:" + ourPort + "/fhir/Patient?name=foo", ourRequestUri); - client.update().resource(p).conditional().where(Patient.NAME.matches().value("foo")).and(Patient.ADDRESS.matches().value("AAA|BBB")).execute(); + client.update().resource(p).conditional().where(Patient.NAME.matches().value("foo")).and(Patient.ADDRESS.matches().value("AAA|BBB")).encodedXml().execute(); assertEquals(1, ourRequestHeaders.get(Constants.HEADER_CONTENT_TYPE).size()); assertEquals(EncodingEnum.XML.getResourceContentTypeNonLegacy() + Constants.HEADER_SUFFIX_CT_UTF_8, ourRequestFirstHeaders.get(Constants.HEADER_CONTENT_TYPE).getValue().replace(";char", "; char")); assertThat(ourRequestBodyString, containsString("")); @@ -1910,7 +1925,7 @@ public class GenericJaxRsClientDstu3Test { assertEquals("http://localhost:" + ourPort + "/fhir/Patient?name=foo&address=AAA%5C%7CBBB", ourRequestUri); - client.update().resource(ourCtx.newXmlParser().encodeResourceToString(p)).conditional().where(Patient.NAME.matches().value("foo")).and(Patient.ADDRESS.matches().value("AAA|BBB")).execute(); + client.update().resource(ourCtx.newXmlParser().encodeResourceToString(p)).conditional().where(Patient.NAME.matches().value("foo")).and(Patient.ADDRESS.matches().value("AAA|BBB")).encodedXml().execute(); assertEquals(1, ourRequestHeaders.get(Constants.HEADER_CONTENT_TYPE).size()); assertEquals(EncodingEnum.XML.getResourceContentTypeNonLegacy() + Constants.HEADER_SUFFIX_CT_UTF_8, ourRequestFirstHeaders.get(Constants.HEADER_CONTENT_TYPE).getValue().replace(";char", "; char")); assertThat(ourRequestBodyString, containsString("")); @@ -1927,6 +1942,7 @@ public class GenericJaxRsClientDstu3Test { ourResponseStatus = Constants.STATUS_HTTP_204_NO_CONTENT; IGenericClient client = ourCtx.newRestfulGenericClient("http://localhost:" + ourPort + "/fhir"); + client.setEncoding(EncodingEnum.XML); @@ -1937,7 +1953,7 @@ public class GenericJaxRsClientDstu3Test { assertEquals(1, ourRequestHeaders.get(Constants.HEADER_CONTENT_TYPE).size()); assertEquals(EncodingEnum.XML.getResourceContentTypeNonLegacy() + Constants.HEADER_SUFFIX_CT_UTF_8, ourRequestFirstHeaders.get(Constants.HEADER_CONTENT_TYPE).getValue().replace(";char", "; char")); assertThat(ourRequestBodyString, containsString("")); - assertEquals("http://localhost:" + ourPort + "/fhir/Patient/123", ourRequestUri); + assertEquals("http://localhost:" + ourPort + "/fhir/Patient/123?_format=xml", ourRequestUri); assertEquals("PUT", ourRequestMethod); @@ -1945,7 +1961,7 @@ public class GenericJaxRsClientDstu3Test { assertEquals(1, ourRequestHeaders.get(Constants.HEADER_CONTENT_TYPE).size()); assertEquals(EncodingEnum.XML.getResourceContentTypeNonLegacy() + Constants.HEADER_SUFFIX_CT_UTF_8, ourRequestFirstHeaders.get(Constants.HEADER_CONTENT_TYPE).getValue().replace(";char", "; char")); assertThat(ourRequestBodyString, containsString("")); - assertEquals("http://localhost:" + ourPort + "/fhir/Patient/123", ourRequestUri); + assertEquals("http://localhost:" + ourPort + "/fhir/Patient/123?_format=xml", ourRequestUri); assertEquals("PUT", ourRequestMethod); } @@ -2020,7 +2036,7 @@ public class GenericJaxRsClientDstu3Test { MethodOutcome response; - response = client.validate().resource(p).execute(); + response = client.validate().resource(p).encodedXml().execute(); assertEquals("http://localhost:" + ourPort + "/fhir/Patient/$validate", ourRequestUri); assertEquals("POST", ourRequestMethod); assertEquals("", ourRequestBodyString); @@ -2063,6 +2079,7 @@ public class GenericJaxRsClientDstu3Test { ourResponseBody = msg; IGenericClient client = ourCtx.newRestfulGenericClient("http://localhost:" + ourPort + "/fhir"); + client.setEncoding(EncodingEnum.XML); Patient p = new Patient(); p.addName().addGiven("GIVEN"); @@ -2074,7 +2091,7 @@ public class GenericJaxRsClientDstu3Test { response = client.validate(p); //@formatter:on - assertEquals("http://localhost:" + ourPort + "/fhir/Patient/$validate", ourRequestUri); + assertEquals("http://localhost:" + ourPort + "/fhir/Patient/$validate?_format=xml", ourRequestUri); assertEquals("POST", ourRequestMethod); assertEquals("", ourRequestBodyString); assertNotNull(response.getOperationOutcome()); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/binstore/BaseBinaryStorageSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/binstore/BaseBinaryStorageSvcImpl.java index 41792fe7d70..b73d8660c3f 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/binstore/BaseBinaryStorageSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/binstore/BaseBinaryStorageSvcImpl.java @@ -23,6 +23,9 @@ package ca.uhn.fhir.jpa.binstore; import com.google.common.hash.HashFunction; import com.google.common.hash.Hashing; import com.google.common.hash.HashingInputStream; +import com.google.common.io.ByteStreams; +import com.google.common.io.CountingInputStream; +import org.apache.commons.lang3.Validate; import org.hl7.fhir.instance.model.api.IIdType; import javax.annotation.Nonnull; @@ -30,18 +33,35 @@ import java.io.InputStream; import java.security.SecureRandom; abstract class BaseBinaryStorageSvcImpl implements IBinaryStorageSvc { - private final SecureRandom myRandom; private final String CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; private final int ID_LENGTH = 100; - private int myMinSize; + private int myMaximumBinarySize = Integer.MAX_VALUE; + private int myMinimumBinarySize; BaseBinaryStorageSvcImpl() { myRandom = new SecureRandom(); } - public void setMinSize(int theMinSize) { - myMinSize = theMinSize; + @Override + public int getMaximumBinarySize() { + return myMaximumBinarySize; + } + + @Override + public int getMinimumBinarySize() { + return myMinimumBinarySize; + } + + @Override + public void setMinimumBinarySize(int theMinimumBinarySize) { + myMinimumBinarySize = theMinimumBinarySize; + } + + @Override + public void setMaximumBinarySize(int theMaximumBinarySize) { + Validate.inclusiveBetween(1, Integer.MAX_VALUE, theMaximumBinarySize); + myMaximumBinarySize = theMaximumBinarySize; } String newRandomId() { @@ -55,12 +75,22 @@ abstract class BaseBinaryStorageSvcImpl implements IBinaryStorageSvc { @Override public boolean shouldStoreBlob(long theSize, IIdType theResourceId, String theContentType) { - return theSize >= myMinSize; + return theSize >= getMinimumBinarySize(); } + @SuppressWarnings("UnstableApiUsage") @Nonnull - static HashingInputStream createHashingInputStream(InputStream theInputStream) { + HashingInputStream createHashingInputStream(InputStream theInputStream) { HashFunction hash = Hashing.sha256(); return new HashingInputStream(hash, theInputStream); } + + @SuppressWarnings("UnstableApiUsage") + @Nonnull + CountingInputStream createCountingInputStream(InputStream theInputStream) { + InputStream stream = ByteStreams.limit(theInputStream, myMaximumBinarySize); + return new CountingInputStream(stream); + } + + } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/binstore/BinaryAccessProvider.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/binstore/BinaryAccessProvider.java new file mode 100644 index 00000000000..30be8a7a9f4 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/binstore/BinaryAccessProvider.java @@ -0,0 +1,358 @@ +package ca.uhn.fhir.jpa.binstore; + +/*- + * #%L + * HAPI FHIR JPA Server + * %% + * Copyright (C) 2014 - 2019 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import ca.uhn.fhir.context.BaseRuntimeElementDefinition; +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.dao.DaoMethodOutcome; +import ca.uhn.fhir.jpa.dao.DaoRegistry; +import ca.uhn.fhir.jpa.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.model.util.JpaConstants; +import ca.uhn.fhir.rest.annotation.IdParam; +import ca.uhn.fhir.rest.annotation.Operation; +import ca.uhn.fhir.rest.annotation.OperationParam; +import ca.uhn.fhir.rest.api.Constants; +import ca.uhn.fhir.rest.api.EncodingEnum; +import ca.uhn.fhir.rest.server.RestfulServer; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; +import ca.uhn.fhir.util.AttachmentUtil; +import ca.uhn.fhir.util.BinaryUtil; +import ca.uhn.fhir.util.DateUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.Validate; +import org.hl7.fhir.instance.model.api.*; +import org.springframework.beans.factory.annotation.Autowired; + +import javax.annotation.Nonnull; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Optional; + +import static ca.uhn.fhir.util.UrlUtil.sanitizeUrlPart; +import static org.apache.commons.lang3.StringUtils.isBlank; + +/** + * This plain provider class can be registered with a JPA RestfulServer + * to provide the $binary-access-read and $binary-access-write + * operations that can be used to access attachment data as a raw binary. + */ +public class BinaryAccessProvider { + + @Autowired + private FhirContext myCtx; + @Autowired + private DaoRegistry myDaoRegistry; + @Autowired(required = false) + private IBinaryStorageSvc myBinaryStorageSvc; + + /** + * $binary-access-read + */ + @Operation(name = JpaConstants.OPERATION_BINARY_ACCESS_READ, global = true, manualResponse = true, idempotent = true) + public void binaryAccessRead( + @IdParam IIdType theResourceId, + @OperationParam(name = "path", min = 1, max = 1) IPrimitiveType thePath, + ServletRequestDetails theRequestDetails, + HttpServletRequest theServletRequest, + HttpServletResponse theServletResponse) throws IOException { + + String path = validateResourceTypeAndPath(theResourceId, thePath); + IFhirResourceDao dao = getDaoForRequest(theResourceId); + IBaseResource resource = dao.read(theResourceId, theRequestDetails, false); + + IBinaryTarget target = findAttachmentForRequest(resource, path, theRequestDetails); + + Optional> attachmentId = target + .getTarget() + .getExtension() + .stream() + .filter(t -> JpaConstants.EXT_EXTERNALIZED_BINARY_ID.equals(t.getUrl())) + .findFirst(); + + if (attachmentId.isPresent()) { + + @SuppressWarnings("unchecked") + IPrimitiveType value = (IPrimitiveType) attachmentId.get().getValue(); + String blobId = value.getValueAsString(); + + IBinaryStorageSvc.StoredDetails blobDetails = myBinaryStorageSvc.fetchBlobDetails(theResourceId, blobId); + if (blobDetails == null) { + String msg = myCtx.getLocalizer().getMessage(BinaryAccessProvider.class, "unknownBlobId"); + throw new InvalidRequestException(msg); + } + + theServletResponse.setStatus(200); + theServletResponse.setContentType(blobDetails.getContentType()); + if (blobDetails.getBytes() <= Integer.MAX_VALUE) { + theServletResponse.setContentLength((int) blobDetails.getBytes()); + } + + RestfulServer server = theRequestDetails.getServer(); + server.addHeadersToResponse(theServletResponse); + + theServletResponse.addHeader(Constants.HEADER_CACHE_CONTROL, Constants.CACHE_CONTROL_PRIVATE); + theServletResponse.addHeader(Constants.HEADER_ETAG, '"' + blobDetails.getHash() + '"'); + theServletResponse.addHeader(Constants.HEADER_LAST_MODIFIED, DateUtils.formatDate(blobDetails.getPublished())); + + myBinaryStorageSvc.writeBlob(theResourceId, blobId, theServletResponse.getOutputStream()); + theServletResponse.getOutputStream().close(); + + } else { + + String contentType = target.getContentType(); + contentType = StringUtils.defaultIfBlank(contentType, Constants.CT_OCTET_STREAM); + + byte[] data = target.getData(); + if (data == null) { + String msg = myCtx.getLocalizer().getMessage(BinaryAccessProvider.class, "noAttachmentDataPresent", sanitizeUrlPart(theResourceId), sanitizeUrlPart(thePath)); + throw new InvalidRequestException(msg); + } + + theServletResponse.setStatus(200); + theServletResponse.setContentType(contentType); + theServletResponse.setContentLength(data.length); + + RestfulServer server = theRequestDetails.getServer(); + server.addHeadersToResponse(theServletResponse); + + theServletResponse.getOutputStream().write(data); + theServletResponse.getOutputStream().close(); + + } + } + + /** + * $binary-access-write + */ + @SuppressWarnings("unchecked") + @Operation(name = JpaConstants.OPERATION_BINARY_ACCESS_WRITE, global = true, manualRequest = true, idempotent = false) + public IBaseResource binaryAccessWrite( + @IdParam IIdType theResourceId, + @OperationParam(name = "path", min = 1, max = 1) IPrimitiveType thePath, + ServletRequestDetails theRequestDetails, + HttpServletRequest theServletRequest, + HttpServletResponse theServletResponse) throws IOException { + + String path = validateResourceTypeAndPath(theResourceId, thePath); + IFhirResourceDao dao = getDaoForRequest(theResourceId); + IBaseResource resource = dao.read(theResourceId, theRequestDetails, false); + + IBinaryTarget target = findAttachmentForRequest(resource, path, theRequestDetails); + + String requestContentType = theServletRequest.getContentType(); + if (isBlank(requestContentType)) { + throw new InvalidRequestException("No content-target supplied"); + } + if (EncodingEnum.forContentTypeStrict(requestContentType) != null) { + throw new InvalidRequestException("This operation is for binary content, got: " + requestContentType); + } + + long size = theServletRequest.getContentLength(); + String blobId = null; + + if (size > 0) { + if (myBinaryStorageSvc != null) { + if (myBinaryStorageSvc.shouldStoreBlob(size, theResourceId, requestContentType)) { + IBinaryStorageSvc.StoredDetails storedDetails = myBinaryStorageSvc.storeBlob(theResourceId, requestContentType, theRequestDetails.getInputStream()); + size = storedDetails.getBytes(); + blobId = storedDetails.getBlobId(); + Validate.notBlank(blobId, "BinaryStorageSvc returned a null blob ID"); // should not happen + } + } + } + + if (blobId == null) { + byte[] bytes = IOUtils.toByteArray(theRequestDetails.getInputStream()); + size = bytes.length; + target.setData(bytes); + } else { + + target + .getTarget() + .getExtension() + .removeIf(t -> JpaConstants.EXT_EXTERNALIZED_BINARY_ID.equals(t.getUrl())); + target.setData(null); + + IBaseExtension ext = target.getTarget().addExtension(); + ext.setUrl(JpaConstants.EXT_EXTERNALIZED_BINARY_ID); + IPrimitiveType blobIdString = (IPrimitiveType) myCtx.getElementDefinition("string").newInstance(); + blobIdString.setValueAsString(blobId); + ext.setValue(blobIdString); + } + + target.setContentType(requestContentType); + target.setSize(null); + if (size <= Integer.MAX_VALUE) { + target.setSize((int) size); + } + + DaoMethodOutcome outcome = dao.update(resource, theRequestDetails); + return outcome.getResource(); + } + + @Nonnull + private IBinaryTarget findAttachmentForRequest(IBaseResource theResource, String thePath, ServletRequestDetails theRequestDetails) { + FhirContext ctx = theRequestDetails.getFhirContext(); + + Optional type = ctx.newFluentPath().evaluateFirst(theResource, thePath, IBase.class); + String resType = myCtx.getResourceDefinition(theResource).getName(); + if (!type.isPresent()) { + String msg = myCtx.getLocalizer().getMessageSanitized(BinaryAccessProvider.class, "unknownPath", resType, thePath); + throw new InvalidRequestException(msg); + } + + // Path is attachment + BaseRuntimeElementDefinition def = ctx.getElementDefinition(type.get().getClass()); + if (def.getName().equals("Attachment")) { + ICompositeType attachment = (ICompositeType) type.get(); + return new IBinaryTarget() { + @Override + public void setSize(Integer theSize) { + AttachmentUtil.setSize(myCtx, attachment, theSize); + } + + @Override + public String getContentType() { + return AttachmentUtil.getOrCreateContentType(myCtx, attachment).getValueAsString(); + } + + @Override + public byte[] getData() { + IPrimitiveType dataDt = AttachmentUtil.getOrCreateData(theRequestDetails.getFhirContext(), attachment); + return dataDt.getValue(); + } + + @Override + public IBaseHasExtensions getTarget() { + return (IBaseHasExtensions) AttachmentUtil.getOrCreateData(theRequestDetails.getFhirContext(), attachment); + } + + @Override + public void setContentType(String theContentType) { + AttachmentUtil.setContentType(myCtx, attachment, theContentType); + } + + + @Override + public void setData(byte[] theBytes) { + AttachmentUtil.setData(theRequestDetails.getFhirContext(), attachment, theBytes); + } + + + }; + } + + // Path is Binary + if (def.getName().equals("Binary")) { + IBaseBinary binary = (IBaseBinary) type.get(); + return new IBinaryTarget() { + @Override + public void setSize(Integer theSize) { + // ignore + } + + @Override + public String getContentType() { + return binary.getContentType(); + } + + @Override + public byte[] getData() { + return binary.getContent(); + } + + @Override + public IBaseHasExtensions getTarget() { + return (IBaseHasExtensions) BinaryUtil.getOrCreateData(myCtx, binary); + } + + @Override + public void setContentType(String theContentType) { + binary.setContentType(theContentType); + } + + + @Override + public void setData(byte[] theBytes) { + binary.setContent(theBytes); + } + + + }; + } + + String msg = myCtx.getLocalizer().getMessageSanitized(BinaryAccessProvider.class, "unknownType", resType, thePath, def.getName()); + throw new InvalidRequestException(msg); + + } + + private String validateResourceTypeAndPath(@IdParam IIdType theResourceId, @OperationParam(name = "path", min = 1, max = 1) IPrimitiveType thePath) { + if (isBlank(theResourceId.getResourceType())) { + throw new InvalidRequestException("No resource type specified"); + } + if (isBlank(theResourceId.getIdPart())) { + throw new InvalidRequestException("No ID specified"); + } + if (thePath == null || isBlank(thePath.getValue())) { + if ("Binary".equals(theResourceId.getResourceType())) { + return "Binary"; + } + throw new InvalidRequestException("No path specified"); + } + + return thePath.getValue(); + } + + @Nonnull + private IFhirResourceDao getDaoForRequest(@IdParam IIdType theResourceId) { + String resourceType = theResourceId.getResourceType(); + IFhirResourceDao dao = myDaoRegistry.getResourceDao(resourceType); + if (dao == null) { + throw new InvalidRequestException("Unknown/unsupported resource type: " + sanitizeUrlPart(resourceType)); + } + return dao; + } + + /** + * Wraps an Attachment datatype or Binary resource, since they both + * hold binary content but don't look entirely similar + */ + private interface IBinaryTarget { + + void setSize(Integer theSize); + + String getContentType(); + + void setContentType(String theContentType); + + byte[] getData(); + + void setData(byte[] theBytes); + + IBaseHasExtensions getTarget(); + + } + + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/binstore/BinaryStorageInterceptor.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/binstore/BinaryStorageInterceptor.java new file mode 100644 index 00000000000..3b77ccd69be --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/binstore/BinaryStorageInterceptor.java @@ -0,0 +1,44 @@ +package ca.uhn.fhir.jpa.binstore; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.interceptor.api.Hook; +import ca.uhn.fhir.interceptor.api.Interceptor; +import ca.uhn.fhir.interceptor.api.Pointcut; +import ca.uhn.fhir.jpa.model.util.JpaConstants; +import org.hl7.fhir.instance.model.api.IBase; +import org.hl7.fhir.instance.model.api.IBaseHasExtensions; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IPrimitiveType; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.List; +import java.util.stream.Collectors; + +@Interceptor +public class BinaryStorageInterceptor { + + @Autowired + private IBinaryStorageSvc myBinaryStorageSvc; + @Autowired + private FhirContext myCtx; + + @Hook(Pointcut.STORAGE_PRESTORAGE_EXPUNGE_RESOURCE) + public void expungeResource(IBaseResource theResource) { + + Class binaryType = myCtx.getElementDefinition("base64Binary").getImplementingClass(); + List binaryElements = myCtx.newTerser().getAllPopulatedChildElementsOfType(theResource, binaryType); + + List attachmentIds = binaryElements + .stream() + .flatMap(t -> ((IBaseHasExtensions) t).getExtension().stream()) + .filter(t -> JpaConstants.EXT_EXTERNALIZED_BINARY_ID.equals(t.getUrl())) + .map(t -> ((IPrimitiveType) t.getValue()).getValueAsString()) + .collect(Collectors.toList()); + + for (String next : attachmentIds) { + myBinaryStorageSvc.expungeBlob(theResource.getIdElement(), next); + } + + } + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/binstore/DatabaseBlobBinaryStorageSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/binstore/DatabaseBlobBinaryStorageSvcImpl.java new file mode 100644 index 00000000000..9032f26a99e --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/binstore/DatabaseBlobBinaryStorageSvcImpl.java @@ -0,0 +1,124 @@ +package ca.uhn.fhir.jpa.binstore; + +import ca.uhn.fhir.jpa.dao.data.IBinaryStorageEntityDao; +import ca.uhn.fhir.jpa.model.entity.BinaryStorageEntity; +import com.google.common.hash.HashingInputStream; +import com.google.common.io.CountingInputStream; +import org.apache.commons.io.IOUtils; +import org.hibernate.LobHelper; +import org.hibernate.Session; +import org.hl7.fhir.instance.model.api.IIdType; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.support.TransactionTemplate; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import javax.persistence.PersistenceContextType; +import javax.transaction.Transactional; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.sql.Blob; +import java.sql.SQLException; +import java.util.Date; +import java.util.Optional; + +@Transactional +public class DatabaseBlobBinaryStorageSvcImpl extends BaseBinaryStorageSvcImpl { + + @PersistenceContext(type = PersistenceContextType.TRANSACTION) + private EntityManager myEntityManager; + @Autowired + private IBinaryStorageEntityDao myBinaryStorageEntityDao; + @Autowired + private PlatformTransactionManager myPlatformTransactionManager; + + @Override + @Transactional(Transactional.TxType.SUPPORTS) + public StoredDetails storeBlob(IIdType theResourceId, String theContentType, InputStream theInputStream) throws IOException { + Date publishedDate = new Date(); + + HashingInputStream hashingInputStream = createHashingInputStream(theInputStream); + CountingInputStream countingInputStream = createCountingInputStream(hashingInputStream); + + String id = newRandomId(); + + BinaryStorageEntity entity = new BinaryStorageEntity(); + entity.setResourceId(theResourceId.toUnqualifiedVersionless().getValue()); + entity.setBlobId(id); + entity.setBlobContentType(theContentType); + entity.setPublished(publishedDate); + + Session session = (Session) myEntityManager.getDelegate(); + LobHelper lobHelper = session.getLobHelper(); + Blob dataBlob = lobHelper.createBlob(countingInputStream, 0); + entity.setBlob(dataBlob); + + // Save the entity + + TransactionTemplate txTemplate = new TransactionTemplate(myPlatformTransactionManager); + txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); + txTemplate.execute(t->{ + myEntityManager.persist(entity); + return null; + }); + + // Update the entity with the final byte count and hash + long bytes = countingInputStream.getCount(); + String hash = hashingInputStream.hash().toString(); + txTemplate.execute(t-> { + myBinaryStorageEntityDao.setSize(id, (int) bytes); + myBinaryStorageEntityDao.setHash(id, hash); + return null; + }); + + return new StoredDetails() + .setBlobId(id) + .setBytes(bytes) + .setPublished(publishedDate) + .setHash(hash) + .setContentType(theContentType); + } + + @Override + public StoredDetails fetchBlobDetails(IIdType theResourceId, String theBlobId) throws IOException { + + Optional entityOpt = myBinaryStorageEntityDao.findByIdAndResourceId(theBlobId, theResourceId.toUnqualifiedVersionless().getValue()); + if (entityOpt.isPresent() == false) { + return null; + } + + BinaryStorageEntity entity = entityOpt.get(); + return new StoredDetails() + .setBlobId(theBlobId) + .setContentType(entity.getBlobContentType()) + .setHash(entity.getHash()) + .setPublished(entity.getPublished()) + .setBytes(entity.getSize()); + } + + @Override + public boolean writeBlob(IIdType theResourceId, String theBlobId, OutputStream theOutputStream) throws IOException { + Optional entityOpt = myBinaryStorageEntityDao.findByIdAndResourceId(theBlobId, theResourceId.toUnqualifiedVersionless().getValue()); + if (entityOpt.isPresent() == false) { + return false; + } + + try { + InputStream inputStream = entityOpt.get().getBlob().getBinaryStream(); + IOUtils.copy(inputStream, theOutputStream); + } catch (SQLException e) { + throw new IOException(e); + } + + return true; + } + + @Override + public void expungeBlob(IIdType theResourceId, String theBlobId) { + Optional entityOpt = myBinaryStorageEntityDao.findByIdAndResourceId(theBlobId, theResourceId.toUnqualifiedVersionless().getValue()); + entityOpt.ifPresent(theBinaryStorageEntity -> myBinaryStorageEntityDao.delete(theBinaryStorageEntity)); + } +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/binstore/FilesystemBinaryStorageSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/binstore/FilesystemBinaryStorageSvcImpl.java index addd99ab493..d59380fae9e 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/binstore/FilesystemBinaryStorageSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/binstore/FilesystemBinaryStorageSvcImpl.java @@ -70,7 +70,7 @@ public class FilesystemBinaryStorageSvcImpl extends BaseBinaryStorageSvcImpl { // Write binary file File storageFilename = getStorageFilename(storagePath, theResourceId, id); ourLog.info("Writing to file: {}", storageFilename.getAbsolutePath()); - CountingInputStream countingInputStream = new CountingInputStream(theInputStream); + CountingInputStream countingInputStream = createCountingInputStream(theInputStream); HashingInputStream hashingInputStream = createHashingInputStream(countingInputStream); try (FileOutputStream outputStream = new FileOutputStream(storageFilename)) { IOUtils.copy(hashingInputStream, outputStream); @@ -110,7 +110,7 @@ public class FilesystemBinaryStorageSvcImpl extends BaseBinaryStorageSvcImpl { } @Override - public void writeBlob(IIdType theResourceId, String theBlobId, OutputStream theOutputStream) throws IOException { + public boolean writeBlob(IIdType theResourceId, String theBlobId, OutputStream theOutputStream) throws IOException { File storagePath = getStoragePath(theBlobId, false); if (storagePath != null) { File file = getStorageFilename(storagePath, theResourceId, theBlobId); @@ -121,6 +121,26 @@ public class FilesystemBinaryStorageSvcImpl extends BaseBinaryStorageSvcImpl { } } } + return false; + } + + @Override + public void expungeBlob(IIdType theResourceId, String theBlobId) { + File storagePath = getStoragePath(theBlobId, false); + if (storagePath != null) { + File storageFile = getStorageFilename(storagePath, theResourceId, theBlobId); + if (storageFile.exists()) { + delete(storageFile, theBlobId); + } + File descriptorFile = getDescriptorFilename(storagePath, theResourceId, theBlobId); + if (descriptorFile.exists()) { + delete(descriptorFile, theBlobId); + } + } + } + + private void delete(File theStorageFile, String theBlobId) { + Validate.isTrue(theStorageFile.delete(), "Failed to delete file for blob %s", theBlobId); } @Nonnull diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/binstore/IBinaryStorageSvc.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/binstore/IBinaryStorageSvc.java index d37ab6b42bb..f57b4df2755 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/binstore/IBinaryStorageSvc.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/binstore/IBinaryStorageSvc.java @@ -39,6 +39,34 @@ import java.util.Date; public interface IBinaryStorageSvc { + /** + * Gets the maximum number of bytes that can be stored in a single binary + * file by this service. The default is {@link Integer#MAX_VALUE} + */ + int getMaximumBinarySize(); + + /** + * Sets the maximum number of bytes that can be stored in a single binary + * file by this service. The default is {@link Integer#MAX_VALUE} + * + * @param theMaximumBinarySize The maximum size + */ + void setMaximumBinarySize(int theMaximumBinarySize); + + /** + * Gets the minimum number of bytes that will be stored. Binary content smaller + * * than this threshold may be inlined even if a binary storage service + * * is active. + */ + int getMinimumBinarySize(); + + /** + * Sets the minimum number of bytes that will be stored. Binary content smaller + * than this threshold may be inlined even if a binary storage service + * is active. + */ + void setMinimumBinarySize(int theMinimumBinarySize); + /** * Give the storage service the ability to veto items from storage * @@ -61,7 +89,12 @@ public interface IBinaryStorageSvc { StoredDetails fetchBlobDetails(IIdType theResourceId, String theBlobId) throws IOException; - void writeBlob(IIdType theResourceId, String theBlobId, OutputStream theOutputStream) throws IOException; + /** + * @return Returns true if the blob was found and written, of false if the blob was not found (i.e. it was expunged or the ID was invalid) + */ + boolean writeBlob(IIdType theResourceId, String theBlobId, OutputStream theOutputStream) throws IOException; + + void expungeBlob(IIdType theResourceId, String theBlobId); @JsonInclude(JsonInclude.Include.NON_NULL) @@ -115,23 +148,48 @@ public interface IBinaryStorageSvc { return myHash; } + public StoredDetails setHash(String theHash) { + myHash = theHash; + return this; + } + public Date getPublished() { return myPublished; } + public StoredDetails setPublished(Date thePublished) { + myPublished = thePublished; + return this; + } + @Nonnull public String getContentType() { return myContentType; } + public StoredDetails setContentType(String theContentType) { + myContentType = theContentType; + return this; + } + @Nonnull public String getBlobId() { return myBlobId; } + public StoredDetails setBlobId(String theBlobId) { + myBlobId = theBlobId; + return this; + } + public long getBytes() { return myBytes; } + public StoredDetails setBytes(long theBytes) { + myBytes = theBytes; + return this; + } + } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/binstore/MemoryBinaryStorageSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/binstore/MemoryBinaryStorageSvcImpl.java index 2774c36063e..389e23c590c 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/binstore/MemoryBinaryStorageSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/binstore/MemoryBinaryStorageSvcImpl.java @@ -69,10 +69,20 @@ public class MemoryBinaryStorageSvcImpl extends BaseBinaryStorageSvcImpl impleme } @Override - public void writeBlob(IIdType theResourceId, String theBlobId, OutputStream theOutputStream) throws IOException { + public boolean writeBlob(IIdType theResourceId, String theBlobId, OutputStream theOutputStream) throws IOException { String key = toKey(theResourceId, theBlobId); byte[] bytes = myDataMap.get(key); + if (bytes == null) { + return false; + } theOutputStream.write(bytes); + return true; + } + + @Override + public void expungeBlob(IIdType theResourceId, String theBlobId) { + String key = toKey(theResourceId, theBlobId); + myDataMap.remove(key); } private String toKey(IIdType theResourceId, String theBlobId) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/binstore/NullBinaryStorageSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/binstore/NullBinaryStorageSvcImpl.java index e3de05c4aae..6db74a254a6 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/binstore/NullBinaryStorageSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/binstore/NullBinaryStorageSvcImpl.java @@ -22,13 +22,31 @@ package ca.uhn.fhir.jpa.binstore; import org.hl7.fhir.instance.model.api.IIdType; -import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.io.UnsupportedEncodingException; public class NullBinaryStorageSvcImpl implements IBinaryStorageSvc { + @Override + public int getMaximumBinarySize() { + return 0; + } + + @Override + public void setMaximumBinarySize(int theMaximumBinarySize) { + // ignore + } + + @Override + public int getMinimumBinarySize() { + return 0; + } + + @Override + public void setMinimumBinarySize(int theMinimumBinarySize) { + // ignore + } + @Override public boolean shouldStoreBlob(long theSize, IIdType theResourceId, String theContentType) { return false; @@ -45,7 +63,12 @@ public class NullBinaryStorageSvcImpl implements IBinaryStorageSvc { } @Override - public void writeBlob(IIdType theResourceId, String theBlobId, OutputStream theOutputStream) { + public boolean writeBlob(IIdType theResourceId, String theBlobId, OutputStream theOutputStream) { + throw new UnsupportedOperationException(); + } + + @Override + public void expungeBlob(IIdType theIdElement, String theBlobId) { throw new UnsupportedOperationException(); } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java index ac2c94ddb8c..9cc03d63d5e 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java @@ -4,9 +4,10 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.i18n.HapiLocalizer; import ca.uhn.fhir.interceptor.api.IInterceptorService; import ca.uhn.fhir.interceptor.executor.InterceptorService; +import ca.uhn.fhir.jpa.binstore.BinaryAccessProvider; +import ca.uhn.fhir.jpa.binstore.BinaryStorageInterceptor; import ca.uhn.fhir.jpa.dao.DaoRegistry; import ca.uhn.fhir.jpa.interceptor.JpaConsentContextServices; -import ca.uhn.fhir.jpa.provider.BinaryAccessProvider; import ca.uhn.fhir.jpa.provider.SubscriptionTriggeringProvider; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; import ca.uhn.fhir.jpa.search.IStaleSearchDeletingSvc; @@ -123,10 +124,16 @@ public abstract class BaseConfig implements SchedulingConfigurer { @Bean(name = "myAttachmentBinaryAccessProvider") @Lazy - public BinaryAccessProvider AttachmentBinaryAccessProvider() { + public BinaryAccessProvider binaryAccessProvider() { return new BinaryAccessProvider(); } + @Bean(name = "myBinaryStorageInterceptor") + @Lazy + public BinaryStorageInterceptor binaryStorageInterceptor() { + return new BinaryStorageInterceptor(); + } + @Bean public TaskScheduler taskScheduler() { ConcurrentTaskScheduler retVal = new ConcurrentTaskScheduler(); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IBinaryStorageEntityDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IBinaryStorageEntityDao.java new file mode 100644 index 00000000000..917c9c3040b --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IBinaryStorageEntityDao.java @@ -0,0 +1,23 @@ +package ca.uhn.fhir.jpa.dao.data; + +import ca.uhn.fhir.jpa.model.entity.BinaryStorageEntity; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.Optional; + +public interface IBinaryStorageEntityDao extends JpaRepository { + + @Modifying + @Query("UPDATE BinaryStorageEntity e SET e.mySize = :blob_size WHERE e.myBlobId = :blob_id") + void setSize(@Param("blob_id") String theId, @Param("blob_size") int theBytes); + + @Modifying + @Query("UPDATE BinaryStorageEntity e SET e.myHash = :blob_hash WHERE e.myBlobId = :blob_id") + void setHash(@Param("blob_id") String theId, @Param("blob_hash") String theHash); + + @Query("SELECT e FROM BinaryStorageEntity e WHERE e.myBlobId = :blob_id AND e.myResourceId = :resource_id") + Optional findByIdAndResourceId(@Param("blob_id") String theBlobId, @Param("resource_id") String theResourceId); +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/expunge/ExpungeRun.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/expunge/ExpungeRun.java index 018645b9d09..0f5485b864d 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/expunge/ExpungeRun.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/expunge/ExpungeRun.java @@ -112,15 +112,15 @@ public class ExpungeRun implements Callable { private void expungeOldVersions() { Slice historicalIds = findHistoricalVersionsOfNonDeletedResources(); - myPartitionRunner.runInPartitionedThreads(historicalIds, partition -> myExpungeDaoService.expungeHistoricalVersions(partition, myRemainingCount)); + myPartitionRunner.runInPartitionedThreads(historicalIds, partition -> myExpungeDaoService.expungeHistoricalVersions(myRequestDetails, partition, myRemainingCount)); } private void deleteCurrentVersionsOfDeletedResources(Slice theResourceIds) { - myPartitionRunner.runInPartitionedThreads(theResourceIds, partition -> myExpungeDaoService.expungeCurrentVersionOfResources(partition, myRemainingCount)); + myPartitionRunner.runInPartitionedThreads(theResourceIds, partition -> myExpungeDaoService.expungeCurrentVersionOfResources(myRequestDetails, partition, myRemainingCount)); } private void deleteHistoricalVersions(Slice theResourceIds) { - myPartitionRunner.runInPartitionedThreads(theResourceIds, partition -> myExpungeDaoService.expungeHistoricalVersionsOfIds(partition, myRemainingCount)); + myPartitionRunner.runInPartitionedThreads(theResourceIds, partition -> myExpungeDaoService.expungeHistoricalVersionsOfIds(myRequestDetails, partition, myRemainingCount)); } private void deleteSearchResultCacheEntries(Slice theResourceIds) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/expunge/IResourceExpungeService.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/expunge/IResourceExpungeService.java index cd1f064d3d6..a8f1a52aafc 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/expunge/IResourceExpungeService.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/expunge/IResourceExpungeService.java @@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.dao.expunge; * #L% */ +import ca.uhn.fhir.rest.api.server.RequestDetails; import org.springframework.data.domain.Slice; import java.util.List; @@ -30,11 +31,11 @@ public interface IResourceExpungeService { Slice findHistoricalVersionsOfNonDeletedResources(String theResourceName, Long theResourceId, Long theVersion, int theI); - void expungeHistoricalVersions(List thePartition, AtomicInteger theRemainingCount); + void expungeHistoricalVersions(RequestDetails theRequestDetails, List thePartition, AtomicInteger theRemainingCount); - void expungeCurrentVersionOfResources(List thePartition, AtomicInteger theRemainingCount); + void expungeCurrentVersionOfResources(RequestDetails theRequestDetails, List thePartition, AtomicInteger theRemainingCount); - void expungeHistoricalVersionsOfIds(List thePartition, AtomicInteger theRemainingCount); + void expungeHistoricalVersionsOfIds(RequestDetails theRequestDetails, List thePartition, AtomicInteger theRemainingCount); void deleteByResourceIdPartitions(List thePartition); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/expunge/ResourceExpungeService.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/expunge/ResourceExpungeService.java index 787073330c7..4cf51db40ca 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/expunge/ResourceExpungeService.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/expunge/ResourceExpungeService.java @@ -20,12 +20,23 @@ package ca.uhn.fhir.jpa.dao.expunge; * #L% */ +import ca.uhn.fhir.interceptor.api.HookParams; +import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; +import ca.uhn.fhir.interceptor.api.Pointcut; +import ca.uhn.fhir.jpa.dao.DaoRegistry; +import ca.uhn.fhir.jpa.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.dao.data.*; import ca.uhn.fhir.jpa.dao.index.IdHelperService; import ca.uhn.fhir.jpa.model.entity.ForcedId; import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable; import ca.uhn.fhir.jpa.model.entity.ResourceTable; +import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster; +import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import org.apache.commons.lang3.Validate; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IIdType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -72,6 +83,10 @@ class ResourceExpungeService implements IResourceExpungeService { private IdHelperService myIdHelperService; @Autowired private IResourceHistoryTagDao myResourceHistoryTagDao; + @Autowired + private IInterceptorBroadcaster myInterceptorBroadcaster; + @Autowired + private DaoRegistry myDaoRegistry; @Override @Transactional @@ -115,18 +130,31 @@ class ResourceExpungeService implements IResourceExpungeService { @Override @Transactional - public void expungeCurrentVersionOfResources(List theResourceIds, AtomicInteger theRemainingCount) { + public void expungeCurrentVersionOfResources(RequestDetails theRequestDetails, List theResourceIds, AtomicInteger theRemainingCount) { for (Long next : theResourceIds) { - expungeCurrentVersionOfResource(next, theRemainingCount); + expungeCurrentVersionOfResource(theRequestDetails, next, theRemainingCount); if (theRemainingCount.get() <= 0) { return; } } } - private void expungeHistoricalVersion(Long theNextVersionId) { + private void expungeHistoricalVersion(RequestDetails theRequestDetails, Long theNextVersionId) { ResourceHistoryTable version = myResourceHistoryTableDao.findById(theNextVersionId).orElseThrow(IllegalArgumentException::new); - ourLog.info("Deleting resource version {}", version.getIdDt().getValue()); + IdDt id = version.getIdDt(); + ourLog.info("Deleting resource version {}", id.getValue()); + + // Interceptor call: STORAGE_PRESTORAGE_EXPUNGE_RESOURCE + if (JpaInterceptorBroadcaster.hasHooks(Pointcut.STORAGE_PRESTORAGE_EXPUNGE_RESOURCE, myInterceptorBroadcaster, theRequestDetails)) { + IFhirResourceDao resourceDao = myDaoRegistry.getResourceDao(id.getResourceType()); + IBaseResource resource = resourceDao.toResource(version, false); + HookParams params = new HookParams() + .add(IIdType.class, id) + .add(IBaseResource.class, resource) + .add(RequestDetails.class, theRequestDetails) + .addIfMatchesType(ServletRequestDetails.class, theRequestDetails); + JpaInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, theRequestDetails, Pointcut.STORAGE_PRESTORAGE_EXPUNGE_RESOURCE, params); + } myResourceHistoryTagDao.deleteAll(version.getTags()); myResourceHistoryTableDao.delete(version); @@ -134,9 +162,9 @@ class ResourceExpungeService implements IResourceExpungeService { @Override @Transactional - public void expungeHistoricalVersionsOfIds(List theResourceIds, AtomicInteger theRemainingCount) { + public void expungeHistoricalVersionsOfIds(RequestDetails theRequestDetails, List theResourceIds, AtomicInteger theRemainingCount) { for (Long next : theResourceIds) { - expungeHistoricalVersionsOfId(next, theRemainingCount); + expungeHistoricalVersionsOfId(theRequestDetails, next, theRemainingCount); if (theRemainingCount.get() <= 0) { return; } @@ -145,21 +173,21 @@ class ResourceExpungeService implements IResourceExpungeService { @Override @Transactional - public void expungeHistoricalVersions(List theHistoricalIds, AtomicInteger theRemainingCount) { + public void expungeHistoricalVersions(RequestDetails theRequestDetails, List theHistoricalIds, AtomicInteger theRemainingCount) { for (Long next : theHistoricalIds) { - expungeHistoricalVersion(next); + expungeHistoricalVersion(theRequestDetails, next); if (theRemainingCount.decrementAndGet() <= 0) { return; } } } - private void expungeCurrentVersionOfResource(Long myResourceId, AtomicInteger theRemainingCount) { + private void expungeCurrentVersionOfResource(RequestDetails theRequestDetails, Long myResourceId, AtomicInteger theRemainingCount) { ResourceTable resource = myResourceTableDao.findById(myResourceId).orElseThrow(IllegalStateException::new); ResourceHistoryTable currentVersion = myResourceHistoryTableDao.findForIdAndVersion(resource.getId(), resource.getVersion()); if (currentVersion != null) { - expungeHistoricalVersion(currentVersion.getId()); + expungeHistoricalVersion(theRequestDetails, currentVersion.getId()); } ourLog.info("Expunging current version of resource {}", resource.getIdDt().getValue()); @@ -194,7 +222,7 @@ class ResourceExpungeService implements IResourceExpungeService { myResourceTagDao.deleteByResourceId(theResourceId); } - private void expungeHistoricalVersionsOfId(Long myResourceId, AtomicInteger theRemainingCount) { + private void expungeHistoricalVersionsOfId(RequestDetails theRequestDetails, Long myResourceId, AtomicInteger theRemainingCount) { ResourceTable resource = myResourceTableDao.findById(myResourceId).orElseThrow(IllegalArgumentException::new); Pageable page = PageRequest.of(0, theRemainingCount.get()); @@ -202,7 +230,7 @@ class ResourceExpungeService implements IResourceExpungeService { Slice versionIds = myResourceHistoryTableDao.findForResourceId(page, resource.getId(), resource.getVersion()); ourLog.debug("Found {} versions of resource {} to expunge", versionIds.getNumberOfElements(), resource.getIdDt().getValue()); for (Long nextVersionId : versionIds) { - expungeHistoricalVersion(nextVersionId); + expungeHistoricalVersion(theRequestDetails, nextVersionId); if (theRemainingCount.decrementAndGet() <= 0) { return; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BinaryAccessProvider.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BinaryAccessProvider.java index 5be1610df99..a64e6b877e5 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BinaryAccessProvider.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BinaryAccessProvider.java @@ -20,238 +20,12 @@ package ca.uhn.fhir.jpa.provider; * #L% */ -import ca.uhn.fhir.context.BaseRuntimeElementDefinition; -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.binstore.IBinaryStorageSvc; -import ca.uhn.fhir.jpa.dao.DaoMethodOutcome; -import ca.uhn.fhir.jpa.dao.DaoRegistry; -import ca.uhn.fhir.jpa.dao.IFhirResourceDao; -import ca.uhn.fhir.jpa.model.util.JpaConstants; -import ca.uhn.fhir.rest.annotation.IdParam; -import ca.uhn.fhir.rest.annotation.Operation; -import ca.uhn.fhir.rest.annotation.OperationParam; -import ca.uhn.fhir.rest.api.Constants; -import ca.uhn.fhir.rest.api.EncodingEnum; -import ca.uhn.fhir.rest.server.RestfulServer; -import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; -import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; -import ca.uhn.fhir.util.AttachmentUtil; -import ca.uhn.fhir.util.DateUtils; -import org.apache.commons.io.IOUtils; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.Validate; -import org.hl7.fhir.instance.model.api.*; -import org.springframework.beans.factory.annotation.Autowired; - -import javax.annotation.Nonnull; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.util.Optional; - -import static ca.uhn.fhir.util.UrlUtil.sanitizeUrlPart; -import static org.apache.commons.lang3.StringUtils.isBlank; - /** - * This plain provider class can be registered with a JPA RestfulServer - * to provide the $binary-access-read and $binary-access-write - * operations that can be used to access attachment data as a raw binary. + * @deprecated Use ca.uhn.fhir.jpa.binstore.BinaryAccessProvider instead */ -public class BinaryAccessProvider { - - @Autowired - private FhirContext myCtx; - @Autowired - private DaoRegistry myDaoRegistry; - @Autowired(required = false) - private IBinaryStorageSvc myBinaryStorageSvc; - - /** - * $binary-access-read - */ - @Operation(name = JpaConstants.OPERATION_BINARY_ACCESS_READ, global = true, manualResponse = true, idempotent = true) - public void binaryAccessRead( - @IdParam IIdType theResourceId, - @OperationParam(name = "path", min = 1, max = 1) IPrimitiveType thePath, - ServletRequestDetails theRequestDetails, - HttpServletRequest theServletRequest, - HttpServletResponse theServletResponse) throws IOException { - - validateResourceTypeAndPath(theResourceId, thePath); - IFhirResourceDao dao = getDaoForRequest(theResourceId); - IBaseResource resource = dao.read(theResourceId, theRequestDetails, false); - - ICompositeType attachment = findAttachmentForRequest(resource, thePath, theRequestDetails); - - IBaseHasExtensions attachmentHasExt = (IBaseHasExtensions) attachment; - Optional> attachmentId = attachmentHasExt - .getExtension() - .stream() - .filter(t -> JpaConstants.EXT_ATTACHMENT_EXTERNAL_BINARY_ID.equals(t.getUrl())) - .findFirst(); - - if (attachmentId.isPresent()) { - - @SuppressWarnings("unchecked") - IPrimitiveType value = (IPrimitiveType) attachmentId.get().getValue(); - String blobId = value.getValueAsString(); - - IBinaryStorageSvc.StoredDetails blobDetails = myBinaryStorageSvc.fetchBlobDetails(theResourceId, blobId); - if (blobDetails == null) { - String msg = myCtx.getLocalizer().getMessage(BinaryAccessProvider.class, "unknownBlobId"); - throw new InvalidRequestException(msg); - } - - theServletResponse.setStatus(200); - theServletResponse.setContentType(blobDetails.getContentType()); - if (blobDetails.getBytes() <= Integer.MAX_VALUE) { - theServletResponse.setContentLength((int) blobDetails.getBytes()); - } - - RestfulServer server = theRequestDetails.getServer(); - server.addHeadersToResponse(theServletResponse); - - theServletResponse.addHeader(Constants.HEADER_CACHE_CONTROL, Constants.CACHE_CONTROL_PRIVATE); - theServletResponse.addHeader(Constants.HEADER_ETAG, '"' + blobDetails.getHash() + '"'); - theServletResponse.addHeader(Constants.HEADER_LAST_MODIFIED, DateUtils.formatDate(blobDetails.getPublished())); - - myBinaryStorageSvc.writeBlob(theResourceId, blobId, theServletResponse.getOutputStream()); - theServletResponse.getOutputStream().close(); - - myBinaryStorageSvc.writeBlob(theResourceId, blobId, theServletResponse.getOutputStream()); - - } else { - - IPrimitiveType contentTypeDt = AttachmentUtil.getOrCreateContentType(theRequestDetails.getFhirContext(), attachment); - String contentType = contentTypeDt.getValueAsString(); - contentType = StringUtils.defaultIfBlank(contentType, Constants.CT_OCTET_STREAM); - - IPrimitiveType dataDt = AttachmentUtil.getOrCreateData(theRequestDetails.getFhirContext(), attachment); - byte[] data = dataDt.getValue(); - if (data == null) { - String msg = myCtx.getLocalizer().getMessage(BinaryAccessProvider.class, "noAttachmentDataPresent", sanitizeUrlPart(theResourceId), sanitizeUrlPart(thePath)); - throw new InvalidRequestException(msg); - } - - theServletResponse.setStatus(200); - theServletResponse.setContentType(contentType); - theServletResponse.setContentLength(data.length); - - RestfulServer server = theRequestDetails.getServer(); - server.addHeadersToResponse(theServletResponse); - - theServletResponse.getOutputStream().write(data); - theServletResponse.getOutputStream().close(); - - } - } - - /** - * $binary-access-write - */ - @SuppressWarnings("unchecked") - @Operation(name = JpaConstants.OPERATION_BINARY_ACCESS_WRITE, global = true, manualRequest = true, idempotent = false) - public IBaseResource binaryAccessWrite( - @IdParam IIdType theResourceId, - @OperationParam(name = "path", min = 1, max = 1) IPrimitiveType thePath, - ServletRequestDetails theRequestDetails, - HttpServletRequest theServletRequest, - HttpServletResponse theServletResponse) throws IOException { - - validateResourceTypeAndPath(theResourceId, thePath); - IFhirResourceDao dao = getDaoForRequest(theResourceId); - IBaseResource resource = dao.read(theResourceId, theRequestDetails, false); - - ICompositeType attachment = findAttachmentForRequest(resource, thePath, theRequestDetails); - - String requestContentType = theServletRequest.getContentType(); - if (isBlank(requestContentType)) { - throw new InvalidRequestException("No content-attachment supplied"); - } - if (EncodingEnum.forContentTypeStrict(requestContentType) != null) { - throw new InvalidRequestException("This operation is for binary content, got: " + requestContentType); - } - - long size = theServletRequest.getContentLength(); - String blobId = null; - - if (size > 0) { - if (myBinaryStorageSvc != null) { - if (myBinaryStorageSvc.shouldStoreBlob(size, theResourceId, requestContentType)) { - IBinaryStorageSvc.StoredDetails storedDetails = myBinaryStorageSvc.storeBlob(theResourceId, requestContentType, theRequestDetails.getInputStream()); - size = storedDetails.getBytes(); - blobId = storedDetails.getBlobId(); - Validate.notBlank(blobId, "BinaryStorageSvc returned a null blob ID"); // should not happen - } - } - } - - if (blobId == null) { - byte[] bytes = IOUtils.toByteArray(theRequestDetails.getInputStream()); - size = bytes.length; - AttachmentUtil.setData(theRequestDetails.getFhirContext(), attachment, bytes); - } else { - - IBaseHasExtensions attachmentHasExt = (IBaseHasExtensions) attachment; - attachmentHasExt.getExtension().removeIf(t -> JpaConstants.EXT_ATTACHMENT_EXTERNAL_BINARY_ID.equals(t.getUrl())); - AttachmentUtil.setData(myCtx, attachment, null); - - IBaseExtension ext = attachmentHasExt.addExtension(); - ext.setUrl(JpaConstants.EXT_ATTACHMENT_EXTERNAL_BINARY_ID); - IPrimitiveType blobIdString = (IPrimitiveType) myCtx.getElementDefinition("string").newInstance(); - blobIdString.setValueAsString(blobId); - ext.setValue(blobIdString); - } - - AttachmentUtil.setContentType(theRequestDetails.getFhirContext(), attachment, requestContentType); - - AttachmentUtil.setSize(theRequestDetails.getFhirContext(), attachment, null); - if (size <= Integer.MAX_VALUE) { - AttachmentUtil.setSize(theRequestDetails.getFhirContext(), attachment, (int) size); - } - - DaoMethodOutcome outcome = dao.update(resource, theRequestDetails); - return outcome.getResource(); - } - - @Nonnull - private ICompositeType findAttachmentForRequest(IBaseResource theResource, @OperationParam(name = "path", min = 1, max = 1) IPrimitiveType thePath, ServletRequestDetails theRequestDetails) { - FhirContext ctx = theRequestDetails.getFhirContext(); - String path = thePath.getValueAsString(); - - Optional type = ctx.newFluentPath().evaluateFirst(theResource, path, ICompositeType.class); - if (!type.isPresent()) { - throw new InvalidRequestException("Unable to find Attachment at path: " + sanitizeUrlPart(path)); - } - - BaseRuntimeElementDefinition def = ctx.getElementDefinition(type.get().getClass()); - if (!def.getName().equals("Attachment")) { - throw new InvalidRequestException("Path does not return an Attachment: " + sanitizeUrlPart(path)); - } - return type.get(); - } - - private void validateResourceTypeAndPath(@IdParam IIdType theResourceId, @OperationParam(name = "path", min = 1, max = 1) IPrimitiveType thePath) { - if (isBlank(theResourceId.getResourceType())) { - throw new InvalidRequestException("No resource type specified"); - } - if (isBlank(theResourceId.getIdPart())) { - throw new InvalidRequestException("No ID specified"); - } - if (thePath == null || isBlank(thePath.getValue())) { - throw new InvalidRequestException("No path specified"); - } - } - - @Nonnull - private IFhirResourceDao getDaoForRequest(@IdParam IIdType theResourceId) { - String resourceType = theResourceId.getResourceType(); - IFhirResourceDao dao = myDaoRegistry.getResourceDao(resourceType); - if (dao == null) { - throw new InvalidRequestException("Unknown/unsupported resource type: " + sanitizeUrlPart(resourceType)); - } - return dao; - } +@Deprecated +public class BinaryAccessProvider extends ca.uhn.fhir.jpa.binstore.BinaryAccessProvider { + // FIXME: JA delete before 4.0.0 } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/CircularQueueCaptureQueriesListener.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/CircularQueueCaptureQueriesListener.java index f300ea9e303..b7787adcd4d 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/CircularQueueCaptureQueriesListener.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/CircularQueueCaptureQueriesListener.java @@ -130,6 +130,13 @@ public class CircularQueueCaptureQueriesListener extends BaseCaptureQueriesListe return getQueriesForCurrentThreadStartingWith("insert"); } + /** + * Returns all INSERT queries executed on the current thread - Index 0 is oldest + */ + public List getAllQueriesForCurrentThread() { + return getQueriesForCurrentThreadStartingWith(""); + } + /** * Returns all UPDATE queries executed on the current thread - Index 0 is oldest */ @@ -209,6 +216,17 @@ public class CircularQueueCaptureQueriesListener extends BaseCaptureQueriesListe ourLog.info("Insert Queries:\n{}", String.join("\n", queries)); } + /** + * Log all captured INSERT queries + */ + public void logAllQueriesForCurrentThread() { + List queries = getAllQueriesForCurrentThread() + .stream() + .map(CircularQueueCaptureQueriesListener::formatQueryAsSql) + .collect(Collectors.toList()); + ourLog.info("Insert Queries:\n{}", String.join("\n", queries)); + } + /** * Log all captured INSERT queries */ diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/binstore/DatabaseBlobBinaryStorageSvcImplTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/binstore/DatabaseBlobBinaryStorageSvcImplTest.java new file mode 100644 index 00000000000..fb72573f1c5 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/binstore/DatabaseBlobBinaryStorageSvcImplTest.java @@ -0,0 +1,133 @@ +package ca.uhn.fhir.jpa.binstore; + +import ca.uhn.fhir.jpa.config.TestR4Config; +import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test; +import org.hl7.fhir.r4.model.IdType; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import static org.hamcrest.Matchers.matchesPattern; +import static org.junit.Assert.*; +import static org.junit.Assert.assertArrayEquals; + +@ContextConfiguration(classes = DatabaseBlobBinaryStorageSvcImplTest.MyConfig.class) +public class DatabaseBlobBinaryStorageSvcImplTest extends BaseJpaR4Test { + private static final byte[] SOME_BYTES = {2, 3, 4, 5, 6, 7, 8, 9, 8, 7, 6, 5, 4, 3, 2, 1}; + + @Autowired + @Qualifier("databaseBlobBinaryStorageSvc") + private IBinaryStorageSvc mySvc; + + @Test + public void testStoreAndRetrieve() throws IOException { + + myCaptureQueriesListener.clear(); + + /* + * Store the binary + */ + ByteArrayInputStream inputStream = new ByteArrayInputStream(SOME_BYTES); + String contentType = "image/png"; + IdType resourceId = new IdType("Binary/123"); + IBinaryStorageSvc.StoredDetails outcome = mySvc.storeBlob(resourceId, contentType, inputStream); + + myCaptureQueriesListener.logAllQueriesForCurrentThread(); + + assertEquals(0, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size()); + assertEquals(1, myCaptureQueriesListener.getInsertQueriesForCurrentThread().size()); + assertEquals(2, myCaptureQueriesListener.getUpdateQueriesForCurrentThread().size()); + + myCaptureQueriesListener.clear(); + + assertThat(outcome.getBlobId(), matchesPattern("^[a-zA-Z0-9]{100}$")); + assertEquals(16, outcome.getBytes()); + + /* + * Read back the details + */ + + IBinaryStorageSvc.StoredDetails details = mySvc.fetchBlobDetails(resourceId, outcome.getBlobId()); + assertEquals(16L, details.getBytes()); + assertEquals(outcome.getBlobId(), details.getBlobId()); + assertEquals("image/png", details.getContentType()); + assertEquals("dc7197cfab936698bef7818975c185a9b88b71a0a0a2493deea487706ddf20cb", details.getHash()); + assertNotNull(details.getPublished()); + + /* + * Read back the contents + */ + + ByteArrayOutputStream capture = new ByteArrayOutputStream(); + mySvc.writeBlob(resourceId, outcome.getBlobId(), capture); + + assertArrayEquals(SOME_BYTES, capture.toByteArray()); + + + } + + @Test + public void testExpunge() throws IOException { + + /* + * Store the binary + */ + ByteArrayInputStream inputStream = new ByteArrayInputStream(SOME_BYTES); + String contentType = "image/png"; + IdType resourceId = new IdType("Binary/123"); + IBinaryStorageSvc.StoredDetails outcome = mySvc.storeBlob(resourceId, contentType, inputStream); + String blobId = outcome.getBlobId(); + + // Expunge + mySvc.expungeBlob(resourceId, blobId); + + ByteArrayOutputStream capture = new ByteArrayOutputStream(); + assertFalse(mySvc.writeBlob(resourceId, outcome.getBlobId(), capture)); + assertEquals(0, capture.size()); + + } + + + @Test + public void testWrongResourceId() throws IOException { + + /* + * Store the binary + */ + ByteArrayInputStream inputStream = new ByteArrayInputStream(SOME_BYTES); + String contentType = "image/png"; + IdType resourceId = new IdType("Binary/123"); + IBinaryStorageSvc.StoredDetails outcome = mySvc.storeBlob(resourceId, contentType, inputStream); + + // Right ID + ByteArrayOutputStream capture = new ByteArrayOutputStream(); + assertTrue(mySvc.writeBlob(resourceId, outcome.getBlobId(), capture)); + assertEquals(16, capture.size()); + + // Wrong ID + capture = new ByteArrayOutputStream(); + assertFalse(mySvc.writeBlob(new IdType("Patient/9999"), outcome.getBlobId(), capture)); + assertEquals(0, capture.size()); + + } + + @Configuration + public static class MyConfig { + + @Primary + @Bean + public IBinaryStorageSvc databaseBlobBinaryStorageSvc() { + return new DatabaseBlobBinaryStorageSvcImpl(); + } + + } +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/binstore/FilesystemBinaryStorageSvcImplTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/binstore/FilesystemBinaryStorageSvcImplTest.java index 05af2cdfe7b..c62efd6866a 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/binstore/FilesystemBinaryStorageSvcImplTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/binstore/FilesystemBinaryStorageSvcImplTest.java @@ -18,8 +18,8 @@ import static org.junit.Assert.*; public class FilesystemBinaryStorageSvcImplTest { - private static final Logger ourLog = LoggerFactory.getLogger(FilesystemBinaryStorageSvcImplTest.class); public static final byte[] SOME_BYTES = {2, 3, 4, 5, 6, 7, 8, 9, 8, 7, 6, 5, 4, 3, 2, 1}; + private static final Logger ourLog = LoggerFactory.getLogger(FilesystemBinaryStorageSvcImplTest.class); private File myPath; private FilesystemBinaryStorageSvcImpl mySvc; @@ -56,4 +56,26 @@ public class FilesystemBinaryStorageSvcImplTest { } + @Test + public void testExpunge() throws IOException { + IIdType id = new IdType("Patient/123"); + String contentType = "image/png"; + IBinaryStorageSvc.StoredDetails outcome = mySvc.storeBlob(id, contentType, new ByteArrayInputStream(SOME_BYTES)); + + ourLog.info("Got id: {}", outcome); + + IBinaryStorageSvc.StoredDetails details = mySvc.fetchBlobDetails(id, outcome.getBlobId()); + assertEquals(16L, details.getBytes()); + assertEquals(outcome.getBlobId(), details.getBlobId()); + assertEquals("image/png", details.getContentType()); + assertEquals("dc7197cfab936698bef7818975c185a9b88b71a0a0a2493deea487706ddf20cb", details.getHash()); + assertNotNull(details.getPublished()); + + mySvc.expungeBlob(id, outcome.getBlobId()); + + ByteArrayOutputStream capture = new ByteArrayOutputStream(); + mySvc.writeBlob(id, outcome.getBlobId(), capture); + assertEquals(0, capture.size()); + } + } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/binstore/NullBinaryStorageSvcImplTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/binstore/NullBinaryStorageSvcImplTest.java index b8ca92aef9e..049758cb5d8 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/binstore/NullBinaryStorageSvcImplTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/binstore/NullBinaryStorageSvcImplTest.java @@ -28,4 +28,10 @@ public class NullBinaryStorageSvcImplTest { public void writeBlob() { mySvc.writeBlob(null, null, null); } + + @Test(expected = UnsupportedOperationException.class) + public void expungeBlob() { + mySvc.expungeBlob(null, null); + } + } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java index 32adc439e02..677ee3dea7e 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java @@ -10,10 +10,12 @@ import net.ttddyy.dsproxy.listener.SingleQueryCountHolder; import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder; import org.apache.commons.dbcp2.BasicDataSource; import org.hibernate.dialect.H2Dialect; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Lazy; +import org.springframework.core.env.Environment; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import org.springframework.transaction.annotation.EnableTransactionManagement; @@ -42,6 +44,9 @@ public class TestR4Config extends BaseJavaConfigR4 { } } + @Autowired + private Environment myEnvironment; + private Exception myLastStackTrace; @Bean @@ -93,6 +98,7 @@ public class TestR4Config extends BaseJavaConfigR4 { } }; + retVal.setDriver(new org.h2.Driver()); retVal.setUrl("jdbc:h2:mem:testdb_r4"); retVal.setMaxWaitMillis(10000); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/BaseJpaR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/BaseJpaR4Test.java index 6ddb2605da8..25ecf91a662 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/BaseJpaR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/BaseJpaR4Test.java @@ -2,6 +2,7 @@ package ca.uhn.fhir.jpa.dao.r4; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.interceptor.api.IInterceptorService; +import ca.uhn.fhir.jpa.binstore.BinaryStorageInterceptor; import ca.uhn.fhir.jpa.config.TestR4Config; import ca.uhn.fhir.jpa.dao.*; import ca.uhn.fhir.jpa.dao.data.*; @@ -10,7 +11,7 @@ import ca.uhn.fhir.jpa.interceptor.PerformanceTracingLoggingInterceptor; import ca.uhn.fhir.jpa.model.entity.ModelConfig; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString; import ca.uhn.fhir.jpa.model.entity.ResourceTable; -import ca.uhn.fhir.jpa.provider.BinaryAccessProvider; +import ca.uhn.fhir.jpa.binstore.BinaryAccessProvider; import ca.uhn.fhir.jpa.provider.r4.JpaSystemProviderR4; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc; @@ -55,6 +56,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.ApplicationContext; import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.util.AopTestUtils; import org.springframework.transaction.PlatformTransactionManager; @@ -98,6 +100,8 @@ public abstract class BaseJpaR4Test extends BaseJpaTest { @Autowired protected BinaryAccessProvider myBinaryAccessProvider; @Autowired + protected BinaryStorageInterceptor myBinaryStorageInterceptor; + @Autowired protected ApplicationContext myAppCtx; @Autowired @Qualifier("myAppointmentDaoR4") diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchOptimizedTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchOptimizedTest.java index d82b8c63ce3..da6bffcc843 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchOptimizedTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchOptimizedTest.java @@ -1,5 +1,6 @@ package ca.uhn.fhir.jpa.dao.r4; +import ca.uhn.fhir.jpa.config.TestR4Config; import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.entity.Search; import ca.uhn.fhir.jpa.model.entity.ResourceTable; @@ -249,7 +250,9 @@ public class FhirResourceDaoR4SearchOptimizedTest extends BaseJpaR4Test { ids = toUnqualifiedVersionlessIdValues(results, 0, 10, true); assertEquals("Patient/PT00000", ids.get(0)); assertEquals("Patient/PT00009", ids.get(9)); - assertEquals(200, myDatabaseBackedPagingProvider.retrieveResultList(null, uuid2).size().intValue()); + IBundleProvider results2 = myDatabaseBackedPagingProvider.retrieveResultList(null, uuid2); + Integer results2Size = results2.size(); + assertEquals(200, results2Size.intValue()); assertNotEquals(uuid, uuid2); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchWithLuceneDisabledTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchWithLuceneDisabledTest.java index 7b52522df65..0fe82cc86db 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchWithLuceneDisabledTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchWithLuceneDisabledTest.java @@ -1,6 +1,7 @@ package ca.uhn.fhir.jpa.dao.r4; import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.config.TestR4Config; import ca.uhn.fhir.jpa.config.TestR4WithoutLuceneConfig; import ca.uhn.fhir.jpa.dao.*; import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc; @@ -25,6 +26,7 @@ import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.annotation.Transactional; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/BaseResourceProviderR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/BaseResourceProviderR4Test.java index fdf5e4d55a3..aef1e4ed5db 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/BaseResourceProviderR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/BaseResourceProviderR4Test.java @@ -100,6 +100,7 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test { ourRestServer = new RestfulServer(myFhirCtx); ourRestServer.registerProviders(myResourceProviders.createProviders()); ourRestServer.registerProvider(myBinaryAccessProvider); + ourRestServer.getInterceptorService().registerInterceptor(myBinaryStorageInterceptor); ourRestServer.getFhirContext().setNarrativeGenerator(new DefaultThymeleafNarrativeGenerator()); ourRestServer.setDefaultResponseEncoding(EncodingEnum.XML); 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 3f7e6809635..dbb63bd5b33 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 @@ -4,8 +4,10 @@ import ca.uhn.fhir.interceptor.api.HookParams; import ca.uhn.fhir.interceptor.api.IAnonymousInterceptor; import ca.uhn.fhir.interceptor.api.Pointcut; import ca.uhn.fhir.jpa.binstore.MemoryBinaryStorageSvcImpl; +import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.rest.api.Constants; +import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException; import com.google.common.base.Charsets; import org.apache.commons.io.IOUtils; import org.apache.http.client.methods.CloseableHttpResponse; @@ -14,9 +16,7 @@ import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.ByteArrayEntity; import org.apache.http.entity.ContentType; import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.r4.model.Attachment; -import org.hl7.fhir.r4.model.DocumentReference; -import org.hl7.fhir.r4.model.StringType; +import org.hl7.fhir.r4.model.*; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -24,6 +24,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import java.io.ByteArrayOutputStream; import java.io.IOException; import static org.hamcrest.Matchers.containsString; @@ -44,14 +45,17 @@ public class BinaryAccessProviderR4Test extends BaseResourceProviderR4Test { @Before public void before() throws Exception { super.before(); - myStorageSvc.setMinSize(10); + myStorageSvc.setMinimumBinarySize(10); + myDaoConfig.setExpungeEnabled(true); + myInterceptorRegistry.registerInterceptor(myBinaryStorageInterceptor); } @Override @After public void after() throws Exception { super.after(); - myStorageSvc.setMinSize(0); + myStorageSvc.setMinimumBinarySize(0); + myDaoConfig.setExpungeEnabled(new DaoConfig().isExpungeEnabled()); } @Test @@ -163,7 +167,8 @@ public class BinaryAccessProviderR4Test extends BaseResourceProviderR4Test { DocumentReference dr = ourClient.read().resource(DocumentReference.class).withId(id).execute(); dr.getContentFirstRep() .getAttachment() - .addExtension(JpaConstants.EXT_ATTACHMENT_EXTERNAL_BINARY_ID, new StringType("AAAAA")); + .getDataElement() + .addExtension(JpaConstants.EXT_EXTERNALIZED_BINARY_ID, new StringType("AAAAA")); ourClient.update().resource(dr).execute(); String path = ourServerBase + @@ -185,7 +190,7 @@ public class BinaryAccessProviderR4Test extends BaseResourceProviderR4Test { * Stores a binary large enough that it should live in binary storage */ @Test - public void testWriteLarge() throws IOException { + public void testWriteLargeAttachment() throws IOException { IIdType id = createDocumentReference(false); IAnonymousInterceptor interceptor = mock(IAnonymousInterceptor.class); @@ -217,7 +222,7 @@ public class BinaryAccessProviderR4Test extends BaseResourceProviderR4Test { assertEquals(15, attachment.getSize()); assertEquals(null, attachment.getData()); assertEquals("2", ref.getMeta().getVersionId()); - attachmentId = attachment.getExtensionString(JpaConstants.EXT_ATTACHMENT_EXTERNAL_BINARY_ID); + attachmentId = attachment.getDataElement().getExtensionString(JpaConstants.EXT_EXTERNALIZED_BINARY_ID); assertThat(attachmentId, matchesPattern("[a-zA-Z0-9]{100}")); } @@ -249,7 +254,7 @@ public class BinaryAccessProviderR4Test extends BaseResourceProviderR4Test { * Stores a binary small enough that it shouldn't live in binary storage */ @Test - public void testWriteSmall() throws IOException { + public void testWriteSmallAttachment() throws IOException { IIdType id = createDocumentReference(false); IAnonymousInterceptor interceptor = mock(IAnonymousInterceptor.class); @@ -281,7 +286,7 @@ public class BinaryAccessProviderR4Test extends BaseResourceProviderR4Test { assertEquals(4, attachment.getSize()); assertArrayEquals(SOME_BYTES_2, attachment.getData()); assertEquals("2", ref.getMeta().getVersionId()); - attachmentId = attachment.getExtensionString(JpaConstants.EXT_ATTACHMENT_EXTERNAL_BINARY_ID); + attachmentId = attachment.getExtensionString(JpaConstants.EXT_EXTERNALIZED_BINARY_ID); assertEquals(null, attachmentId); } @@ -292,6 +297,129 @@ public class BinaryAccessProviderR4Test extends BaseResourceProviderR4Test { } + /** + * Stores a binary large enough that it should live in binary storage + */ + @Test + public void testWriteLargeBinary() throws IOException { + Binary binary = new Binary(); + binary.setContentType("image/png"); + + ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(binary)); + + IIdType id = ourClient.create().resource(binary).execute().getId().toUnqualifiedVersionless(); + + IAnonymousInterceptor interceptor = mock(IAnonymousInterceptor.class); + myInterceptorRegistry.registerAnonymousInterceptor(Pointcut.STORAGE_PRESHOW_RESOURCES, interceptor); + myInterceptorRegistry.registerAnonymousInterceptor(Pointcut.STORAGE_PRESTORAGE_RESOURCE_CREATED, interceptor); + myInterceptorRegistry.registerAnonymousInterceptor(Pointcut.STORAGE_PRESTORAGE_RESOURCE_UPDATED, interceptor); + + // Write using the operation + + String path = ourServerBase + + "/Binary/" + id.getIdPart() + "/" + + JpaConstants.OPERATION_BINARY_ACCESS_WRITE + + "?path=Binary"; + HttpPost post = new HttpPost(path); + post.setEntity(new ByteArrayEntity(SOME_BYTES, ContentType.IMAGE_JPEG)); + post.addHeader("Accept", "application/fhir+json; _pretty=true"); + String attachmentId; + try (CloseableHttpResponse resp = ourHttpClient.execute(post)) { + + assertEquals(200, resp.getStatusLine().getStatusCode()); + assertThat(resp.getEntity().getContentType().getValue(), containsString("application/fhir+json")); + String response = IOUtils.toString(resp.getEntity().getContent(), Constants.CHARSET_UTF8); + ourLog.info("Response: {}", response); + + Binary target = myFhirCtx.newJsonParser().parseResource(Binary.class, response); + + assertEquals(ContentType.IMAGE_JPEG.getMimeType(), target.getContentType()); + assertEquals(null, target.getData()); + assertEquals("2", target.getMeta().getVersionId()); + attachmentId = target.getDataElement().getExtensionString(JpaConstants.EXT_EXTERNALIZED_BINARY_ID); + assertThat(attachmentId, matchesPattern("[a-zA-Z0-9]{100}")); + + } + + verify(interceptor, timeout(5000).times(1)).invoke(eq(Pointcut.STORAGE_PRESHOW_RESOURCES), any()); + verify(interceptor, timeout(5000).times(1)).invoke(eq(Pointcut.STORAGE_PRESTORAGE_RESOURCE_UPDATED), any()); + verifyNoMoreInteractions(interceptor); + + // Read it back using the operation + + path = ourServerBase + + "/Binary/" + id.getIdPart() + "/" + + JpaConstants.OPERATION_BINARY_ACCESS_READ + + "?path=Binary"; + HttpGet get = new HttpGet(path); + try (CloseableHttpResponse resp = ourHttpClient.execute(get)) { + + assertEquals(200, resp.getStatusLine().getStatusCode()); + assertEquals("image/jpeg", resp.getEntity().getContentType().getValue()); + assertEquals(SOME_BYTES.length, resp.getEntity().getContentLength()); + + byte[] actualBytes = IOUtils.toByteArray(resp.getEntity().getContent()); + assertArrayEquals(SOME_BYTES, actualBytes); + } + + } + + /** + * Stores a binary large enough that it should live in binary storage + */ + @Test + public void testWriteLargeBinaryWithoutExplicitPath() throws IOException { + Binary binary = new Binary(); + binary.setContentType("image/png"); + + ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(binary)); + + IIdType id = ourClient.create().resource(binary).execute().getId().toUnqualifiedVersionless(); + + // Write using the operation + + String path = ourServerBase + + "/Binary/" + id.getIdPart() + "/" + + JpaConstants.OPERATION_BINARY_ACCESS_WRITE; + HttpPost post = new HttpPost(path); + post.setEntity(new ByteArrayEntity(SOME_BYTES, ContentType.IMAGE_JPEG)); + post.addHeader("Accept", "application/fhir+json; _pretty=true"); + String attachmentId; + try (CloseableHttpResponse resp = ourHttpClient.execute(post)) { + + assertEquals(200, resp.getStatusLine().getStatusCode()); + assertThat(resp.getEntity().getContentType().getValue(), containsString("application/fhir+json")); + String response = IOUtils.toString(resp.getEntity().getContent(), Constants.CHARSET_UTF8); + ourLog.info("Response: {}", response); + + Binary target = myFhirCtx.newJsonParser().parseResource(Binary.class, response); + + assertEquals(ContentType.IMAGE_JPEG.getMimeType(), target.getContentType()); + assertEquals(null, target.getData()); + assertEquals("2", target.getMeta().getVersionId()); + attachmentId = target.getDataElement().getExtensionString(JpaConstants.EXT_EXTERNALIZED_BINARY_ID); + assertThat(attachmentId, matchesPattern("[a-zA-Z0-9]{100}")); + + } + + // Read it back using the operation + + path = ourServerBase + + "/Binary/" + id.getIdPart() + "/" + + JpaConstants.OPERATION_BINARY_ACCESS_READ; + HttpGet get = new HttpGet(path); + try (CloseableHttpResponse resp = ourHttpClient.execute(get)) { + + assertEquals(200, resp.getStatusLine().getStatusCode()); + assertEquals("image/jpeg", resp.getEntity().getContentType().getValue()); + assertEquals(SOME_BYTES.length, resp.getEntity().getContentLength()); + + byte[] actualBytes = IOUtils.toByteArray(resp.getEntity().getContent()); + assertArrayEquals(SOME_BYTES, actualBytes); + } + + } + private IIdType createDocumentReference(boolean theSetData) { DocumentReference documentReference = new DocumentReference(); Attachment attachment = documentReference @@ -315,6 +443,60 @@ public class BinaryAccessProviderR4Test extends BaseResourceProviderR4Test { } + @Test + public void testResourceExpungeAlsoExpungesBinaryData() throws IOException { + IIdType id = createDocumentReference(false); + + String path = ourServerBase + + "/DocumentReference/" + id.getIdPart() + "/" + + JpaConstants.OPERATION_BINARY_ACCESS_WRITE + + "?path=DocumentReference.content.attachment"; + HttpPost post = new HttpPost(path); + post.setEntity(new ByteArrayEntity(SOME_BYTES, ContentType.IMAGE_JPEG)); + post.addHeader("Accept", "application/fhir+json; _pretty=true"); + String attachmentId; + try (CloseableHttpResponse resp = ourHttpClient.execute(post)) { + assertEquals(200, resp.getStatusLine().getStatusCode()); + assertThat(resp.getEntity().getContentType().getValue(), containsString("application/fhir+json")); + String response = IOUtils.toString(resp.getEntity().getContent(), Constants.CHARSET_UTF8); + DocumentReference ref = myFhirCtx.newJsonParser().parseResource(DocumentReference.class, response); + Attachment attachment = ref.getContentFirstRep().getAttachment(); + attachmentId = attachment.getDataElement().getExtensionString(JpaConstants.EXT_EXTERNALIZED_BINARY_ID); + assertThat(attachmentId, matchesPattern("[a-zA-Z0-9]{100}")); + } + + ByteArrayOutputStream capture = new ByteArrayOutputStream(); + myStorageSvc.writeBlob(id, attachmentId, capture); + assertEquals(15, capture.size()); + + // Now delete (logical delete- should not expunge the binary) + ourClient.delete().resourceById(id).execute(); + try { + ourClient.read().resource("DocumentReference").withId(id).execute(); + fail(); + } catch (ResourceGoneException e) { + // good + } + + capture = new ByteArrayOutputStream(); + myStorageSvc.writeBlob(id, attachmentId, capture); + assertEquals(15, capture.size()); + + // Now expunge + Parameters parameters = new Parameters(); + parameters.addParameter().setName(JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_DELETED_RESOURCES).setValue(new BooleanType(true)); + ourClient + .operation() + .onInstance(id) + .named(JpaConstants.OPERATION_EXPUNGE) + .withParameters(parameters) + .execute(); + + capture = new ByteArrayOutputStream(); + assertFalse(myStorageSvc.writeBlob(id, attachmentId, capture)); + assertEquals(0, capture.size()); + + } } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ExpungeR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ExpungeR4Test.java index a41d8244953..6b98ee2a39b 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ExpungeR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ExpungeR4Test.java @@ -339,12 +339,18 @@ public class ExpungeR4Test extends BaseResourceProviderR4Test { public void testExpungeEverythingWhereResourceInSearchResults() { createStandardPatients(); - IBundleProvider search = myPatientDao.search(new SearchParameterMap()); + runInTransaction(() -> { + await().until(()->mySearchEntityDao.count() == 0); + await().until(()->mySearchResultDao.count() == 0); + }); + + PersistedJpaSearchFirstPageBundleProvider search = (PersistedJpaSearchFirstPageBundleProvider) myPatientDao.search(new SearchParameterMap()); assertEquals(PersistedJpaSearchFirstPageBundleProvider.class, search.getClass()); assertEquals(2, search.size().intValue()); assertEquals(2, search.getResources(0, 2).size()); runInTransaction(() -> { + await().until(()->mySearchEntityDao.count() == 1); await().until(()->mySearchResultDao.count() == 2); ourLog.info("Search results: {}", mySearchResultDao.findAll().toString()); assertEquals(mySearchResultDao.findAll().toString(), 2, mySearchResultDao.count()); diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/JdbcUtils.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/JdbcUtils.java index 615054cbcf4..014de578bd4 100644 --- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/JdbcUtils.java +++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/JdbcUtils.java @@ -157,6 +157,8 @@ public class JdbcUtils { case Types.TIMESTAMP: case Types.TIMESTAMP_WITH_TIMEZONE: return BaseTableColumnTypeTask.ColumnTypeEnum.DATE_TIMESTAMP.getDescriptor(null); + case Types.BLOB: + return BaseTableColumnTypeTask.ColumnTypeEnum.BLOB.getDescriptor(null); default: throw new IllegalArgumentException("Don't know how to handle datatype " + dataType + " for column " + theColumnName + " on table " + theTableName); } diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseTableColumnTypeTask.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseTableColumnTypeTask.java index f38918141ac..14697c039d7 100644 --- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseTableColumnTypeTask.java +++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseTableColumnTypeTask.java @@ -86,6 +86,15 @@ public abstract class BaseTableColumnTypeTask extends B setColumnType(ColumnTypeEnum.BOOLEAN, DriverTypeEnum.ORACLE_12C, "number(1,0)"); setColumnType(ColumnTypeEnum.BOOLEAN, DriverTypeEnum.POSTGRES_9_4, "boolean"); setColumnType(ColumnTypeEnum.BOOLEAN, DriverTypeEnum.MYSQL_5_7, "bit"); + + setColumnType(ColumnTypeEnum.BLOB, DriverTypeEnum.H2_EMBEDDED, "blob"); + setColumnType(ColumnTypeEnum.BLOB, DriverTypeEnum.DERBY_EMBEDDED, "blob"); + setColumnType(ColumnTypeEnum.BLOB, DriverTypeEnum.MARIADB_10_1, "longblob"); + setColumnType(ColumnTypeEnum.BLOB, DriverTypeEnum.MYSQL_5_7, "longblob"); + setColumnType(ColumnTypeEnum.BLOB, DriverTypeEnum.ORACLE_12C, "blob"); + setColumnType(ColumnTypeEnum.BLOB, DriverTypeEnum.POSTGRES_9_4, "oid"); + setColumnType(ColumnTypeEnum.BLOB, DriverTypeEnum.MSSQL_2012, "varbinary(MAX)"); + } public ColumnTypeEnum getColumnType() { @@ -197,6 +206,14 @@ public abstract class BaseTableColumnTypeTask extends B Assert.isTrue(theColumnLength == null, "Must not supply a column length"); return "int"; } + }, + + BLOB { + @Override + public String getDescriptor(Long theColumnLength) { + Assert.isTrue(theColumnLength == null, "Must not supply a column length"); + return "blob"; + } }; diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java index 8fbdc47ed8c..36659301668 100644 --- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java +++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java @@ -63,6 +63,16 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks { protected void init400() { Builder version = forVersion(VersionEnum.V4_0_0); + // BinaryStorageEntity + Builder.BuilderAddTableByColumns binaryBlob = version.addTableByColumns("HFJ_BINARY_STORAGE_BLOB", "BLOB_ID"); + binaryBlob.addColumn("BLOB_ID").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.STRING, 200); + binaryBlob.addColumn("RESOURCE_ID").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.STRING, 100); + binaryBlob.addColumn("BLOB_SIZE").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.INT); + binaryBlob.addColumn("CONTENT_TYPE").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.STRING, 100); + binaryBlob.addColumn("BLOB_DATA").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.BLOB); + binaryBlob.addColumn("PUBLISHED_DATE").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.DATE_TIMESTAMP); + binaryBlob.addColumn("BLOB_HASH").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.STRING, 128); + // Interim builds used this name version.onTable("TRM_VALUESET_CODE").dropThisTable(); diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BinaryStorageEntity.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BinaryStorageEntity.java new file mode 100644 index 00000000000..b4f2e5de1d3 --- /dev/null +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BinaryStorageEntity.java @@ -0,0 +1,87 @@ +package ca.uhn.fhir.jpa.model.entity; + +import javax.persistence.*; +import java.sql.Blob; +import java.util.Date; + +@Entity +@Table(name = "HFJ_BINARY_STORAGE_BLOB") +public class BinaryStorageEntity { + + @Id + @Column(name = "BLOB_ID", length = 200, nullable = false) + private String myBlobId; + @Column(name = "RESOURCE_ID", length = 100, nullable = false) + private String myResourceId; + @Column(name = "BLOB_SIZE", nullable = true) + private int mySize; + @Column(name = "CONTENT_TYPE", nullable = false, length = 100) + private String myBlobContentType; + @Lob + @Column(name = "BLOB_DATA", nullable = false, insertable = true, updatable = false) + private Blob myBlob; + @Temporal(TemporalType.TIMESTAMP) + @Column(name = "PUBLISHED_DATE", nullable = false) + private Date myPublished; + @Column(name = "BLOB_HASH", length = 128, nullable = true) + private String myHash; + + public Date getPublished() { + if (myPublished == null) { + return null; + } + return new Date(myPublished.getTime()); + } + + public void setPublished(Date thePublishedDate) { + myPublished = thePublishedDate; + } + + public String getHash() { + return myHash; + } + + public void setHash(String theHash) { + myHash = theHash; + } + + public String getBlobId() { + return myBlobId; + } + + public void setBlobId(String theBlobId) { + myBlobId = theBlobId; + } + + public String getResourceId() { + return myResourceId; + } + + public void setResourceId(String theResourceId) { + myResourceId = theResourceId; + } + + public int getSize() { + return mySize; + } + + public void setSize(int theSize) { + mySize = theSize; + } + + public String getBlobContentType() { + return myBlobContentType; + } + + public void setBlobContentType(String theBlobContentType) { + myBlobContentType = theBlobContentType; + } + + public Blob getBlob() { + return myBlob; + } + + public void setBlob(Blob theBlob) { + myBlob = theBlob; + } +} diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/util/JpaConstants.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/util/JpaConstants.java index 8920d443f40..ae1cc3d0d6d 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/util/JpaConstants.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/util/JpaConstants.java @@ -210,6 +210,6 @@ public class JpaConstants { /** * Extension ID for external binary references */ - public static final String EXT_ATTACHMENT_EXTERNAL_BINARY_ID = "http://hapifhir.io/fhir/StructureDefinition/attachment-external-binary-id"; + public static final String EXT_EXTERNALIZED_BINARY_ID = "http://hapifhir.io/fhir/StructureDefinition/externalized-binary-id"; } diff --git a/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/rest/client/GenericClientDstu2_1Test.java b/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/rest/client/GenericClientDstu2_1Test.java index ddda90d3b7a..e5a41d7f5ef 100644 --- a/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/rest/client/GenericClientDstu2_1Test.java +++ b/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/rest/client/GenericClientDstu2_1Test.java @@ -418,9 +418,9 @@ public class GenericClientDstu2_1Test { assertEquals("http://example.com/fhir/Binary", capt.getAllValues().get(0).getURI().toASCIIString()); validateUserAgent(capt); - assertEquals("application/xml+fhir;charset=utf-8", capt.getAllValues().get(0).getHeaders("Content-Type")[0].getValue().toLowerCase().replace(" ", "")); - assertEquals(Constants.CT_FHIR_XML, capt.getAllValues().get(0).getHeaders("Accept")[0].getValue()); - Binary output = ourCtx.newXmlParser().parseResource(Binary.class, extractBodyAsString(capt)); + assertEquals("application/json+fhir;charset=utf-8", capt.getAllValues().get(0).getHeaders("Content-Type")[0].getValue().toLowerCase().replace(" ", "")); + assertEquals(Constants.CT_FHIR_JSON, capt.getAllValues().get(0).getHeaders("Accept")[0].getValue()); + Binary output = ourCtx.newJsonParser().parseResource(Binary.class, extractBodyAsString(capt)); assertEquals(Constants.CT_FHIR_JSON, output.getContentType()); Patient outputPt = (Patient) ourCtx.newJsonParser().parseResource(new String(output.getContent(), "UTF-8")); @@ -457,9 +457,9 @@ public class GenericClientDstu2_1Test { assertEquals("http://example.com/fhir/Binary", capt.getAllValues().get(0).getURI().toASCIIString()); validateUserAgent(capt); - assertEquals("application/xml+fhir;charset=utf-8", capt.getAllValues().get(0).getHeaders("Content-Type")[0].getValue().toLowerCase().replace(" ", "")); - assertEquals(Constants.CT_FHIR_XML, capt.getAllValues().get(0).getHeaders("Accept")[0].getValue()); - assertArrayEquals(new byte[] { 0, 1, 2, 3, 4 }, ourCtx.newXmlParser().parseResource(Binary.class, extractBodyAsString(capt)).getContent()); + assertEquals("application/json+fhir;charset=utf-8", capt.getAllValues().get(0).getHeaders("Content-Type")[0].getValue().toLowerCase().replace(" ", "")); + assertEquals(Constants.CT_FHIR_JSON, capt.getAllValues().get(0).getHeaders("Accept")[0].getValue()); + assertArrayEquals(new byte[] { 0, 1, 2, 3, 4 }, ourCtx.newJsonParser().parseResource(Binary.class, extractBodyAsString(capt)).getContent()); } @@ -569,7 +569,7 @@ public class GenericClientDstu2_1Test { assertEquals(myAnswerCount, capt.getAllValues().size()); assertEquals("http://example.com/fhir/Patient", capt.getAllValues().get(0).getURI().toASCIIString()); - assertEquals(Constants.CT_FHIR_XML, capt.getAllValues().get(0).getFirstHeader("content-type").getValue().replaceAll(";.*", "")); + assertEquals(Constants.CT_FHIR_JSON, capt.getAllValues().get(0).getFirstHeader("content-type").getValue().replaceAll(";.*", "")); assertEquals("http://foo.com/base/Patient/222/_history/3", capt.getAllValues().get(1).getURI().toASCIIString()); } @@ -1076,6 +1076,7 @@ public class GenericClientDstu2_1Test { .update() .resource(bundle) .prefer(PreferReturnEnum.REPRESENTATION) + .encodedXml() .execute(); HttpPut httpRequest = (HttpPut) capt.getValue(); @@ -1670,10 +1671,10 @@ public class GenericClientDstu2_1Test { assertEquals("http://example.com/fhir/Patient/111", capt.getAllValues().get(0).getURI().toASCIIString()); validateUserAgent(capt); - assertEquals("application/xml+fhir;charset=utf-8", capt.getAllValues().get(0).getHeaders("Content-Type")[0].getValue().toLowerCase().replace(" ", "")); - assertEquals(Constants.CT_FHIR_XML, capt.getAllValues().get(0).getHeaders("Accept")[0].getValue()); + assertEquals("application/json+fhir;charset=utf-8", capt.getAllValues().get(0).getHeaders("Content-Type")[0].getValue().toLowerCase().replace(" ", "")); + assertEquals(Constants.CT_FHIR_JSON, capt.getAllValues().get(0).getHeaders("Accept")[0].getValue()); String body = extractBodyAsString(capt); - assertThat(body, containsString("")); + assertThat(body, containsString("\"id\":\"111\"")); } @Test diff --git a/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/rest/client/NonGenericClientDstu2_1Test.java b/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/rest/client/NonGenericClientDstu2_1Test.java index e7b90fb50a2..acd36f754fb 100644 --- a/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/rest/client/NonGenericClientDstu2_1Test.java +++ b/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/rest/client/NonGenericClientDstu2_1Test.java @@ -144,7 +144,7 @@ public class NonGenericClientDstu2_1Test { assertEquals("
OK!
", resp); assertEquals("http://example.com/fhir/$validate", capt.getAllValues().get(idx).getURI().toString()); String request = extractBodyAsString(capt,idx); - assertEquals("", request); + assertEquals("{\"resourceType\":\"Parameters\",\"parameter\":[{\"name\":\"resource\",\"resource\":{\"resourceType\":\"Patient\",\"name\":[{\"family\":[\"FAM\"]}]}}]}", request); idx = 1; outcome = client.validate(patient, ValidationModeEnum.CREATE, "http://foo"); @@ -152,7 +152,7 @@ public class NonGenericClientDstu2_1Test { assertEquals("
OK!
", resp); assertEquals("http://example.com/fhir/$validate", capt.getAllValues().get(idx).getURI().toString()); request = extractBodyAsString(capt,idx); - assertEquals("", request); + assertEquals("{\"resourceType\":\"Parameters\",\"parameter\":[{\"name\":\"resource\",\"resource\":{\"resourceType\":\"Patient\",\"name\":[{\"family\":[\"FAM\"]}]}},{\"name\":\"mode\",\"valueString\":\"create\"},{\"name\":\"profile\",\"valueString\":\"http://foo\"}]}", request); } diff --git a/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/rest/client/OperationClientDstu2_1Test.java b/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/rest/client/OperationClientDstu2_1Test.java index 14160645433..68253e6c4a9 100644 --- a/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/rest/client/OperationClientDstu2_1Test.java +++ b/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/rest/client/OperationClientDstu2_1Test.java @@ -96,7 +96,7 @@ public class OperationClientDstu2_1Test { String requestBody = IOUtils.toString(((HttpPost) value).getEntity().getContent()); IOUtils.closeQuietly(((HttpPost) value).getEntity().getContent()); ourLog.info(requestBody); - Parameters request = ourCtx.newXmlParser().parseResource(Parameters.class, requestBody); + Parameters request = ourCtx.newJsonParser().parseResource(Parameters.class, requestBody); assertEquals("http://foo/$nonrepeating", value.getURI().toASCIIString()); assertEquals(2, request.getParameter().size()); assertEquals("valstr", request.getParameter().get(0).getName()); @@ -131,7 +131,7 @@ public class OperationClientDstu2_1Test { String requestBody = IOUtils.toString(((HttpPost) value).getEntity().getContent()); IOUtils.closeQuietly(((HttpPost) value).getEntity().getContent()); ourLog.info(requestBody); - Parameters request = ourCtx.newXmlParser().parseResource(Parameters.class, requestBody); + Parameters request = ourCtx.newJsonParser().parseResource(Parameters.class, requestBody); assertEquals("http://foo/$nonrepeating", value.getURI().toASCIIString()); assertEquals(2, request.getParameter().size()); assertEquals("valstr", request.getParameter().get(0).getName()); diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/client/BundleTypeDstu2Test.java b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/client/BundleTypeDstu2Test.java index f7deec9466b..82357761c74 100644 --- a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/client/BundleTypeDstu2Test.java +++ b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/client/BundleTypeDstu2Test.java @@ -74,7 +74,7 @@ public class BundleTypeDstu2Test { p1.addIdentifier().setSystem("urn:system").setValue("value"); IGenericClient client = ourCtx.newRestfulGenericClient("http://foo"); - client.transaction().withResources(Arrays.asList((IBaseResource) p1)).execute(); + client.transaction().withResources(Arrays.asList((IBaseResource) p1)).encodedXml().execute(); HttpUriRequest value = capt.getValue(); diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/client/ClientWithProfileDstu2Test.java b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/client/ClientWithProfileDstu2Test.java index 3686d38744a..fa66405cc8f 100644 --- a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/client/ClientWithProfileDstu2Test.java +++ b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/client/ClientWithProfileDstu2Test.java @@ -79,7 +79,7 @@ public class ClientWithProfileDstu2Test { int idx = 0; - client.create().resource(new MyPatient()).execute(); + client.create().resource(new MyPatient()).encodedXml().execute(); HttpPost value = (HttpPost) capt.getAllValues().get(idx); String requestBody = IOUtils.toString(((HttpPost) value).getEntity().getContent()); diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/client/GenericClientDstu2Test.java b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/client/GenericClientDstu2Test.java index 826faa6048c..f499992b6c6 100644 --- a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/client/GenericClientDstu2Test.java +++ b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/client/GenericClientDstu2Test.java @@ -296,7 +296,7 @@ public class GenericClientDstu2Test { Patient p = new Patient(); p.addName().addFamily("FOOFAMILY"); - client.create().resource(p).execute(); + client.create().resource(p).encodedXml().execute(); assertEquals(1, capt.getAllValues().get(idx).getHeaders(Constants.HEADER_CONTENT_TYPE).length); assertEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); @@ -307,7 +307,7 @@ public class GenericClientDstu2Test { p.setId("123"); - client.create().resource(p).execute(); + client.create().resource(p).encodedXml().execute(); assertEquals(1, capt.getAllValues().get(idx).getHeaders(Constants.HEADER_CONTENT_TYPE).length); assertEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); String body = extractBody(capt, idx); @@ -338,7 +338,7 @@ public class GenericClientDstu2Test { Patient p = new Patient(); p.addName().addFamily("FOOFAMILY"); - client.create().resource(p).conditionalByUrl("Patient?name=foo").execute(); + client.create().resource(p).conditionalByUrl("Patient?name=foo").encodedXml().execute(); assertEquals(1, capt.getAllValues().get(idx).getHeaders(Constants.HEADER_CONTENT_TYPE).length); assertEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); assertThat(extractBody(capt, idx), containsString("")); @@ -347,7 +347,7 @@ public class GenericClientDstu2Test { assertEquals("POST", capt.getAllValues().get(idx).getRequestLine().getMethod()); idx++; - client.create().resource(p).conditionalByUrl("Patient?name=http://foo|bar").execute(); + client.create().resource(p).conditionalByUrl("Patient?name=http://foo|bar").encodedXml().execute(); assertEquals(1, capt.getAllValues().get(idx).getHeaders(Constants.HEADER_CONTENT_TYPE).length); assertEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); assertThat(extractBody(capt, idx), containsString("")); @@ -356,7 +356,7 @@ public class GenericClientDstu2Test { assertEquals("POST", capt.getAllValues().get(idx).getRequestLine().getMethod()); idx++; - client.create().resource(p).conditional().where(Patient.NAME.matches().value("foo")).execute(); + client.create().resource(p).conditional().where(Patient.NAME.matches().value("foo")).encodedXml().execute(); assertEquals(1, capt.getAllValues().get(idx).getHeaders(Constants.HEADER_CONTENT_TYPE).length); assertEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); assertThat(extractBody(capt, idx), containsString("")); @@ -387,7 +387,7 @@ public class GenericClientDstu2Test { Patient p = new Patient(); p.addName().addFamily("FOOFAMILY"); - client.create().resource(p).execute(); + client.create().resource(p).encodedXml().execute(); assertEquals(1, capt.getAllValues().get(idx).getHeaders(Constants.HEADER_CONTENT_TYPE).length); assertEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); @@ -767,6 +767,7 @@ public class GenericClientDstu2Test { .add() .onResource(new IdDt("Patient/123")) .meta(inMeta) + .encodedXml() .execute(); //@formatter:on assertEquals("http://example.com/fhir/Patient/123/$meta-add", capt.getAllValues().get(idx).getURI().toASCIIString()); @@ -1072,13 +1073,13 @@ public class GenericClientDstu2Test { int idx = 0; - //@formatter:off Parameters resp = client .operation() .onServer() .named("$SOMEOPERATION") - .withParameters(inParams).execute(); - //@formatter:on + .withParameters(inParams) + .encodedXml() + .execute(); assertEquals("http://example.com/fhir/$SOMEOPERATION", capt.getAllValues().get(idx).getURI().toASCIIString()); assertEquals(1, capt.getAllValues().get(idx).getHeaders(Constants.HEADER_CONTENT_TYPE).length); assertEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); @@ -1113,15 +1114,14 @@ public class GenericClientDstu2Test { int idx = 0; - //@formatter:off Parameters resp = client .operation() .onServer() .named("$SOMEOPERATION") .withParameter(Parameters.class, "name1", new StringDt("value1")) .andParameter("name2", new StringDt("value1")) + .encodedXml() .execute(); - //@formatter:on assertEquals("http://example.com/fhir/$SOMEOPERATION", capt.getAllValues().get(idx).getURI().toASCIIString()); assertEquals(respString, p.encodeResourceToString(resp)); assertEquals(1, capt.getAllValues().get(idx).getHeaders(Constants.HEADER_CONTENT_TYPE).length); @@ -1148,10 +1148,10 @@ public class GenericClientDstu2Test { assertEquals("http://example.com/fhir/$SOMEOPERATION", capt.getAllValues().get(idx).getURI().toASCIIString()); assertEquals(respString, p.encodeResourceToString(resp)); assertEquals(1, capt.getAllValues().get(idx).getHeaders(Constants.HEADER_CONTENT_TYPE).length); - assertEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); + assertEquals(EncodingEnum.JSON.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); assertEquals("POST", capt.getAllValues().get(idx).getRequestLine().getMethod()); assertEquals( - "", + "{\"resourceType\":\"Parameters\",\"parameter\":[{\"name\":\"name1\",\"valueIdentifier\":{\"system\":\"system1\",\"value\":\"value1\"}},{\"name\":\"name2\",\"valueString\":\"value1\"}]}", (extractBody(capt, idx))); idx++; @@ -1171,10 +1171,10 @@ public class GenericClientDstu2Test { assertEquals("http://example.com/fhir/$SOMEOPERATION", capt.getAllValues().get(idx).getURI().toASCIIString()); assertEquals(respString, p.encodeResourceToString(resp)); assertEquals(1, capt.getAllValues().get(idx).getHeaders(Constants.HEADER_CONTENT_TYPE).length); - assertEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); + assertEquals(EncodingEnum.JSON.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); assertEquals("POST", capt.getAllValues().get(idx).getRequestLine().getMethod()); assertEquals( - "", + "{\"resourceType\":\"Parameters\",\"parameter\":[{\"name\":\"name1\",\"valueIdentifier\":{\"system\":\"system1\",\"value\":\"value1\"}},{\"name\":\"name2\",\"resource\":{\"resourceType\":\"Patient\",\"active\":true}}]}", (extractBody(capt, idx))); idx++; @@ -1253,7 +1253,9 @@ public class GenericClientDstu2Test { .operation() .onServer() .named("$SOMEOPERATION") - .withParameters(inParams).execute(); + .withParameters(inParams) + .encodedXml() + .execute(); //@formatter:on assertEquals("http://example.com/fhir/$SOMEOPERATION", capt.getAllValues().get(idx).getURI().toASCIIString()); assertEquals(respString, p.encodeResourceToString(resp)); @@ -1268,7 +1270,9 @@ public class GenericClientDstu2Test { .operation() .onType(Patient.class) .named("$SOMEOPERATION") - .withParameters(inParams).execute(); + .withParameters(inParams) + .encodedXml() + .execute(); //@formatter:on assertEquals("http://example.com/fhir/Patient/$SOMEOPERATION", capt.getAllValues().get(idx).getURI().toASCIIString()); assertEquals(respString, p.encodeResourceToString(resp)); @@ -1283,7 +1287,9 @@ public class GenericClientDstu2Test { .operation() .onInstance(new IdDt("Patient", "123")) .named("$SOMEOPERATION") - .withParameters(inParams).execute(); + .withParameters(inParams) + .encodedXml() + .execute(); //@formatter:on assertEquals("http://example.com/fhir/Patient/123/$SOMEOPERATION", capt.getAllValues().get(idx).getURI().toASCIIString()); assertEquals(respString, p.encodeResourceToString(resp)); @@ -1331,7 +1337,9 @@ public class GenericClientDstu2Test { .operation() .onServer() .named("$SOMEOPERATION") - .withNoParameters(Parameters.class).execute(); + .withNoParameters(Parameters.class) + .encodedXml() + .execute(); //@formatter:on assertEquals("http://example.com/fhir/$SOMEOPERATION", capt.getAllValues().get(idx).getURI().toASCIIString()); assertEquals(respString, p.encodeResourceToString(resp)); @@ -1346,7 +1354,9 @@ public class GenericClientDstu2Test { .operation() .onType(Patient.class) .named("$SOMEOPERATION") - .withNoParameters(Parameters.class).execute(); + .withNoParameters(Parameters.class) + .encodedXml() + .execute(); //@formatter:on assertEquals("http://example.com/fhir/Patient/$SOMEOPERATION", capt.getAllValues().get(idx).getURI().toASCIIString()); assertEquals(respString, p.encodeResourceToString(resp)); @@ -1361,7 +1371,9 @@ public class GenericClientDstu2Test { .operation() .onInstance(new IdDt("Patient", "123")) .named("$SOMEOPERATION") - .withNoParameters(Parameters.class).execute(); + .withNoParameters(Parameters.class) + .encodedXml() + .execute(); //@formatter:on assertEquals("http://example.com/fhir/Patient/123/$SOMEOPERATION", capt.getAllValues().get(idx).getURI().toASCIIString()); assertEquals(respString, p.encodeResourceToString(resp)); @@ -1377,6 +1389,7 @@ public class GenericClientDstu2Test { .onInstance(new IdDt("http://foo.com/bar/baz/Patient/123/_history/22")) .named("$SOMEOPERATION") .withNoParameters(Parameters.class) + .encodedXml() .execute(); // @formatter:on assertEquals("http://example.com/fhir/Patient/123/$SOMEOPERATION", capt.getAllValues().get(idx).getURI().toASCIIString()); @@ -1428,6 +1441,7 @@ public class GenericClientDstu2Test { .named("validate-code") .withParameter(Parameters.class, "code", new CodeDt("8495-4")) .andParameter("system", new UriDt("http://loinc.org")) + .encodedXml() .execute(); //@formatter:off @@ -2353,7 +2367,7 @@ public class GenericClientDstu2Test { Patient p = new Patient(); p.addName().addFamily("FOOFAMILY"); - client.update().resource(p).conditionalByUrl("Patient?name=foo").execute(); + client.update().resource(p).conditionalByUrl("Patient?name=foo").encodedXml().execute(); assertEquals(1, capt.getAllValues().get(idx).getHeaders(Constants.HEADER_CONTENT_TYPE).length); assertEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); assertThat(extractBody(capt, idx), containsString("")); @@ -2361,7 +2375,7 @@ public class GenericClientDstu2Test { assertEquals("http://example.com/fhir/Patient?name=foo", capt.getAllValues().get(idx).getURI().toString()); idx++; - client.update().resource(p).conditionalByUrl("Patient?name=http://foo|bar").execute(); + client.update().resource(p).conditionalByUrl("Patient?name=http://foo|bar").encodedXml().execute(); assertEquals(1, capt.getAllValues().get(idx).getHeaders(Constants.HEADER_CONTENT_TYPE).length); assertEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); assertThat(extractBody(capt, idx), containsString("")); @@ -2369,7 +2383,7 @@ public class GenericClientDstu2Test { assertEquals("http://example.com/fhir/Patient?name=http%3A//foo%7Cbar", capt.getAllValues().get(idx).getURI().toString()); idx++; - client.update().resource(ourCtx.newXmlParser().encodeResourceToString(p)).conditionalByUrl("Patient?name=foo").execute(); + client.update().resource(ourCtx.newXmlParser().encodeResourceToString(p)).conditionalByUrl("Patient?name=foo").encodedXml().execute(); assertEquals(1, capt.getAllValues().get(idx).getHeaders(Constants.HEADER_CONTENT_TYPE).length); assertEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); assertThat(extractBody(capt, idx), containsString("")); @@ -2377,7 +2391,7 @@ public class GenericClientDstu2Test { assertEquals("http://example.com/fhir/Patient?name=foo", capt.getAllValues().get(idx).getURI().toString()); idx++; - client.update().resource(p).conditional().where(Patient.NAME.matches().value("foo")).and(Patient.ADDRESS.matches().value("AAA|BBB")).execute(); + client.update().resource(p).conditional().where(Patient.NAME.matches().value("foo")).and(Patient.ADDRESS.matches().value("AAA|BBB")).encodedXml().execute(); assertEquals(1, capt.getAllValues().get(idx).getHeaders(Constants.HEADER_CONTENT_TYPE).length); assertEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); assertThat(extractBody(capt, idx), containsString("")); @@ -2385,7 +2399,7 @@ public class GenericClientDstu2Test { assertEquals("http://example.com/fhir/Patient?name=foo&address=AAA%5C%7CBBB", capt.getAllValues().get(idx).getURI().toString()); idx++; - client.update().resource(ourCtx.newXmlParser().encodeResourceToString(p)).conditional().where(Patient.NAME.matches().value("foo")).and(Patient.ADDRESS.matches().value("AAA|BBB")).execute(); + client.update().resource(ourCtx.newXmlParser().encodeResourceToString(p)).conditional().where(Patient.NAME.matches().value("foo")).and(Patient.ADDRESS.matches().value("AAA|BBB")).encodedXml().execute(); assertEquals(1, capt.getAllValues().get(idx).getHeaders(Constants.HEADER_CONTENT_TYPE).length); assertEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); assertThat(extractBody(capt, idx), containsString("")); @@ -2416,16 +2430,16 @@ public class GenericClientDstu2Test { client.update(new IdDt("Patient/123"), p); assertEquals(1, capt.getAllValues().get(idx).getHeaders(Constants.HEADER_CONTENT_TYPE).length); - assertEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); - assertThat(extractBody(capt, idx), containsString("")); + assertEquals(EncodingEnum.JSON.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); + assertThat(extractBody(capt, idx), containsString("{\"family\":[\"FOOFAMILY\"]}")); assertEquals("http://example.com/fhir/Patient/123", capt.getAllValues().get(idx).getURI().toString()); assertEquals("PUT", capt.getAllValues().get(idx).getRequestLine().getMethod()); idx++; client.update("123", p); assertEquals(1, capt.getAllValues().get(idx).getHeaders(Constants.HEADER_CONTENT_TYPE).length); - assertEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); - assertThat(extractBody(capt, idx), containsString("")); + assertEquals(EncodingEnum.JSON.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); + assertThat(extractBody(capt, idx), containsString("{\"family\":[\"FOOFAMILY\"]}")); assertEquals("http://example.com/fhir/Patient/123", capt.getAllValues().get(idx).getURI().toString()); assertEquals("PUT", capt.getAllValues().get(idx).getRequestLine().getMethod()); idx++; @@ -2517,7 +2531,7 @@ public class GenericClientDstu2Test { int idx = 0; MethodOutcome response; - response = client.validate().resource(p).execute(); + response = client.validate().resource(p).encodedXml().execute(); assertEquals("http://example.com/fhir/Patient/$validate", capt.getAllValues().get(idx).getURI().toASCIIString()); assertEquals("POST", capt.getAllValues().get(idx).getRequestLine().getMethod()); assertEquals("application/xml+fhir", capt.getAllValues().get(idx).getFirstHeader("content-type").getValue().replaceAll(";.*", "")); @@ -2528,7 +2542,7 @@ public class GenericClientDstu2Test { assertEquals("FOOBAR", toOo(response.getOperationOutcome()).getIssueFirstRep().getDiagnosticsElement().getValue()); idx++; - response = client.validate().resource(ourCtx.newXmlParser().encodeResourceToString(p)).execute(); + response = client.validate().resource(ourCtx.newXmlParser().encodeResourceToString(p)).encodedXml().execute(); assertEquals("http://example.com/fhir/Patient/$validate", capt.getAllValues().get(idx).getURI().toASCIIString()); assertEquals("POST", capt.getAllValues().get(idx).getRequestLine().getMethod()); assertEquals("application/xml+fhir", capt.getAllValues().get(idx).getFirstHeader("content-type").getValue().replaceAll(";.*", "")); @@ -2584,14 +2598,12 @@ public class GenericClientDstu2Test { int idx = 0; MethodOutcome response; - //@formatter:off response = client.validate(p); - //@formatter:on assertEquals("http://example.com/fhir/Patient/$validate", capt.getAllValues().get(idx).getURI().toASCIIString()); assertEquals("POST", capt.getAllValues().get(idx).getRequestLine().getMethod()); assertEquals( - "", + "{\"resourceType\":\"Parameters\",\"parameter\":[{\"name\":\"resource\",\"resource\":{\"resourceType\":\"Patient\",\"name\":[{\"given\":[\"GIVEN\"]}]}}]}", extractBody(capt, idx)); assertNotNull(response.getOperationOutcome()); assertEquals("FOOBAR", toOo(response.getOperationOutcome()).getIssueFirstRep().getDiagnosticsElement().getValue()); diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/client/OperationClientDstu2Test.java b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/client/OperationClientDstu2Test.java index 85605fc00e2..55d03015dd6 100644 --- a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/client/OperationClientDstu2Test.java +++ b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/client/OperationClientDstu2Test.java @@ -88,7 +88,7 @@ public class OperationClientDstu2Test { String requestBody = IOUtils.toString(((HttpPost) value).getEntity().getContent()); IOUtils.closeQuietly(((HttpPost) value).getEntity().getContent()); ourLog.info(requestBody); - Parameters request = ourCtx.newXmlParser().parseResource(Parameters.class, requestBody); + Parameters request = ourCtx.newJsonParser().parseResource(Parameters.class, requestBody); assertEquals("http://foo/Patient/222/$OP_INSTANCE", value.getURI().toASCIIString()); assertEquals(2, request.getParameter().size()); assertEquals("PARAM1", request.getParameter().get(0).getName()); @@ -128,7 +128,7 @@ public class OperationClientDstu2Test { String requestBody = IOUtils.toString(((HttpPost) value).getEntity().getContent()); IOUtils.closeQuietly(((HttpPost) value).getEntity().getContent()); ourLog.info(requestBody); - Parameters request = ourCtx.newXmlParser().parseResource(Parameters.class, requestBody); + Parameters request = ourCtx.newJsonParser().parseResource(Parameters.class, requestBody); assertEquals("http://foo/Patient/222/$OP_INSTANCE_WITH_BUNDLE_RETURN", value.getURI().toASCIIString()); assertEquals(2, request.getParameter().size()); assertEquals("PARAM1", request.getParameter().get(0).getName()); @@ -165,7 +165,7 @@ public class OperationClientDstu2Test { String requestBody = IOUtils.toString(((HttpPost) value).getEntity().getContent()); IOUtils.closeQuietly(((HttpPost) value).getEntity().getContent()); ourLog.info(requestBody); - Parameters request = ourCtx.newXmlParser().parseResource(Parameters.class, requestBody); + Parameters request = ourCtx.newJsonParser().parseResource(Parameters.class, requestBody); assertEquals("http://foo/$OP_SERVER", value.getURI().toASCIIString()); assertEquals(2, request.getParameter().size()); assertEquals("PARAM1", request.getParameter().get(0).getName()); @@ -180,7 +180,7 @@ public class OperationClientDstu2Test { requestBody = IOUtils.toString(((HttpPost) value).getEntity().getContent()); IOUtils.closeQuietly(((HttpPost) value).getEntity().getContent()); ourLog.info(requestBody); - request = ourCtx.newXmlParser().parseResource(Parameters.class, requestBody); + request = ourCtx.newJsonParser().parseResource(Parameters.class, requestBody); assertEquals(1, request.getParameter().size()); assertEquals("PARAM2", request.getParameter().get(0).getName()); assertEquals(Boolean.TRUE, ((Patient) request.getParameter().get(0).getResource()).getActive()); @@ -192,7 +192,7 @@ public class OperationClientDstu2Test { requestBody = IOUtils.toString(((HttpPost) value).getEntity().getContent()); IOUtils.closeQuietly(((HttpPost) value).getEntity().getContent()); ourLog.info(requestBody); - request = ourCtx.newXmlParser().parseResource(Parameters.class, requestBody); + request = ourCtx.newJsonParser().parseResource(Parameters.class, requestBody); assertEquals(0, request.getParameter().size()); idx++; @@ -225,7 +225,7 @@ public class OperationClientDstu2Test { String requestBody = IOUtils.toString(((HttpPost) value).getEntity().getContent()); IOUtils.closeQuietly(((HttpPost) value).getEntity().getContent()); ourLog.info(requestBody); - Parameters request = ourCtx.newXmlParser().parseResource(Parameters.class, requestBody); + Parameters request = ourCtx.newJsonParser().parseResource(Parameters.class, requestBody); assertEquals("http://foo/$OP_SERVER_LIST_PARAM", value.getURI().toASCIIString()); assertEquals(3, request.getParameter().size()); assertEquals("PARAM2", request.getParameter().get(0).getName()); @@ -242,7 +242,7 @@ public class OperationClientDstu2Test { requestBody = IOUtils.toString(((HttpPost) value).getEntity().getContent()); IOUtils.closeQuietly(((HttpPost) value).getEntity().getContent()); ourLog.info(requestBody); - request = ourCtx.newXmlParser().parseResource(Parameters.class, requestBody); + request = ourCtx.newJsonParser().parseResource(Parameters.class, requestBody); assertEquals("http://foo/$OP_SERVER_LIST_PARAM", value.getURI().toASCIIString()); assertEquals(2, request.getParameter().size()); assertEquals("PARAM3", request.getParameter().get(0).getName()); @@ -251,13 +251,13 @@ public class OperationClientDstu2Test { assertEquals("PARAM3str2", ((StringDt) request.getParameter().get(1).getValue()).getValue()); idx++; - response = client.opServerListParam(null, new ArrayList()); + response = client.opServerListParam(null, new ArrayList<>()); assertEquals("FOO", response.getParameter().get(0).getName()); value = (HttpPost) capt.getAllValues().get(idx); - requestBody = IOUtils.toString(((HttpPost) value).getEntity().getContent()); + requestBody = IOUtils.toString(value.getEntity().getContent()); IOUtils.closeQuietly(((HttpPost) value).getEntity().getContent()); ourLog.info(requestBody); - request = ourCtx.newXmlParser().parseResource(Parameters.class, requestBody); + request = ourCtx.newJsonParser().parseResource(Parameters.class, requestBody); assertEquals("http://foo/$OP_SERVER_LIST_PARAM", value.getURI().toASCIIString()); assertEquals(0, request.getParameter().size()); idx++; @@ -268,7 +268,7 @@ public class OperationClientDstu2Test { requestBody = IOUtils.toString(((HttpPost) value).getEntity().getContent()); IOUtils.closeQuietly(((HttpPost) value).getEntity().getContent()); ourLog.info(requestBody); - request = ourCtx.newXmlParser().parseResource(Parameters.class, requestBody); + request = ourCtx.newJsonParser().parseResource(Parameters.class, requestBody); assertEquals("http://foo/$OP_SERVER_LIST_PARAM", value.getURI().toASCIIString()); assertEquals(0, request.getParameter().size()); idx++; @@ -302,7 +302,7 @@ public class OperationClientDstu2Test { String requestBody = IOUtils.toString(((HttpPost) value).getEntity().getContent()); IOUtils.closeQuietly(((HttpPost) value).getEntity().getContent()); ourLog.info(requestBody); - Parameters request = ourCtx.newXmlParser().parseResource(Parameters.class, requestBody); + Parameters request = ourCtx.newJsonParser().parseResource(Parameters.class, requestBody); assertEquals("http://foo/Patient/$OP_TYPE", value.getURI().toASCIIString()); assertEquals(2, request.getParameter().size()); assertEquals("PARAM1", request.getParameter().get(0).getName()); diff --git a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/client/ClientMimetypeDstu3Test.java b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/client/ClientMimetypeDstu3Test.java index e6cf4b68ddf..606d8f35270 100644 --- a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/client/ClientMimetypeDstu3Test.java +++ b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/client/ClientMimetypeDstu3Test.java @@ -59,7 +59,7 @@ public class ClientMimetypeDstu3Test { Patient pt = new Patient(); pt.getText().setDivAsString("A PATIENT"); - MethodOutcome outcome = client.create().resource(pt).execute(); + MethodOutcome outcome = client.create().resource(pt).encodedXml().execute(); assertEquals("
FINAL VALUE
", ((Patient) outcome.getResource()).getText().getDivAsString()); assertEquals("http://example.com/fhir/Patient", capt.getAllValues().get(0).getURI().toASCIIString()); @@ -79,7 +79,7 @@ public class ClientMimetypeDstu3Test { Patient pt = new Patient(); pt.getText().setDivAsString("A PATIENT"); - MethodOutcome outcome = client.create().resource(pt).execute(); + MethodOutcome outcome = client.create().resource(pt).encodedXml().execute(); assertEquals("
FINAL VALUE
", ((Patient) outcome.getResource()).getText().getDivAsString()); assertEquals("http://example.com/fhir/Patient", capt.getAllValues().get(0).getURI().toASCIIString()); diff --git a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/client/GenericClientDstu3Test.java b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/client/GenericClientDstu3Test.java index fa21ed916d0..625b0ba0a85 100644 --- a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/client/GenericClientDstu3Test.java +++ b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/client/GenericClientDstu3Test.java @@ -404,9 +404,9 @@ public class GenericClientDstu3Test { assertEquals("http://example.com/fhir/Binary", capt.getAllValues().get(0).getURI().toASCIIString()); validateUserAgent(capt); - assertEquals("application/fhir+xml;charset=utf-8", capt.getAllValues().get(0).getHeaders("Content-Type")[0].getValue().toLowerCase().replace(" ", "")); - assertEquals(Constants.HEADER_ACCEPT_VALUE_XML_NON_LEGACY, capt.getAllValues().get(0).getHeaders("Accept")[0].getValue()); - Binary output = ourCtx.newXmlParser().parseResource(Binary.class, extractBodyAsString(capt)); + assertEquals("application/fhir+json;charset=utf-8", capt.getAllValues().get(0).getHeaders("Content-Type")[0].getValue().toLowerCase().replace(" ", "")); + assertEquals(Constants.HEADER_ACCEPT_VALUE_JSON_NON_LEGACY, capt.getAllValues().get(0).getHeaders("Accept")[0].getValue()); + Binary output = ourCtx.newJsonParser().parseResource(Binary.class, extractBodyAsString(capt)); assertEquals(Constants.CT_FHIR_JSON, output.getContentType()); Patient outputPt = (Patient) ourCtx.newJsonParser().parseResource(new String(output.getContent(), "UTF-8")); @@ -443,9 +443,9 @@ public class GenericClientDstu3Test { assertEquals("http://example.com/fhir/Binary", capt.getAllValues().get(0).getURI().toASCIIString()); validateUserAgent(capt); - assertEquals("application/fhir+xml;charset=utf-8", capt.getAllValues().get(0).getHeaders("Content-Type")[0].getValue().toLowerCase().replace(" ", "")); - assertEquals(Constants.HEADER_ACCEPT_VALUE_XML_NON_LEGACY, capt.getAllValues().get(0).getHeaders("Accept")[0].getValue()); - assertArrayEquals(new byte[] { 0, 1, 2, 3, 4 }, ourCtx.newXmlParser().parseResource(Binary.class, extractBodyAsString(capt)).getContent()); + assertEquals("application/fhir+json;charset=utf-8", capt.getAllValues().get(0).getHeaders("Content-Type")[0].getValue().toLowerCase().replace(" ", "")); + assertEquals(Constants.HEADER_ACCEPT_VALUE_JSON_NON_LEGACY, capt.getAllValues().get(0).getHeaders("Accept")[0].getValue()); + assertArrayEquals(new byte[] { 0, 1, 2, 3, 4 }, ourCtx.newJsonParser().parseResource(Binary.class, extractBodyAsString(capt)).getContent()); } @@ -555,7 +555,7 @@ public class GenericClientDstu3Test { assertEquals(myAnswerCount, capt.getAllValues().size()); assertEquals("http://example.com/fhir/Patient", capt.getAllValues().get(0).getURI().toASCIIString()); - assertEquals(Constants.CT_FHIR_XML_NEW, capt.getAllValues().get(0).getFirstHeader("content-type").getValue().replaceAll(";.*", "")); + assertEquals(Constants.CT_FHIR_JSON_NEW, capt.getAllValues().get(0).getFirstHeader("content-type").getValue().replaceAll(";.*", "")); assertEquals("http://foo.com/base/Patient/222/_history/3", capt.getAllValues().get(1).getURI().toASCIIString()); } @@ -1011,6 +1011,7 @@ public class GenericClientDstu3Test { .update() .resource(bundle) .prefer(PreferReturnEnum.REPRESENTATION) + .encodedXml() .execute(); HttpPut httpRequest = (HttpPut) capt.getValue(); @@ -1635,10 +1636,10 @@ public class GenericClientDstu3Test { assertEquals("http://example.com/fhir/Patient/111", capt.getAllValues().get(0).getURI().toASCIIString()); validateUserAgent(capt); - assertEquals("application/fhir+xml;charset=utf-8", capt.getAllValues().get(0).getHeaders("Content-Type")[0].getValue().toLowerCase().replace(" ", "")); - assertEquals(Constants.HEADER_ACCEPT_VALUE_XML_NON_LEGACY, capt.getAllValues().get(0).getHeaders("Accept")[0].getValue()); + assertEquals("application/fhir+json;charset=utf-8", capt.getAllValues().get(0).getHeaders("Content-Type")[0].getValue().toLowerCase().replace(" ", "")); + assertEquals(Constants.HEADER_ACCEPT_VALUE_JSON_NON_LEGACY, capt.getAllValues().get(0).getHeaders("Accept")[0].getValue()); String body = extractBodyAsString(capt); - assertThat(body, containsString("")); + assertThat(body, containsString("\"id\":\"111\"")); } @Test diff --git a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/client/NonGenericClientDstu3Test.java b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/client/NonGenericClientDstu3Test.java index 7c397df723b..ea1f22ee933 100644 --- a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/client/NonGenericClientDstu3Test.java +++ b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/client/NonGenericClientDstu3Test.java @@ -144,7 +144,7 @@ public class NonGenericClientDstu3Test { assertEquals("
OK!
", resp); assertEquals("http://example.com/fhir/$validate", capt.getAllValues().get(idx).getURI().toString()); String request = extractBodyAsString(capt,idx); - assertEquals("", request); + assertEquals("{\"resourceType\":\"Parameters\",\"parameter\":[{\"name\":\"resource\",\"resource\":{\"resourceType\":\"Patient\",\"name\":[{\"family\":\"FAM\"}]}}]}", request); idx = 1; outcome = client.validate(patient, ValidationModeEnum.CREATE, "http://foo"); @@ -152,7 +152,7 @@ public class NonGenericClientDstu3Test { assertEquals("
OK!
", resp); assertEquals("http://example.com/fhir/$validate", capt.getAllValues().get(idx).getURI().toString()); request = extractBodyAsString(capt,idx); - assertEquals("", request); + assertEquals("{\"resourceType\":\"Parameters\",\"parameter\":[{\"name\":\"resource\",\"resource\":{\"resourceType\":\"Patient\",\"name\":[{\"family\":\"FAM\"}]}},{\"name\":\"mode\",\"valueString\":\"create\"},{\"name\":\"profile\",\"valueString\":\"http://foo\"}]}", request); } diff --git a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/client/OperationClientDstu3Test.java b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/client/OperationClientDstu3Test.java index 0720b0036ec..f96db4bf7d9 100644 --- a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/client/OperationClientDstu3Test.java +++ b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/client/OperationClientDstu3Test.java @@ -96,7 +96,7 @@ public class OperationClientDstu3Test { String requestBody = IOUtils.toString(((HttpPost) value).getEntity().getContent()); IOUtils.closeQuietly(((HttpPost) value).getEntity().getContent()); ourLog.info(requestBody); - Parameters request = ourCtx.newXmlParser().parseResource(Parameters.class, requestBody); + Parameters request = ourCtx.newJsonParser().parseResource(Parameters.class, requestBody); assertEquals("http://foo/$nonrepeating", value.getURI().toASCIIString()); assertEquals(2, request.getParameter().size()); assertEquals("valstr", request.getParameter().get(0).getName()); @@ -131,7 +131,7 @@ public class OperationClientDstu3Test { String requestBody = IOUtils.toString(((HttpPost) value).getEntity().getContent()); IOUtils.closeQuietly(((HttpPost) value).getEntity().getContent()); ourLog.info(requestBody); - Parameters request = ourCtx.newXmlParser().parseResource(Parameters.class, requestBody); + Parameters request = ourCtx.newJsonParser().parseResource(Parameters.class, requestBody); assertEquals("http://foo/$nonrepeating", value.getURI().toASCIIString()); assertEquals(2, request.getParameter().size()); assertEquals("valstr", request.getParameter().get(0).getName()); diff --git a/hapi-fhir-structures-hl7org-dstu2/src/test/java/ca/uhn/fhir/rest/client/GenericClientDstu2Hl7OrgTest.java b/hapi-fhir-structures-hl7org-dstu2/src/test/java/ca/uhn/fhir/rest/client/GenericClientDstu2Hl7OrgTest.java index 6e072b27d3c..92a4a7ef3eb 100644 --- a/hapi-fhir-structures-hl7org-dstu2/src/test/java/ca/uhn/fhir/rest/client/GenericClientDstu2Hl7OrgTest.java +++ b/hapi-fhir-structures-hl7org-dstu2/src/test/java/ca/uhn/fhir/rest/client/GenericClientDstu2Hl7OrgTest.java @@ -236,7 +236,7 @@ public class GenericClientDstu2Hl7OrgTest { @Test public void testOperationWithListOfParameterResponse() throws Exception { - IParser p = ourCtx.newXmlParser(); + IParser p = ourCtx.newJsonParser(); Parameters inParams = new Parameters(); inParams.addParameter().setValue(new StringType("STRINGVALIN1")); @@ -251,7 +251,7 @@ public class GenericClientDstu2Hl7OrgTest { ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); - when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { @@ -263,17 +263,16 @@ public class GenericClientDstu2Hl7OrgTest { int idx = 0; - //@formatter:off Parameters resp = client .operation() .onServer() .named("$SOMEOPERATION") - .withParameters(inParams).execute(); - //@formatter:on + .withParameters(inParams) + .execute(); assertEquals("http://example.com/fhir/$SOMEOPERATION", capt.getAllValues().get(idx).getURI().toASCIIString()); assertEquals(respString, p.encodeResourceToString(resp)); assertEquals(1, capt.getAllValues().get(idx).getHeaders(Constants.HEADER_CONTENT_TYPE).length); - assertEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); + assertEquals(EncodingEnum.JSON.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); assertEquals(extractBody(capt, idx), reqString); assertEquals("POST", capt.getAllValues().get(idx).getRequestLine().getMethod()); idx++; @@ -288,7 +287,7 @@ public class GenericClientDstu2Hl7OrgTest { assertEquals("http://example.com/fhir/Patient/$SOMEOPERATION", capt.getAllValues().get(idx).getURI().toASCIIString()); assertEquals(respString, p.encodeResourceToString(resp)); assertEquals(1, capt.getAllValues().get(idx).getHeaders(Constants.HEADER_CONTENT_TYPE).length); - assertEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); + assertEquals(EncodingEnum.JSON.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); assertEquals(extractBody(capt, idx), reqString); assertEquals("POST", capt.getAllValues().get(idx).getRequestLine().getMethod()); idx++; @@ -303,7 +302,7 @@ public class GenericClientDstu2Hl7OrgTest { assertEquals("http://example.com/fhir/Patient/123/$SOMEOPERATION", capt.getAllValues().get(idx).getURI().toASCIIString()); assertEquals(respString, p.encodeResourceToString(resp)); assertEquals(1, capt.getAllValues().get(idx).getHeaders(Constants.HEADER_CONTENT_TYPE).length); - assertEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); + assertEquals(EncodingEnum.JSON.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); assertEquals(extractBody(capt, idx), reqString); assertEquals("POST", capt.getAllValues().get(idx).getRequestLine().getMethod()); idx++; @@ -316,7 +315,7 @@ public class GenericClientDstu2Hl7OrgTest { @Test public void testOperationWithNoInParameters() throws Exception { - IParser p = ourCtx.newXmlParser(); + IParser p = ourCtx.newJsonParser(); Parameters inParams = new Parameters(); final String reqString = p.encodeResourceToString(inParams); @@ -329,7 +328,7 @@ public class GenericClientDstu2Hl7OrgTest { ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); - when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { @@ -351,7 +350,7 @@ public class GenericClientDstu2Hl7OrgTest { assertEquals("http://example.com/fhir/$SOMEOPERATION", capt.getAllValues().get(idx).getURI().toASCIIString()); assertEquals(respString, p.encodeResourceToString(resp)); assertEquals(1, capt.getAllValues().get(idx).getHeaders(Constants.HEADER_CONTENT_TYPE).length); - assertEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); + assertEquals(EncodingEnum.JSON.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); assertEquals(extractBody(capt, idx), reqString); assertEquals("POST", capt.getAllValues().get(idx).getRequestLine().getMethod()); idx++; @@ -366,7 +365,7 @@ public class GenericClientDstu2Hl7OrgTest { assertEquals("http://example.com/fhir/Patient/$SOMEOPERATION", capt.getAllValues().get(idx).getURI().toASCIIString()); assertEquals(respString, p.encodeResourceToString(resp)); assertEquals(1, capt.getAllValues().get(idx).getHeaders(Constants.HEADER_CONTENT_TYPE).length); - assertEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); + assertEquals(EncodingEnum.JSON.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); assertEquals(extractBody(capt, idx), reqString); assertEquals("POST", capt.getAllValues().get(idx).getRequestLine().getMethod()); idx++; @@ -381,7 +380,7 @@ public class GenericClientDstu2Hl7OrgTest { assertEquals("http://example.com/fhir/Patient/123/$SOMEOPERATION", capt.getAllValues().get(idx).getURI().toASCIIString()); assertEquals(respString, p.encodeResourceToString(resp)); assertEquals(1, capt.getAllValues().get(idx).getHeaders(Constants.HEADER_CONTENT_TYPE).length); - assertEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); + assertEquals(EncodingEnum.JSON.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); assertEquals(extractBody(capt, idx), reqString); assertEquals("POST", capt.getAllValues().get(idx).getRequestLine().getMethod()); idx++; @@ -563,7 +562,7 @@ public class GenericClientDstu2Hl7OrgTest { @Test public void testOperationWithBundleResponse() throws Exception { - IParser p = ourCtx.newXmlParser(); + IParser p = ourCtx.newJsonParser(); Parameters inParams = new Parameters(); inParams.addParameter().setValue(new StringType("STRINGVALIN1")); @@ -577,7 +576,7 @@ public class GenericClientDstu2Hl7OrgTest { ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); - when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { @@ -598,7 +597,7 @@ public class GenericClientDstu2Hl7OrgTest { //@formatter:on assertEquals("http://example.com/fhir/$SOMEOPERATION", capt.getAllValues().get(idx).getURI().toASCIIString()); assertEquals(1, capt.getAllValues().get(idx).getHeaders(Constants.HEADER_CONTENT_TYPE).length); - assertEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); + assertEquals(EncodingEnum.JSON.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); assertEquals(extractBody(capt, idx), reqString); assertEquals("POST", capt.getAllValues().get(idx).getRequestLine().getMethod()); assertEquals(1, resp.getParameter().size()); @@ -756,8 +755,8 @@ public class GenericClientDstu2Hl7OrgTest { client.create().resource(p).conditionalByUrl("Patient?name=foo").execute(); assertEquals(1, capt.getAllValues().get(idx).getHeaders(Constants.HEADER_CONTENT_TYPE).length); - assertEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); - assertThat(extractBody(capt, idx), containsString("")); + assertEquals(EncodingEnum.JSON.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); + assertThat(extractBody(capt, idx), containsString("{\"family\":[\"FOOFAMILY\"]}")); assertEquals("http://example.com/fhir/Patient", capt.getAllValues().get(idx).getURI().toString()); assertEquals("http://example.com/fhir/Patient?name=foo", capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_IF_NONE_EXIST).getValue()); assertEquals("POST", capt.getAllValues().get(idx).getRequestLine().getMethod()); @@ -765,8 +764,8 @@ public class GenericClientDstu2Hl7OrgTest { client.create().resource(p).conditional().where(new StringClientParam("name").matches().value("foo")).execute(); assertEquals(1, capt.getAllValues().get(idx).getHeaders(Constants.HEADER_CONTENT_TYPE).length); - assertEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); - assertThat(extractBody(capt, idx), containsString("")); + assertEquals(EncodingEnum.JSON.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); + assertThat(extractBody(capt, idx), containsString("{\"family\":[\"FOOFAMILY\"]}")); assertEquals("http://example.com/fhir/Patient", capt.getAllValues().get(idx).getURI().toString()); assertEquals("http://example.com/fhir/Patient?name=foo", capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_IF_NONE_EXIST).getValue()); assertEquals("POST", capt.getAllValues().get(idx).getRequestLine().getMethod()); @@ -793,7 +792,7 @@ public class GenericClientDstu2Hl7OrgTest { Patient p = new Patient(); p.addName().addFamily("FOOFAMILY"); - client.update().resource(p).conditionalByUrl("Patient?name=foo").execute(); + client.update().resource(p).conditionalByUrl("Patient?name=foo").encodedXml().execute(); assertEquals(1, capt.getAllValues().get(idx).getHeaders(Constants.HEADER_CONTENT_TYPE).length); assertEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); assertThat(extractBody(capt, idx), containsString("")); @@ -801,7 +800,7 @@ public class GenericClientDstu2Hl7OrgTest { assertEquals("http://example.com/fhir/Patient?name=foo", capt.getAllValues().get(idx).getURI().toString()); idx++; - client.update().resource(ourCtx.newXmlParser().encodeResourceToString(p)).conditionalByUrl("Patient?name=foo").execute(); + client.update().resource(ourCtx.newXmlParser().encodeResourceToString(p)).conditionalByUrl("Patient?name=foo").encodedXml().execute(); assertEquals(1, capt.getAllValues().get(idx).getHeaders(Constants.HEADER_CONTENT_TYPE).length); assertEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); assertThat(extractBody(capt, idx), containsString("")); @@ -809,7 +808,7 @@ public class GenericClientDstu2Hl7OrgTest { assertEquals("http://example.com/fhir/Patient?name=foo", capt.getAllValues().get(idx).getURI().toString()); idx++; - client.update().resource(p).conditional().where(new StringClientParam("name").matches().value("foo")).and(new StringClientParam("address").matches().value("AAA|BBB")).execute(); + client.update().resource(p).conditional().where(new StringClientParam("name").matches().value("foo")).and(new StringClientParam("address").matches().value("AAA|BBB")).encodedXml().execute(); assertEquals(1, capt.getAllValues().get(idx).getHeaders(Constants.HEADER_CONTENT_TYPE).length); assertEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); assertThat(extractBody(capt, idx), containsString("")); @@ -818,7 +817,7 @@ public class GenericClientDstu2Hl7OrgTest { idx++; client.update().resource(ourCtx.newXmlParser().encodeResourceToString(p)).conditional().where(new StringClientParam("name").matches().value("foo")) - .and(new StringClientParam("address").matches().value("AAA|BBB")).execute(); + .and(new StringClientParam("address").matches().value("AAA|BBB")).encodedXml().execute(); assertEquals(1, capt.getAllValues().get(idx).getHeaders(Constants.HEADER_CONTENT_TYPE).length); assertEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); assertThat(extractBody(capt, idx), containsString("")); diff --git a/hapi-fhir-structures-hl7org-dstu2/src/test/java/ca/uhn/fhir/rest/client/OperationClientTest.java b/hapi-fhir-structures-hl7org-dstu2/src/test/java/ca/uhn/fhir/rest/client/OperationClientTest.java index dee13ba756d..37f3cf98afa 100644 --- a/hapi-fhir-structures-hl7org-dstu2/src/test/java/ca/uhn/fhir/rest/client/OperationClientTest.java +++ b/hapi-fhir-structures-hl7org-dstu2/src/test/java/ca/uhn/fhir/rest/client/OperationClientTest.java @@ -78,7 +78,7 @@ public class OperationClientTest { String requestBody = IOUtils.toString(((HttpPost) value).getEntity().getContent()); IOUtils.closeQuietly(((HttpPost) value).getEntity().getContent()); ourLog.info(requestBody); - Parameters request = ourCtx.newXmlParser().parseResource(Parameters.class, requestBody); + Parameters request = ourCtx.newJsonParser().parseResource(Parameters.class, requestBody); assertEquals("http://foo/Patient/222/$OP_INSTANCE", value.getURI().toASCIIString()); assertEquals(2, request.getParameter().size()); assertEquals("PARAM1", request.getParameter().get(0).getName()); @@ -115,7 +115,7 @@ public class OperationClientTest { String requestBody = IOUtils.toString(((HttpPost) value).getEntity().getContent()); IOUtils.closeQuietly(((HttpPost) value).getEntity().getContent()); ourLog.info(requestBody); - Parameters request = ourCtx.newXmlParser().parseResource(Parameters.class, requestBody); + Parameters request = ourCtx.newJsonParser().parseResource(Parameters.class, requestBody); assertEquals("http://foo/$OP_SERVER", value.getURI().toASCIIString()); assertEquals(2, request.getParameter().size()); assertEquals("PARAM1", request.getParameter().get(0).getName()); @@ -130,7 +130,7 @@ public class OperationClientTest { requestBody = IOUtils.toString(((HttpPost) value).getEntity().getContent()); IOUtils.closeQuietly(((HttpPost) value).getEntity().getContent()); ourLog.info(requestBody); - request = ourCtx.newXmlParser().parseResource(Parameters.class, requestBody); + request = ourCtx.newJsonParser().parseResource(Parameters.class, requestBody); assertEquals(1, request.getParameter().size()); assertEquals("PARAM2", request.getParameter().get(0).getName()); assertEquals(Boolean.TRUE, ((Patient) request.getParameter().get(0).getResource()).getActive()); @@ -142,7 +142,7 @@ public class OperationClientTest { requestBody = IOUtils.toString(((HttpPost) value).getEntity().getContent()); IOUtils.closeQuietly(((HttpPost) value).getEntity().getContent()); ourLog.info(requestBody); - request = ourCtx.newXmlParser().parseResource(Parameters.class, requestBody); + request = ourCtx.newJsonParser().parseResource(Parameters.class, requestBody); assertEquals(0, request.getParameter().size()); idx++; @@ -175,7 +175,7 @@ public class OperationClientTest { String requestBody = IOUtils.toString(((HttpPost) value).getEntity().getContent()); IOUtils.closeQuietly(((HttpPost) value).getEntity().getContent()); ourLog.info(requestBody); - Parameters request = ourCtx.newXmlParser().parseResource(Parameters.class, requestBody); + Parameters request = ourCtx.newJsonParser().parseResource(Parameters.class, requestBody); assertEquals("http://foo/$OP_SERVER_LIST_PARAM", value.getURI().toASCIIString()); assertEquals(3, request.getParameter().size()); assertEquals("PARAM2", request.getParameter().get(0).getName()); @@ -192,7 +192,7 @@ public class OperationClientTest { requestBody = IOUtils.toString(((HttpPost) value).getEntity().getContent()); IOUtils.closeQuietly(((HttpPost) value).getEntity().getContent()); ourLog.info(requestBody); - request = ourCtx.newXmlParser().parseResource(Parameters.class, requestBody); + request = ourCtx.newJsonParser().parseResource(Parameters.class, requestBody); assertEquals("http://foo/$OP_SERVER_LIST_PARAM", value.getURI().toASCIIString()); assertEquals(2, request.getParameter().size()); assertEquals("PARAM3", request.getParameter().get(0).getName()); @@ -207,7 +207,7 @@ public class OperationClientTest { requestBody = IOUtils.toString(((HttpPost) value).getEntity().getContent()); IOUtils.closeQuietly(((HttpPost) value).getEntity().getContent()); ourLog.info(requestBody); - request = ourCtx.newXmlParser().parseResource(Parameters.class, requestBody); + request = ourCtx.newJsonParser().parseResource(Parameters.class, requestBody); assertEquals("http://foo/$OP_SERVER_LIST_PARAM", value.getURI().toASCIIString()); assertEquals(0, request.getParameter().size()); idx++; @@ -218,7 +218,7 @@ public class OperationClientTest { requestBody = IOUtils.toString(((HttpPost) value).getEntity().getContent()); IOUtils.closeQuietly(((HttpPost) value).getEntity().getContent()); ourLog.info(requestBody); - request = ourCtx.newXmlParser().parseResource(Parameters.class, requestBody); + request = ourCtx.newJsonParser().parseResource(Parameters.class, requestBody); assertEquals("http://foo/$OP_SERVER_LIST_PARAM", value.getURI().toASCIIString()); assertEquals(0, request.getParameter().size()); idx++; @@ -253,7 +253,7 @@ public class OperationClientTest { String requestBody = IOUtils.toString(((HttpPost) value).getEntity().getContent()); IOUtils.closeQuietly(((HttpPost) value).getEntity().getContent()); ourLog.info(requestBody); - Parameters request = ourCtx.newXmlParser().parseResource(Parameters.class, requestBody); + Parameters request = ourCtx.newJsonParser().parseResource(Parameters.class, requestBody); assertEquals("http://foo/Patient/$OP_TYPE", value.getURI().toASCIIString()); assertEquals(2, request.getParameter().size()); assertEquals("PARAM1", request.getParameter().get(0).getName()); diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/JsonParserR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/JsonParserR4Test.java index 0d32b891cdd..36698fd89bd 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/JsonParserR4Test.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/JsonParserR4Test.java @@ -39,6 +39,18 @@ public class JsonParserR4Test { return b; } + @Test + public void testEncodeExtensionOnBinaryData() { + Binary b = new Binary(); + b.getDataElement().addExtension("http://foo", new StringType("AAA")); + + String output = ourCtx.newJsonParser().setSummaryMode(true).encodeResourceToString(b); + assertEquals("{\"resourceType\":\"Binary\",\"meta\":{\"tag\":[{\"system\":\"http://terminology.hl7.org/CodeSystem/v3-ObservationValue\",\"code\":\"SUBSETTED\",\"display\":\"Resource encoded in summary mode\"}]}}", output); + + output = ourCtx.newJsonParser().setDontEncodeElements(Sets.newHashSet("*.id", "*.meta")).encodeResourceToString(b); + assertEquals("{\"resourceType\":\"Binary\",\"_data\":{\"extension\":[{\"url\":\"http://foo\",\"valueString\":\"AAA\"}]}}", output); + } + @Test public void testDontStripVersions() { FhirContext ctx = FhirContext.forR4(); diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/BinaryClientTest.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/BinaryClientTest.java index 2df30ecdfbf..0b3877d1295 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/BinaryClientTest.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/BinaryClientTest.java @@ -1,12 +1,13 @@ package ca.uhn.fhir.rest.client; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; +import static org.hamcrest.CoreMatchers.containsString; +import static org.junit.Assert.*; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.io.ByteArrayInputStream; +import com.google.common.base.Charsets; import org.apache.commons.io.IOUtils; import org.apache.http.HttpResponse; import org.apache.http.ProtocolVersion; @@ -90,13 +91,36 @@ public class BinaryClientTest { } + @Test + public void testCreateWithNoBytes() throws Exception { + Binary res = new Binary(); + res.setContentType("image/png"); + + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(httpResponse); + when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 201, "OK")); + when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML)); + when(httpResponse.getEntity().getContent()).thenReturn(new ByteArrayInputStream(new byte[] {})); + + IClient client = mtCtx.newRestfulClient(IClient.class, "http://foo"); + client.create(res); + + assertEquals(HttpPost.class, capt.getValue().getClass()); + HttpPost post = (HttpPost) capt.getValue(); + assertEquals("http://foo/Binary", post.getURI().toString()); + + assertThat(capt.getValue().getFirstHeader("Content-Type").getValue(), containsString(Constants.CT_FHIR_JSON_NEW)); + assertEquals("{\"resourceType\":\"Binary\",\"contentType\":\"image/png\"}", IOUtils.toString(post.getEntity().getContent(), Charsets.UTF_8)); + + } + private interface IClient extends IBasicClient { @Read(type = Binary.class) - public Binary read(@IdParam IdType theBinary); + Binary read(@IdParam IdType theBinary); @Create(type = Binary.class) - public MethodOutcome create(@ResourceParam Binary theBinary); + MethodOutcome create(@ResourceParam Binary theBinary); } diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/ClientHeadersR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/ClientHeadersR4Test.java index 3f88fdd9f08..af07f2a7cf4 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/ClientHeadersR4Test.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/ClientHeadersR4Test.java @@ -123,7 +123,7 @@ public class ClientHeadersR4Test { assertNotNull(resp); assertEquals(1, ourHeaders.get(Constants.HEADER_CONTENT_TYPE).size()); - assertEquals("application/fhir+xml; charset=UTF-8", ourHeaders.get(Constants.HEADER_CONTENT_TYPE).get(0)); + assertEquals("application/fhir+json; charset=UTF-8", ourHeaders.get(Constants.HEADER_CONTENT_TYPE).get(0)); } @Before diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/ClientMimetypeR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/ClientMimetypeR4Test.java index 2b952551b45..cec8bee613d 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/ClientMimetypeR4Test.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/ClientMimetypeR4Test.java @@ -59,7 +59,7 @@ public class ClientMimetypeR4Test { Patient pt = new Patient(); pt.getText().setDivAsString("A PATIENT"); - MethodOutcome outcome = client.create().resource(pt).execute(); + MethodOutcome outcome = client.create().resource(pt).encodedXml().execute(); assertEquals("
FINAL VALUE
", ((Patient) outcome.getResource()).getText().getDivAsString()); assertEquals("http://example.com/fhir/Patient", capt.getAllValues().get(0).getURI().toASCIIString()); @@ -79,7 +79,7 @@ public class ClientMimetypeR4Test { Patient pt = new Patient(); pt.getText().setDivAsString("A PATIENT"); - MethodOutcome outcome = client.create().resource(pt).execute(); + MethodOutcome outcome = client.create().resource(pt).encodedXml().execute(); assertEquals("
FINAL VALUE
", ((Patient) outcome.getResource()).getText().getDivAsString()); assertEquals("http://example.com/fhir/Patient", capt.getAllValues().get(0).getURI().toASCIIString()); diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/ClientR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/ClientR4Test.java index f0c4e42e79e..13a9932ff16 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/ClientR4Test.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/ClientR4Test.java @@ -157,9 +157,9 @@ public class ClientR4Test { assertEquals(HttpPost.class, capt.getValue().getClass()); HttpPost post = (HttpPost) capt.getValue(); - assertThat(IOUtils.toString(post.getEntity().getContent(), Charsets.UTF_8), StringContains.containsString(") output.getParameterFirstRep().getValue()).getValueAsString()); } @@ -845,9 +845,9 @@ public class GenericClientR4Test { assertEquals("http://example.com/fhir/Patient/123/_history/456/$opname", capt.getAllValues().get(0).getURI().toASCIIString()); validateUserAgent(capt); - assertEquals("application/fhir+xml;charset=utf-8", capt.getAllValues().get(0).getHeaders("Content-Type")[0].getValue().toLowerCase().replace(" ", "")); - assertEquals(Constants.HEADER_ACCEPT_VALUE_XML_NON_LEGACY, capt.getAllValues().get(0).getHeaders("Accept")[0].getValue()); - Parameters output = ourCtx.newXmlParser().parseResource(Parameters.class, extractBodyAsString(capt)); + assertEquals("application/fhir+json;charset=utf-8", capt.getAllValues().get(0).getHeaders("Content-Type")[0].getValue().toLowerCase().replace(" ", "")); + assertEquals(Constants.HEADER_ACCEPT_VALUE_JSON_NON_LEGACY, capt.getAllValues().get(0).getHeaders("Accept")[0].getValue()); + Parameters output = ourCtx.newJsonParser().parseResource(Parameters.class, extractBodyAsString(capt)); assertEquals("name", output.getParameterFirstRep().getName()); assertEquals("true", ((IPrimitiveType) output.getParameterFirstRep().getValue()).getValueAsString()); } @@ -889,9 +889,9 @@ public class GenericClientR4Test { assertEquals("http://example.com/fhir/$opname", capt.getAllValues().get(0).getURI().toASCIIString()); validateUserAgent(capt); - assertEquals("application/fhir+xml;charset=utf-8", capt.getAllValues().get(0).getHeaders("Content-Type")[0].getValue().toLowerCase().replace(" ", "")); - assertEquals(Constants.HEADER_ACCEPT_VALUE_XML_NON_LEGACY, capt.getAllValues().get(0).getHeaders("Accept")[0].getValue()); - Parameters output = ourCtx.newXmlParser().parseResource(Parameters.class, extractBodyAsString(capt)); + assertEquals("application/fhir+json;charset=utf-8", capt.getAllValues().get(0).getHeaders("Content-Type")[0].getValue().toLowerCase().replace(" ", "")); + assertEquals(Constants.HEADER_ACCEPT_VALUE_JSON_NON_LEGACY, capt.getAllValues().get(0).getHeaders("Accept")[0].getValue()); + Parameters output = ourCtx.newJsonParser().parseResource(Parameters.class, extractBodyAsString(capt)); assertEquals("name", output.getParameterFirstRep().getName()); assertEquals("true", ((IPrimitiveType) output.getParameterFirstRep().getValue()).getValueAsString()); } @@ -1012,9 +1012,9 @@ public class GenericClientR4Test { assertEquals("http://example.com/fhir/Patient/$opname", capt.getAllValues().get(0).getURI().toASCIIString()); validateUserAgent(capt); - assertEquals("application/fhir+xml;charset=utf-8", capt.getAllValues().get(0).getHeaders("Content-Type")[0].getValue().toLowerCase().replace(" ", "")); - assertEquals(Constants.HEADER_ACCEPT_VALUE_XML_NON_LEGACY, capt.getAllValues().get(0).getHeaders("Accept")[0].getValue()); - Parameters output = ourCtx.newXmlParser().parseResource(Parameters.class, extractBodyAsString(capt)); + assertEquals("application/fhir+json;charset=utf-8", capt.getAllValues().get(0).getHeaders("Content-Type")[0].getValue().toLowerCase().replace(" ", "")); + assertEquals(Constants.HEADER_ACCEPT_VALUE_JSON_NON_LEGACY, capt.getAllValues().get(0).getHeaders("Accept")[0].getValue()); + Parameters output = ourCtx.newJsonParser().parseResource(Parameters.class, extractBodyAsString(capt)); assertEquals("name", output.getParameterFirstRep().getName()); assertEquals("true", ((IPrimitiveType) output.getParameterFirstRep().getValue()).getValueAsString()); } @@ -1342,6 +1342,7 @@ public class GenericClientR4Test { .update() .resource(bundle) .prefer(PreferReturnEnum.REPRESENTATION) + .encodedXml() .execute(); HttpPut httpRequest = (HttpPut) capt.getValue(); @@ -2109,10 +2110,10 @@ public class GenericClientR4Test { assertEquals("http://example.com/fhir/Patient/111", capt.getAllValues().get(0).getURI().toASCIIString()); validateUserAgent(capt); - assertEquals("application/fhir+xml;charset=utf-8", capt.getAllValues().get(0).getHeaders("Content-Type")[0].getValue().toLowerCase().replace(" ", "")); - assertEquals(Constants.HEADER_ACCEPT_VALUE_XML_NON_LEGACY, capt.getAllValues().get(0).getHeaders("Accept")[0].getValue()); + assertEquals("application/fhir+json;charset=utf-8", capt.getAllValues().get(0).getHeaders("Content-Type")[0].getValue().toLowerCase().replace(" ", "")); + assertEquals(Constants.HEADER_ACCEPT_VALUE_JSON_NON_LEGACY, capt.getAllValues().get(0).getHeaders("Accept")[0].getValue()); String body = extractBodyAsString(capt); - assertThat(body, containsString("")); + assertThat(body, containsString("\"id\":\"111\"")); } @Test diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/GenericClientTest.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/GenericClientTest.java index 24acacae09b..91a5024f212 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/GenericClientTest.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/GenericClientTest.java @@ -324,7 +324,7 @@ public class GenericClientTest { assertEquals("http://example.com/fhir/Patient", capt.getValue().getURI().toString()); assertEquals("POST", capt.getValue().getMethod()); assertEquals(1, capt.getAllValues().get(count).getHeaders(Constants.HEADER_CONTENT_TYPE).length); - assertEquals(EncodingEnum.XML.getResourceContentTypeNonLegacy() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(count).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); + assertEquals(EncodingEnum.JSON.getResourceContentTypeNonLegacy() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(count).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); assertEquals("myHeaderValue", capt.getValue().getFirstHeader("myHeaderName").getValue()); count++; @@ -335,7 +335,7 @@ public class GenericClientTest { client.create().resource(p1).execute(); assertEquals("http://example.com/fhir/Patient", capt.getAllValues().get(1).getURI().toString()); assertEquals(1, capt.getAllValues().get(count).getHeaders(Constants.HEADER_CONTENT_TYPE).length); - assertEquals(EncodingEnum.XML.getResourceContentTypeNonLegacy() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(count).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); + assertEquals(EncodingEnum.JSON.getResourceContentTypeNonLegacy() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(count).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); count++; String resourceText = " "; @@ -583,7 +583,7 @@ public class GenericClientTest { assertEquals("http://example.com/fhir/MessageHeader/$process-message", capt.getAllValues().get(count).getURI().toString()); String requestContent = IOUtils.toString(((HttpPost) capt.getAllValues().get(count)).getEntity().getContent(), Charsets.UTF_8); - assertThat(requestContent, startsWith("")); + assertThat(requestContent, containsString("{\"resourceType\":\"Parameters\"")); count++; } @@ -1565,7 +1565,7 @@ public class GenericClientTest { } @Test - public void testTransaction() throws Exception { + public void testTransactionJson() throws Exception { Bundle input = createTransactionBundleInput(); Bundle output = createTransactionBundleOutput(); @@ -1585,7 +1585,7 @@ public class GenericClientTest { assertEquals("http://example.com/fhir", capt.getValue().getURI().toString()); assertEquals(input.getEntry().get(0).getResource().getId(), response.getEntry().get(0).getResource().getId()); - assertEquals(EncodingEnum.XML.getResourceContentTypeNonLegacy() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(0).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); + assertEquals(EncodingEnum.JSON.getResourceContentTypeNonLegacy() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(0).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); } @@ -1606,6 +1606,7 @@ public class GenericClientTest { Bundle response = client.transaction() .withBundle(input) + .encodedXml() .execute(); assertEquals("http://example.com/fhir", capt.getValue().getURI().toString()); @@ -1646,7 +1647,7 @@ public class GenericClientTest { assertEquals(1, capt.getAllValues().size()); assertEquals(1, capt.getAllValues().get(count).getHeaders(Constants.HEADER_CONTENT_TYPE).length); - assertEquals(EncodingEnum.XML.getResourceContentTypeNonLegacy() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(count).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); + assertEquals(EncodingEnum.JSON.getResourceContentTypeNonLegacy() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(count).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); count++; MethodOutcome outcome = client.update().resource(p1).execute(); diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/NonGenericClientR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/NonGenericClientR4Test.java index e28f7e6b54d..94f67764db0 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/NonGenericClientR4Test.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/NonGenericClientR4Test.java @@ -144,7 +144,7 @@ public class NonGenericClientR4Test { assertEquals("
OK!
", resp); assertEquals("http://example.com/fhir/$validate", capt.getAllValues().get(idx).getURI().toString()); String request = extractBodyAsString(capt,idx); - assertEquals("", request); + assertEquals("{\"resourceType\":\"Parameters\",\"parameter\":[{\"name\":\"resource\",\"resource\":{\"resourceType\":\"Patient\",\"name\":[{\"family\":\"FAM\"}]}}]}", request); idx = 1; outcome = client.validate(patient, ValidationModeEnum.CREATE, "http://foo"); @@ -152,7 +152,7 @@ public class NonGenericClientR4Test { assertEquals("
OK!
", resp); assertEquals("http://example.com/fhir/$validate", capt.getAllValues().get(idx).getURI().toString()); request = extractBodyAsString(capt,idx); - assertEquals("", request); + assertEquals("{\"resourceType\":\"Parameters\",\"parameter\":[{\"name\":\"resource\",\"resource\":{\"resourceType\":\"Patient\",\"name\":[{\"family\":\"FAM\"}]}},{\"name\":\"mode\",\"valueString\":\"create\"},{\"name\":\"profile\",\"valueString\":\"http://foo\"}]}", request); } diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/OperationClientR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/OperationClientR4Test.java index 6f658fe5ec6..2528eb30186 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/OperationClientR4Test.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/OperationClientR4Test.java @@ -97,7 +97,7 @@ public class OperationClientR4Test { String requestBody = IOUtils.toString(value.getEntity().getContent(), Charsets.UTF_8); IOUtils.closeQuietly(value.getEntity().getContent()); ourLog.info(requestBody); - Parameters request = ourCtx.newXmlParser().parseResource(Parameters.class, requestBody); + Parameters request = ourCtx.newJsonParser().parseResource(Parameters.class, requestBody); assertEquals("http://foo/$nonrepeating", value.getURI().toASCIIString()); assertEquals(2, request.getParameter().size()); assertEquals("valstr", request.getParameter().get(0).getName()); @@ -165,7 +165,7 @@ public class OperationClientR4Test { String requestBody = IOUtils.toString(value.getEntity().getContent(), Charsets.UTF_8); IOUtils.closeQuietly(value.getEntity().getContent()); ourLog.info(requestBody); - Parameters request = ourCtx.newXmlParser().parseResource(Parameters.class, requestBody); + Parameters request = ourCtx.newJsonParser().parseResource(Parameters.class, requestBody); assertEquals("http://foo/$nonrepeating", value.getURI().toASCIIString()); assertEquals(2, request.getParameter().size()); assertEquals("valstr", request.getParameter().get(0).getName()); diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/TransactionClientTest.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/TransactionClientTest.java index 312f8dc3359..d0c1529a5a3 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/TransactionClientTest.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/TransactionClientTest.java @@ -80,7 +80,7 @@ public class TransactionClientTest { HttpPost post = (HttpPost) capt.getValue(); assertEquals("http://foo", post.getURI().toString()); - Bundle bundle = ctx.newXmlParser().parseResource(Bundle.class, new InputStreamReader(post.getEntity().getContent())); + Bundle bundle = ctx.newJsonParser().parseResource(Bundle.class, new InputStreamReader(post.getEntity().getContent())); ourLog.info(ctx.newXmlParser().setPrettyPrint(true).encodeResourceToString(bundle)); assertEquals(2, bundle.getEntry().size()); @@ -119,7 +119,7 @@ public class TransactionClientTest { HttpPost post = (HttpPost) capt.getValue(); assertEquals("http://foo", post.getURI().toString()); - Bundle bundle = ctx.newXmlParser().parseResource(Bundle.class, new InputStreamReader(post.getEntity().getContent())); + Bundle bundle = ctx.newJsonParser().parseResource(Bundle.class, new InputStreamReader(post.getEntity().getContent())); ourLog.info(ctx.newXmlParser().setPrettyPrint(true).encodeResourceToString(bundle)); assertEquals(2, bundle.getEntry().size()); diff --git a/hapi-tinder-plugin/src/main/resources/vm/resource.vm b/hapi-tinder-plugin/src/main/resources/vm/resource.vm index f224e7cd5d5..a0baced377a 100644 --- a/hapi-tinder-plugin/src/main/resources/vm/resource.vm +++ b/hapi-tinder-plugin/src/main/resources/vm/resource.vm @@ -120,6 +120,12 @@ public class ${className} extends ca.uhn.fhir.model.${version}.resource.BaseReso return this; } + @Override + public boolean hasData() { + return getContent() != null; + } + + #end #if ( ${className} == "Bundle" ) /** diff --git a/src/changes/changes.xml b/src/changes/changes.xml index da938846fcf..2147555efc7 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -45,6 +45,13 @@ should be an easy fix to existing code. ]]> + + Breaking Change: + The HAPI FHIR REST client and server will now default to using JSON encoding instead of XML when + the user has not explicitly configured a preference. + ]]> + A new interceptor called ConsentInterceptor]]> has been added. This interceptor allows @@ -111,10 +118,6 @@ can delete them. The boolean return value of the hook indicates whether the server should try checking for conflicts again (true means try again). - - The default RestfulServer encoding has been changed from XML to JSON in order to - reflect the most common usage. - The HAPI FHIR unit test suite has been refactored to no longer rely on PortUtil to assign a free port. This should theoretically result in fewer failed builds resulting from @@ -332,6 +335,7 @@ a narrative on an untitled DiagnosticReport were fixed. Thanks to GitHub user @navyflower for reporting! +