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 28c840a0940..c89e71d4baf 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
@@ -9,9 +9,9 @@ package ca.uhn.fhir.parser;
* 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.
@@ -27,6 +27,7 @@ import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.util.UrlUtil;
+import com.google.common.base.Charsets;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.*;
@@ -623,6 +624,16 @@ public abstract class BaseParser implements IParser {
return mySuppressNarratives;
}
+ @Override
+ public IBaseResource parseResource(InputStream theInputStream) throws DataFormatException {
+ return parseResource(new InputStreamReader(theInputStream, Charsets.UTF_8));
+ }
+
+ @Override
+ public T parseResource(Class theResourceType, InputStream theInputStream) throws DataFormatException {
+ return parseResource(theResourceType, new InputStreamReader(theInputStream, Charsets.UTF_8));
+ }
+
@Override
public T parseResource(Class theResourceType, Reader theReader) throws DataFormatException {
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 b10b6e23ea5..0bde9217995 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
@@ -19,14 +19,23 @@ package ca.uhn.fhir.parser;
* limitations under the License.
* #L%
*/
-import java.io.*;
-import java.util.*;
-import org.hl7.fhir.instance.model.api.*;
-
-import ca.uhn.fhir.context.*;
+import ca.uhn.fhir.context.ConfigurationException;
+import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.context.ParserOptions;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.rest.api.EncodingEnum;
+import org.hl7.fhir.instance.model.api.IAnyResource;
+import org.hl7.fhir.instance.model.api.IBaseResource;
+import org.hl7.fhir.instance.model.api.IIdType;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.io.Writer;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
/**
* A parser, which can be used to convert between HAPI FHIR model/structure objects, and their respective String wire
@@ -127,6 +136,20 @@ public interface IParser {
*/
T parseResource(Class theResourceType, Reader theReader) throws DataFormatException;
+ /**
+ * Parses a resource
+ *
+ * @param theResourceType
+ * The resource type to use. This can be used to explicitly specify a class which extends a built-in type
+ * (e.g. a custom type extending the default Patient class)
+ * @param theInputStream
+ * The InputStream to parse input from, with an implied charset of UTF-8. Note that the InputStream will not be closed by the parser upon completion.
+ * @return A parsed resource
+ * @throws DataFormatException
+ * If the resource can not be parsed because the data is not recognized or invalid for any reason
+ */
+ T parseResource(Class theResourceType, InputStream theInputStream) throws DataFormatException;
+
/**
* Parses a resource
*
@@ -153,6 +176,19 @@ public interface IParser {
*/
IBaseResource parseResource(Reader theReader) throws ConfigurationException, DataFormatException;
+ /**
+ * Parses a resource
+ *
+ * @param theInputStream
+ * The InputStream to parse input from (charset is assumed to be UTF-8).
+ * Note that the stream will not be closed by the parser upon completion.
+ * @return A parsed resource. Note that the returned object will be an instance of {@link IResource} or
+ * {@link IAnyResource} depending on the specific FhirContext which created this parser.
+ * @throws DataFormatException
+ * If the resource can not be parsed because the data is not recognized or invalid for any reason
+ */
+ IBaseResource parseResource(InputStream theInputStream) throws ConfigurationException, DataFormatException;
+
/**
* Parses a resource
*
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/Constants.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/Constants.java
index 4fc0ab5e5a4..deaed08c063 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/Constants.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/Constants.java
@@ -42,8 +42,14 @@ public class Constants {
*/
public static final Set CORS_ALLWED_METHODS;
public static final String CT_FHIR_JSON = "application/json+fhir";
+ /**
+ * The FHIR MimeType for JSON encoding in FHIR DSTU3+
+ */
public static final String CT_FHIR_JSON_NEW = "application/fhir+json";
public static final String CT_FHIR_XML = "application/xml+fhir";
+ /**
+ * The FHIR MimeType for XML encoding in FHIR DSTU3+
+ */
public static final String CT_FHIR_XML_NEW = "application/fhir+xml";
public static final String CT_HTML = "text/html";
public static final String CT_HTML_WITH_UTF8 = "text/html" + CHARSET_UTF8_CTSUFFIX;
@@ -86,6 +92,7 @@ public class Constants {
public static final String HEADER_CONTENT_LOCATION = "Content-Location";
public static final String HEADER_CONTENT_LOCATION_LC = HEADER_CONTENT_LOCATION.toLowerCase();
public static final String HEADER_CONTENT_TYPE = "Content-Type";
+ public static final String HEADER_CONTENT_TYPE_LC = HEADER_CONTENT_TYPE.toLowerCase();
public static final String HEADER_COOKIE = "Cookie";
public static final String HEADER_CORS_ALLOW_METHODS = "Access-Control-Allow-Methods";
public static final String HEADER_CORS_ALLOW_ORIGIN = "Access-Control-Allow-Origin";
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/MethodOutcome.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/MethodOutcome.java
index 68ffee11480..c82e781f5bf 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/MethodOutcome.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/MethodOutcome.java
@@ -9,9 +9,9 @@ package ca.uhn.fhir.rest.api;
* 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.
@@ -20,11 +20,13 @@ package ca.uhn.fhir.rest.api;
* #L%
*/
+import ca.uhn.fhir.util.CoverageIgnore;
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
-import ca.uhn.fhir.util.CoverageIgnore;
+import java.util.List;
+import java.util.Map;
public class MethodOutcome {
@@ -32,6 +34,7 @@ public class MethodOutcome {
private IIdType myId;
private IBaseOperationOutcome myOperationOutcome;
private IBaseResource myResource;
+ private Map> myResponseHeaders;
/**
* Constructor
@@ -42,13 +45,10 @@ public class MethodOutcome {
/**
* Constructor
- *
- * @param theId
- * The ID of the created/updated resource
- *
- * @param theCreated
- * If not null, indicates whether the resource was created (as opposed to being updated). This is generally not needed, since the server can assume based on the method being called
- * whether the result was a creation or an update. However, it can be useful if you are implementing an update method that does a create if the ID doesn't already exist.
+ *
+ * @param theId The ID of the created/updated resource
+ * @param theCreated If not null, indicates whether the resource was created (as opposed to being updated). This is generally not needed, since the server can assume based on the method being called
+ * whether the result was a creation or an update. However, it can be useful if you are implementing an update method that does a create if the ID doesn't already exist.
*/
@CoverageIgnore
public MethodOutcome(IIdType theId, Boolean theCreated) {
@@ -58,12 +58,9 @@ public class MethodOutcome {
/**
* Constructor
- *
- * @param theId
- * The ID of the created/updated resource
- *
- * @param theBaseOperationOutcome
- * The operation outcome to return with the response (or null for none)
+ *
+ * @param theId The ID of the created/updated resource
+ * @param theBaseOperationOutcome The operation outcome to return with the response (or null for none)
*/
public MethodOutcome(IIdType theId, IBaseOperationOutcome theBaseOperationOutcome) {
myId = theId;
@@ -72,16 +69,11 @@ public class MethodOutcome {
/**
* Constructor
- *
- * @param theId
- * The ID of the created/updated resource
- *
- * @param theBaseOperationOutcome
- * The operation outcome to return with the response (or null for none)
- *
- * @param theCreated
- * If not null, indicates whether the resource was created (as opposed to being updated). This is generally not needed, since the server can assume based on the method being called
- * whether the result was a creation or an update. However, it can be useful if you are implementing an update method that does a create if the ID doesn't already exist.
+ *
+ * @param theId The ID of the created/updated resource
+ * @param theBaseOperationOutcome The operation outcome to return with the response (or null for none)
+ * @param theCreated If not null, indicates whether the resource was created (as opposed to being updated). This is generally not needed, since the server can assume based on the method being called
+ * whether the result was a creation or an update. However, it can be useful if you are implementing an update method that does a create if the ID doesn't already exist.
*/
public MethodOutcome(IIdType theId, IBaseOperationOutcome theBaseOperationOutcome, Boolean theCreated) {
myId = theId;
@@ -91,9 +83,8 @@ public class MethodOutcome {
/**
* Constructor
- *
- * @param theId
- * The ID of the created/updated resource
+ *
+ * @param theId The ID of the created/updated resource
*/
public MethodOutcome(IIdType theId) {
myId = theId;
@@ -101,9 +92,8 @@ public class MethodOutcome {
/**
* Constructor
- *
- * @param theOperationOutcome
- * The operation outcome resource to return
+ *
+ * @param theOperationOutcome The operation outcome resource to return
*/
public MethodOutcome(IBaseOperationOutcome theOperationOutcome) {
myOperationOutcome = theOperationOutcome;
@@ -117,19 +107,54 @@ public class MethodOutcome {
return myCreated;
}
+ /**
+ * If not null, indicates whether the resource was created (as opposed to being updated). This is generally not needed, since the server can assume based on the method being called whether the
+ * result was a creation or an update. However, it can be useful if you are implementing an update method that does a create if the ID doesn't already exist.
+ *
+ * Users of HAPI should only interact with this method in Server applications
+ *
+ *
+ * @param theCreated If not null, indicates whether the resource was created (as opposed to being updated). This is generally not needed, since the server can assume based on the method being called
+ * whether the result was a creation or an update. However, it can be useful if you are implementing an update method that does a create if the ID doesn't already exist.
+ * @return Returns a reference to this
for easy method chaining
+ */
+ public MethodOutcome setCreated(Boolean theCreated) {
+ myCreated = theCreated;
+ return this;
+ }
+
public IIdType getId() {
return myId;
}
+ /**
+ * @param theId The ID of the created/updated resource
+ * @return Returns a reference to this
for easy method chaining
+ */
+ public MethodOutcome setId(IIdType theId) {
+ myId = theId;
+ return this;
+ }
+
/**
* Returns the {@link IBaseOperationOutcome} resource to return to the client or null
if none.
- *
+ *
* @return This method will return null, unlike many methods in the API.
*/
public IBaseOperationOutcome getOperationOutcome() {
return myOperationOutcome;
}
+ /**
+ * Sets the {@link IBaseOperationOutcome} resource to return to the client. Set to null
(which is the default) if none.
+ *
+ * @return Returns a reference to this
for easy method chaining
+ */
+ public MethodOutcome setOperationOutcome(IBaseOperationOutcome theBaseOperationOutcome) {
+ myOperationOutcome = theBaseOperationOutcome;
+ return this;
+ }
+
/**
* From a client response: If the method returned an actual resource body (e.g. a create/update with
* "Prefer: return=representation") this field will be populated with the
@@ -139,50 +164,15 @@ public class MethodOutcome {
return myResource;
}
- /**
- * If not null, indicates whether the resource was created (as opposed to being updated). This is generally not needed, since the server can assume based on the method being called whether the
- * result was a creation or an update. However, it can be useful if you are implementing an update method that does a create if the ID doesn't already exist.
- *
- * Users of HAPI should only interact with this method in Server applications
- *
- *
- * @param theCreated
- * If not null, indicates whether the resource was created (as opposed to being updated). This is generally not needed, since the server can assume based on the method being called
- * whether the result was a creation or an update. However, it can be useful if you are implementing an update method that does a create if the ID doesn't already exist.
- * @return Returns a reference to this
for easy method chaining
- */
- public MethodOutcome setCreated(Boolean theCreated) {
- myCreated = theCreated;
- return this;
- }
-
- /**
- * @param theId
- * The ID of the created/updated resource
- * @return Returns a reference to this
for easy method chaining
- */
- public MethodOutcome setId(IIdType theId) {
- myId = theId;
- return this;
- }
-
- /**
- * Sets the {@link IBaseOperationOutcome} resource to return to the client. Set to null
(which is the default) if none.
- * @return Returns a reference to this
for easy method chaining
- */
- public MethodOutcome setOperationOutcome(IBaseOperationOutcome theBaseOperationOutcome) {
- myOperationOutcome = theBaseOperationOutcome;
- return this;
- }
-
/**
* In a server response: This field may be populated in server code with the final resource for operations
* where a resource body is being created/updated. E.g. for an update method, this field could be populated with
- * the resource after the update is applied, with the new version ID, lastUpdate time, etc.
+ * the resource after the update is applied, with the new version ID, lastUpdate time, etc.
*
* This field is optional, but if it is populated the server will return the resource body if requested to
* do so via the HTTP Prefer header.
- *
+ *
+ *
* @return Returns a reference to this
for easy method chaining
*/
public MethodOutcome setResource(IBaseResource theResource) {
@@ -190,4 +180,23 @@ public class MethodOutcome {
return this;
}
+ /**
+ * Gets the headers for the HTTP response
+ */
+ public Map> getResponseHeaders() {
+ return myResponseHeaders;
+ }
+
+ /**
+ * Sets the headers for the HTTP response
+ */
+ public void setResponseHeaders(Map> theResponseHeaders) {
+ myResponseHeaders = theResponseHeaders;
+ }
+
+ public void setCreatedUsingStatusCode(int theResponseStatusCode) {
+ if (theResponseStatusCode == Constants.STATUS_HTTP_201_CREATED) {
+ setCreated(true);
+ }
+ }
}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/api/IHttpRequest.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/api/IHttpRequest.java
index d7780caa397..c0a68a06a45 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/api/IHttpRequest.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/api/IHttpRequest.java
@@ -9,9 +9,9 @@ package ca.uhn.fhir.rest.client.api;
* 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.
@@ -28,16 +28,18 @@ import java.util.Map;
* Http Request. Allows addition of headers and execution of the request.
*/
public interface IHttpRequest {
-
+
/**
* Add a header to the request
- * @param theName the header name
+ *
+ * @param theName the header name
* @param theValue the header value
*/
void addHeader(String theName, String theValue);
/**
* Execute the request
+ *
* @return the response
*/
IHttpResponse execute() throws IOException;
@@ -50,7 +52,8 @@ public interface IHttpRequest {
/**
* Return the request body as a string.
- * If this is not supported by the underlying technology, null is returned
+ * If this is not supported by the underlying technology, null is returned
+ *
* @return a string representation of the request or null if not supported or empty.
*/
String getRequestBodyFromStream() throws IOException;
@@ -59,10 +62,16 @@ public interface IHttpRequest {
* Return the request URI, or null
*/
String getUri();
-
+
/**
* Return the HTTP verb (e.g. "GET")
*/
String getHttpVerbName();
-
+
+ /**
+ * Remove any headers matching the given name
+ *
+ * @param theHeaderName The header name, e.g. "Accept" (must not be null or blank)
+ */
+ void removeHeaders(String theHeaderName);
}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/api/IHttpResponse.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/api/IHttpResponse.java
index 9c5a6f395e6..4e84c9f0617 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/api/IHttpResponse.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/api/IHttpResponse.java
@@ -70,7 +70,7 @@ public interface IHttpResponse {
void close();
/**
- * Returna reader for the response entity
+ * Returns a reader for the response entity
*/
Reader createReader() throws IOException;
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/exceptions/NonFhirResponseException.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/exceptions/NonFhirResponseException.java
index 5cf326bb9f6..b8d193ef388 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/exceptions/NonFhirResponseException.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/exceptions/NonFhirResponseException.java
@@ -9,9 +9,9 @@ package ca.uhn.fhir.rest.client.exceptions;
* 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.
@@ -19,15 +19,18 @@ package ca.uhn.fhir.rest.client.exceptions;
* limitations under the License.
* #L%
*/
-import static org.apache.commons.lang3.StringUtils.isBlank;
-
-import java.io.IOException;
-import java.io.Reader;
-
-import org.apache.commons.io.IOUtils;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import ca.uhn.fhir.util.CoverageIgnore;
+import com.google.common.base.Charsets;
+import org.apache.commons.io.IOUtils;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+
+import static org.apache.commons.lang3.StringUtils.isBlank;
@CoverageIgnore
public class NonFhirResponseException extends BaseServerResponseException {
@@ -36,24 +39,30 @@ public class NonFhirResponseException extends BaseServerResponseException {
/**
* Constructor
- *
- * @param theMessage
- * The message
- * @param theResponseText
- * @param theStatusCode
- * @param theResponseReader
- * @param theContentType
+ *
+ * @param theMessage The message
+ * @param theStatusCode The HTTP status code
*/
NonFhirResponseException(int theStatusCode, String theMessage) {
super(theStatusCode, theMessage);
}
+ public static NonFhirResponseException newInstance(int theStatusCode, String theContentType, InputStream theInputStream) {
+ return newInstance(theStatusCode, theContentType, new InputStreamReader(theInputStream, Charsets.UTF_8));
+ }
+
public static NonFhirResponseException newInstance(int theStatusCode, String theContentType, Reader theReader) {
String responseBody = "";
try {
responseBody = IOUtils.toString(theReader);
} catch (IOException e) {
- IOUtils.closeQuietly(theReader);
+ // ignore
+ } finally {
+ try {
+ theReader.close();
+ } catch (IOException theE) {
+ // ignore
+ }
}
NonFhirResponseException retVal;
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IClientExecutable.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IClientExecutable.java
index 331575d2605..3222d325dcd 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IClientExecutable.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IClientExecutable.java
@@ -2,6 +2,7 @@ package ca.uhn.fhir.rest.gclient;
import ca.uhn.fhir.rest.api.CacheControlDirective;
import ca.uhn.fhir.rest.api.EncodingEnum;
+import ca.uhn.fhir.rest.api.RequestFormatParamStyleEnum;
import ca.uhn.fhir.rest.api.SummaryEnum;
import org.hl7.fhir.instance.model.api.IBaseResource;
@@ -16,9 +17,9 @@ import java.util.List;
* 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.
@@ -28,12 +29,12 @@ import java.util.List;
*/
-public interface IClientExecutable, Y> {
+public interface IClientExecutable, Y> {
/**
* If set to true, the client will log the request and response to the SLF4J logger. This can be useful for
* debugging, but is generally not desirable in a production situation.
- *
+ *
* @deprecated Use the client logging interceptor to log requests and responses instead. See here for more information.
*/
@Deprecated
@@ -46,16 +47,45 @@ public interface IClientExecutable, Y> {
T cacheControl(CacheControlDirective theCacheControlDirective);
/**
- * Request that the server return subsetted resources, containing only the elements specified in the given parameters.
+ * Request that the server return subsetted resources, containing only the elements specified in the given parameters.
* For example: subsetElements("name", "identifier")
requests that the server only return
- * the "name" and "identifier" fields in the returned resource, and omit any others.
+ * the "name" and "identifier" fields in the returned resource, and omit any others.
*/
T elementsSubset(String... theElements);
+ /**
+ * Request that the server respond with JSON via the Accept header and possibly also the
+ * _format
parameter if {@link ca.uhn.fhir.rest.client.api.IRestfulClient#setFormatParamStyle(RequestFormatParamStyleEnum) configured to do so}.
+ *
+ * This method will have no effect if {@link #accept(String) a custom Accept header} is specified.
+ *
+ *
+ * @see #accept(String)
+ */
T encoded(EncodingEnum theEncoding);
+ /**
+ * Request that the server respond with JSON via the Accept header and possibly also the
+ * _format
parameter if {@link ca.uhn.fhir.rest.client.api.IRestfulClient#setFormatParamStyle(RequestFormatParamStyleEnum) configured to do so}.
+ *
+ * This method will have no effect if {@link #accept(String) a custom Accept header} is specified.
+ *
+ *
+ * @see #accept(String)
+ * @see #encoded(EncodingEnum)
+ */
T encodedJson();
+ /**
+ * Request that the server respond with JSON via the Accept header and possibly also the
+ * _format
parameter if {@link ca.uhn.fhir.rest.client.api.IRestfulClient#setFormatParamStyle(RequestFormatParamStyleEnum) configured to do so}.
+ *
+ * This method will have no effect if {@link #accept(String) a custom Accept header} is specified.
+ *
+ *
+ * @see #accept(String)
+ * @see #encoded(EncodingEnum)
+ */
T encodedXml();
/**
@@ -84,11 +114,33 @@ public interface IClientExecutable, Y> {
*/
T preferResponseTypes(List> theTypes);
+ /**
+ * Request pretty-printed response via the _pretty
parameter
+ */
T prettyPrint();
/**
- * Request that the server modify the response using the _summary
param
+ * Request that the server modify the response using the _summary
param
*/
T summaryMode(SummaryEnum theSummary);
+ /**
+ * Specifies a custom Accept
header that should be supplied with the
+ * request.
+ *
+ * Note that this method overrides any encoding preferences specified with
+ * {@link #encodedJson()} or {@link #encodedXml()}. It is generally easier to
+ * just use those methods if you simply want to request a specific FHIR encoding.
+ *
+ *
+ * @param theHeaderValue The header value, e.g. "application/fhir+json". Constants such
+ * as {@link ca.uhn.fhir.rest.api.Constants#CT_FHIR_XML_NEW} and
+ * {@link ca.uhn.fhir.rest.api.Constants#CT_FHIR_JSON_NEW} may
+ * be useful. If set to null
or an empty string, the
+ * default Accept header will be used.
+ * @see #encoded(EncodingEnum)
+ * @see #encodedJson()
+ * @see #encodedXml()
+ */
+ T accept(String theHeaderValue);
}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IOperationUntypedWithInput.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IOperationUntypedWithInput.java
index ba922755475..ea41945171b 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IOperationUntypedWithInput.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IOperationUntypedWithInput.java
@@ -20,6 +20,7 @@ package ca.uhn.fhir.rest.gclient;
* #L%
*/
+import ca.uhn.fhir.rest.api.MethodOutcome;
import org.hl7.fhir.instance.model.api.IBaseResource;
public interface IOperationUntypedWithInput extends IClientExecutable, T> {
@@ -43,4 +44,9 @@ public interface IOperationUntypedWithInput extends IClientExecutable IOperationUntypedWithInput returnResourceType(Class theReturnType);
+ /**
+ * Request that the method chain returns a {@link MethodOutcome} object. This object
+ * will contain details
+ */
+ IOperationUntypedWithInput returnMethodOutcome();
}
diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/apache/ApacheHttpRequest.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/apache/ApacheHttpRequest.java
index f968eb8901b..895c2a526fa 100644
--- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/apache/ApacheHttpRequest.java
+++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/apache/ApacheHttpRequest.java
@@ -20,12 +20,11 @@ package ca.uhn.fhir.rest.client.apache;
* #L%
*/
-import java.io.IOException;
-import java.nio.charset.Charset;
-import java.util.*;
-
+import ca.uhn.fhir.rest.client.api.IHttpRequest;
+import ca.uhn.fhir.rest.client.api.IHttpResponse;
import ca.uhn.fhir.util.StopWatch;
import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.Validate;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
@@ -34,13 +33,14 @@ import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.entity.ContentType;
-import ca.uhn.fhir.rest.client.api.IHttpRequest;
-import ca.uhn.fhir.rest.client.api.IHttpResponse;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.*;
/**
* A Http Request based on Apache. This is an adapter around the class
* {@link org.apache.http.client.methods.HttpRequestBase HttpRequestBase}
- *
+ *
* @author Peter Van Houte | peter.vanhoute@agfa.com | Agfa Healthcare
*/
public class ApacheHttpRequest implements IHttpRequest {
@@ -79,6 +79,7 @@ public class ApacheHttpRequest implements IHttpRequest {
/**
* Get the ApacheRequest
+ *
* @return the ApacheRequest
*/
public HttpRequestBase getApacheRequest() {
@@ -90,6 +91,12 @@ public class ApacheHttpRequest implements IHttpRequest {
return myRequest.getMethod();
}
+ @Override
+ public void removeHeaders(String theHeaderName) {
+ Validate.notBlank(theHeaderName, "theHeaderName must not be null or blank");
+ myRequest.removeHeaders(theHeaderName);
+ }
+
@Override
public String getRequestBodyFromStream() throws IOException {
if (myRequest instanceof HttpEntityEnclosingRequest) {
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 e81a7486170..4a931cb5cc1 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
@@ -33,19 +33,20 @@ import ca.uhn.fhir.rest.client.method.IClientResponseHandler;
import ca.uhn.fhir.rest.client.method.IClientResponseHandlerHandlesBinary;
import ca.uhn.fhir.rest.client.method.MethodUtil;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
+import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
+import ca.uhn.fhir.util.BinaryUtil;
import ca.uhn.fhir.util.OperationOutcomeUtil;
import ca.uhn.fhir.util.XmlDetectionUtil;
+import com.google.common.base.Charsets;
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 java.io.IOException;
-import java.io.InputStream;
-import java.io.Reader;
-import java.io.StringReader;
+import java.io.*;
import java.util.*;
+import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public abstract class BaseClient implements IRestfulClient {
@@ -93,14 +94,16 @@ public abstract class BaseClient implements IRestfulClient {
}
- protected Map> createExtraParams() {
- HashMap> retVal = new LinkedHashMap>();
+ protected Map> createExtraParams(String theCustomAcceptHeader) {
+ HashMap> retVal = new LinkedHashMap<>();
- if (myRequestFormatParamStyle == RequestFormatParamStyleEnum.SHORT) {
- if (getEncoding() == EncodingEnum.XML) {
- retVal.put(Constants.PARAM_FORMAT, Collections.singletonList("xml"));
- } else if (getEncoding() == EncodingEnum.JSON) {
- retVal.put(Constants.PARAM_FORMAT, Collections.singletonList("json"));
+ if (isBlank(theCustomAcceptHeader)) {
+ if (myRequestFormatParamStyle == RequestFormatParamStyleEnum.SHORT) {
+ if (getEncoding() == EncodingEnum.XML) {
+ retVal.put(Constants.PARAM_FORMAT, Collections.singletonList("xml"));
+ } else if (getEncoding() == EncodingEnum.JSON) {
+ retVal.put(Constants.PARAM_FORMAT, Collections.singletonList("json"));
+ }
}
}
@@ -115,7 +118,7 @@ public abstract class BaseClient implements IRestfulClient {
public T fetchResourceFromUrl(Class theResourceType, String theUrl) {
BaseHttpClientInvocation clientInvocation = new HttpGetClientInvocation(getFhirContext(), theUrl);
ResourceResponseHandler binding = new ResourceResponseHandler(theResourceType);
- return invokeClient(getFhirContext(), binding, clientInvocation, null, false, false, null, null, null);
+ return invokeClient(getFhirContext(), binding, clientInvocation, null, false, false, null, null, null, null);
}
void forceConformanceCheck() {
@@ -200,11 +203,11 @@ public abstract class BaseClient implements IRestfulClient {
}
T invokeClient(FhirContext theContext, IClientResponseHandler binding, BaseHttpClientInvocation clientInvocation, boolean theLogRequestAndResponse) {
- return invokeClient(theContext, binding, clientInvocation, null, null, theLogRequestAndResponse, null, null, null);
+ return invokeClient(theContext, binding, clientInvocation, null, null, theLogRequestAndResponse, null, null, null, null);
}
T invokeClient(FhirContext theContext, IClientResponseHandler binding, BaseHttpClientInvocation clientInvocation, EncodingEnum theEncoding, Boolean thePrettyPrint,
- boolean theLogRequestAndResponse, SummaryEnum theSummaryMode, Set theSubsetElements, CacheControlDirective theCacheControlDirective) {
+ boolean theLogRequestAndResponse, SummaryEnum theSummaryMode, Set theSubsetElements, CacheControlDirective theCacheControlDirective, String theCustomAcceptHeader) {
if (!myDontValidateConformance) {
myFactory.validateServerBaseIfConfiguredToDoSo(myUrlBase, myClient, this);
@@ -215,10 +218,10 @@ public abstract class BaseClient implements IRestfulClient {
IHttpRequest httpRequest = null;
IHttpResponse response = null;
try {
- Map> params = createExtraParams();
+ Map> params = createExtraParams(theCustomAcceptHeader);
if (clientInvocation instanceof HttpGetClientInvocation) {
- if (myRequestFormatParamStyle == RequestFormatParamStyleEnum.SHORT) {
+ if (myRequestFormatParamStyle == RequestFormatParamStyleEnum.SHORT && isBlank(theCustomAcceptHeader)) {
if (theEncoding == EncodingEnum.XML) {
params.put(Constants.PARAM_FORMAT, Collections.singletonList("xml"));
} else if (theEncoding == EncodingEnum.JSON) {
@@ -248,6 +251,11 @@ public abstract class BaseClient implements IRestfulClient {
httpRequest = clientInvocation.asHttpRequest(myUrlBase, params, encoding, thePrettyPrint);
+ if (isNotBlank(theCustomAcceptHeader)) {
+ httpRequest.removeHeaders(Constants.HEADER_ACCEPT);
+ httpRequest.addHeader(Constants.HEADER_ACCEPT, theCustomAcceptHeader);
+ }
+
if (theCacheControlDirective != null) {
StringBuilder b = new StringBuilder();
addToCacheControlHeader(b, Constants.CACHE_CONTROL_NO_CACHE, theCacheControlDirective.isNoCache());
@@ -289,14 +297,10 @@ public abstract class BaseClient implements IRestfulClient {
if (response.getStatus() < 200 || response.getStatus() > 299) {
String body = null;
- Reader reader = null;
- try {
- reader = response.createReader();
+ try (Reader reader = response.createReader()) {
body = IOUtils.toString(reader);
} catch (Exception e) {
ourLog.debug("Failed to read input stream", e);
- } finally {
- IOUtils.closeQuietly(reader);
}
String message = "HTTP " + response.getStatus() + " " + response.getStatusInfo();
@@ -334,27 +338,22 @@ public abstract class BaseClient implements IRestfulClient {
if (binding instanceof IClientResponseHandlerHandlesBinary) {
IClientResponseHandlerHandlesBinary handlesBinary = (IClientResponseHandlerHandlesBinary) binding;
if (handlesBinary.isBinary()) {
- InputStream reader = response.readEntity();
- try {
+ try (InputStream reader = response.readEntity()) {
return handlesBinary.invokeClient(mimeType, reader, response.getStatus(), headers);
- } finally {
- IOUtils.closeQuietly(reader);
}
}
}
- Reader reader = response.createReader();
+ try (InputStream inputStream = response.readEntity()) {
+ InputStream inputStreamToReturn = inputStream;
- if (ourLog.isTraceEnabled() || myKeepResponses || theLogRequestAndResponse) {
- String responseString = IOUtils.toString(reader);
- keepResponseAndLogIt(theLogRequestAndResponse, response, responseString);
- reader = new StringReader(responseString);
- }
+ if (ourLog.isTraceEnabled() || myKeepResponses || theLogRequestAndResponse) {
+ String responseString = IOUtils.toString(inputStream, Charsets.UTF_8);
+ keepResponseAndLogIt(theLogRequestAndResponse, response, responseString);
+ inputStreamToReturn = new ByteArrayInputStream(responseString.getBytes(Charsets.UTF_8));
+ }
- try {
- return binding.invokeClient(mimeType, reader, response.getStatus(), headers);
- } finally {
- IOUtils.closeQuietly(reader);
+ return binding.invokeClient(mimeType, inputStreamToReturn, response.getStatus(), headers);
}
} catch (DataFormatException e) {
@@ -463,7 +462,48 @@ public abstract class BaseClient implements IRestfulClient {
myInterceptors.remove(theInterceptor);
}
- protected final class ResourceResponseHandler implements IClientResponseHandler {
+ protected final class ResourceOrBinaryResponseHandler extends ResourceResponseHandler {
+
+
+ @Override
+ public IBaseResource invokeClient(String theResponseMimeType, InputStream theResponseInputStream, int theResponseStatusCode, Map> theHeaders) throws BaseServerResponseException {
+
+ /*
+ * For operation responses, if the response content type is a FHIR content-type
+ * (which is will probably almost always be) we just handle it normally. However,
+ * if we get back a successful (2xx) response from an operation, and the content
+ * type is something other than FHIR, we'll return it as a Binary wrapped in
+ * a Parameters resource.
+ */
+ EncodingEnum respType = EncodingEnum.forContentType(theResponseMimeType);
+ if (respType != null || theResponseStatusCode < 200 || theResponseStatusCode >= 300) {
+ return super.invokeClient(theResponseMimeType, theResponseInputStream, theResponseStatusCode, theHeaders);
+ }
+
+ // Create a Binary resource to return
+ IBaseBinary responseBinary = BinaryUtil.newBinary(getFhirContext());
+
+ // Fetch the content type
+ String contentType = null;
+ List contentTypeHeaders = theHeaders.get(Constants.HEADER_CONTENT_TYPE_LC);
+ if (contentTypeHeaders != null && contentTypeHeaders.size() > 0) {
+ contentType = contentTypeHeaders.get(0);
+ }
+ responseBinary.setContentType(contentType);
+
+ // Fetch the content itself
+ try {
+ responseBinary.setContent(IOUtils.toByteArray(theResponseInputStream));
+ } catch (IOException e) {
+ throw new InternalErrorException("IO failure parsing response", e);
+ }
+
+ return responseBinary;
+ }
+
+ }
+
+ protected class ResourceResponseHandler implements IClientResponseHandler {
private boolean myAllowHtmlResponse;
private IIdType myId;
@@ -498,20 +538,20 @@ public abstract class BaseClient implements IRestfulClient {
}
@Override
- public T invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map> theHeaders) throws BaseServerResponseException {
+ public T invokeClient(String theResponseMimeType, InputStream theResponseInputStream, int theResponseStatusCode, Map> theHeaders) throws BaseServerResponseException {
EncodingEnum respType = EncodingEnum.forContentType(theResponseMimeType);
if (respType == null) {
if (myAllowHtmlResponse && theResponseMimeType.toLowerCase().contains(Constants.CT_HTML) && myReturnType != null) {
- return readHtmlResponse(theResponseReader);
+ return readHtmlResponse(theResponseInputStream);
}
- throw NonFhirResponseException.newInstance(theResponseStatusCode, theResponseMimeType, theResponseReader);
+ throw NonFhirResponseException.newInstance(theResponseStatusCode, theResponseMimeType, theResponseInputStream);
}
IParser parser = respType.newParser(getFhirContext());
parser.setServerBaseUrl(getUrlBase());
if (myPreferResponseTypes != null) {
parser.setPreferTypes(myPreferResponseTypes);
}
- T retVal = parser.parseResource(myReturnType, theResponseReader);
+ T retVal = parser.parseResource(myReturnType, theResponseInputStream);
MethodUtil.parseClientRequestResourceHeaders(myId, theHeaders, retVal);
@@ -519,7 +559,7 @@ public abstract class BaseClient implements IRestfulClient {
}
@SuppressWarnings("unchecked")
- private T readHtmlResponse(Reader theResponseReader) {
+ private T readHtmlResponse(InputStream theResponseInputStream) {
RuntimeResourceDefinition resDef = getFhirContext().getResourceDefinition(myReturnType);
IBaseResource instance = resDef.newInstance();
BaseRuntimeChildDefinition textChild = resDef.getChildByName("text");
@@ -531,7 +571,7 @@ public abstract class BaseClient implements IRestfulClient {
BaseRuntimeElementDefinition> divElement = divChild.getChildByName("div");
IPrimitiveType> divInstance = (IPrimitiveType>) divElement.newInstance();
try {
- divInstance.setValueAsString(IOUtils.toString(theResponseReader));
+ divInstance.setValueAsString(IOUtils.toString(theResponseInputStream, Charsets.UTF_8));
} catch (Exception e) {
throw new InvalidResponseException(400, "Failed to process HTML response from server: " + e.getMessage(), e);
}
@@ -539,8 +579,9 @@ public abstract class BaseClient implements IRestfulClient {
return (T) instance;
}
- public void setPreferResponseTypes(List> thePreferResponseTypes) {
+ public ResourceResponseHandler setPreferResponseTypes(List> thePreferResponseTypes) {
myPreferResponseTypes = thePreferResponseTypes;
+ return this;
}
}
diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/GenericClient.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/GenericClient.java
index b9705fc3534..175efe5c7ba 100644
--- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/GenericClient.java
+++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/GenericClient.java
@@ -46,19 +46,19 @@ import ca.uhn.fhir.rest.server.exceptions.NotModifiedException;
import ca.uhn.fhir.util.ICallable;
import ca.uhn.fhir.util.ParametersUtil;
import ca.uhn.fhir.util.UrlUtil;
+import com.google.common.base.Charsets;
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 java.io.IOException;
+import java.io.InputStream;
import java.io.Reader;
import java.util.*;
import java.util.Map.Entry;
-import static org.apache.commons.lang3.StringUtils.defaultString;
-import static org.apache.commons.lang3.StringUtils.isBlank;
-import static org.apache.commons.lang3.StringUtils.isNotBlank;
+import static org.apache.commons.lang3.StringUtils.*;
/**
* @author James Agnew
@@ -98,7 +98,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
}
private T doReadOrVRead(final Class theType, IIdType theId, boolean theVRead, ICallable theNotModifiedHandler, String theIfVersionMatches, Boolean thePrettyPrint,
- SummaryEnum theSummary, EncodingEnum theEncoding, Set theSubsetElements) {
+ SummaryEnum theSummary, EncodingEnum theEncoding, Set theSubsetElements, String theCustomAcceptHeaderValue) {
String resName = toResourceName(theType);
IIdType id = theId;
if (!id.hasBaseUrl()) {
@@ -120,7 +120,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
}
}
if (isKeepResponses()) {
- myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding(), isPrettyPrint());
+ myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(theCustomAcceptHeaderValue), getEncoding(), isPrettyPrint());
}
if (theIfVersionMatches != null) {
@@ -131,10 +131,10 @@ public class GenericClient extends BaseClient implements IGenericClient {
ResourceResponseHandler binding = new ResourceResponseHandler<>(theType, (Class extends IBaseResource>) null, id, allowHtmlResponse);
if (theNotModifiedHandler == null) {
- return invokeClient(myContext, binding, invocation, theEncoding, thePrettyPrint, myLogRequestAndResponse, theSummary, theSubsetElements, null);
+ return invokeClient(myContext, binding, invocation, theEncoding, thePrettyPrint, myLogRequestAndResponse, theSummary, theSubsetElements, null, theCustomAcceptHeaderValue);
}
try {
- return invokeClient(myContext, binding, invocation, theEncoding, thePrettyPrint, myLogRequestAndResponse, theSummary, theSubsetElements, null);
+ return invokeClient(myContext, binding, invocation, theEncoding, thePrettyPrint, myLogRequestAndResponse, theSummary, theSubsetElements, null, theCustomAcceptHeaderValue);
} catch (NotModifiedException e) {
return theNotModifiedHandler.call();
}
@@ -228,7 +228,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
@Override
public T read(final Class theType, UriDt theUrl) {
IdDt id = theUrl instanceof IdDt ? ((IdDt) theUrl) : new IdDt(theUrl);
- return doReadOrVRead(theType, id, false, null, null, false, null, null, null);
+ return doReadOrVRead(theType, id, false, null, null, false, null, null, null, null);
}
@Override
@@ -269,7 +269,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
public MethodOutcome update(IdDt theIdDt, IBaseResource theResource) {
BaseHttpClientInvocation invocation = MethodUtil.createUpdateInvocation(theResource, null, theIdDt, myContext);
if (isKeepResponses()) {
- myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding(), isPrettyPrint());
+ myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(null), getEncoding(), isPrettyPrint());
}
OutcomeResponseHandler binding = new OutcomeResponseHandler();
@@ -293,7 +293,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
invocation = ValidateMethodBindingDstu2Plus.createValidateInvocation(myContext, theResource);
if (isKeepResponses()) {
- myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding(), isPrettyPrint());
+ myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(null), getEncoding(), isPrettyPrint());
}
OutcomeResponseHandler binding = new OutcomeResponseHandler();
@@ -306,7 +306,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
if (theId.hasVersionIdPart() == false) {
throw new IllegalArgumentException(myContext.getLocalizer().getMessage(I18N_NO_VERSION_ID_FOR_VREAD, theId.getValue()));
}
- return doReadOrVRead(theType, theId, true, null, null, false, null, null, null);
+ return doReadOrVRead(theType, theId, true, null, null, false, null, null, null, null);
}
@Override
@@ -315,49 +315,6 @@ public class GenericClient extends BaseClient implements IGenericClient {
return vread(theType, resId);
}
- private static void addParam(Map> params, String parameterName, String parameterValue) {
- if (!params.containsKey(parameterName)) {
- params.put(parameterName, new ArrayList<>());
- }
- params.get(parameterName).add(parameterValue);
- }
-
- private static void addPreferHeader(PreferReturnEnum thePrefer, BaseHttpClientInvocation theInvocation) {
- if (thePrefer != null) {
- theInvocation.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RETURN + '=' + thePrefer.getHeaderValue());
- }
- }
-
- private static String validateAndEscapeConditionalUrl(String theSearchUrl) {
- Validate.notBlank(theSearchUrl, "Conditional URL can not be blank/null");
- StringBuilder b = new StringBuilder();
- boolean haveHadQuestionMark = false;
- for (int i = 0; i < theSearchUrl.length(); i++) {
- char nextChar = theSearchUrl.charAt(i);
- if (!haveHadQuestionMark) {
- if (nextChar == '?') {
- haveHadQuestionMark = true;
- } else if (!Character.isLetter(nextChar)) {
- throw new IllegalArgumentException("Conditional URL must be in the format \"[ResourceType]?[Params]\" and must not have a base URL - Found: " + theSearchUrl);
- }
- b.append(nextChar);
- } else {
- switch (nextChar) {
- case '|':
- case '?':
- case '$':
- case ':':
- b.append(UrlUtil.escapeUrlParam(Character.toString(nextChar)));
- break;
- default:
- b.append(nextChar);
- break;
- }
- }
- }
- return b.toString();
- }
-
private enum MetaOperation {
ADD,
DELETE,
@@ -366,14 +323,25 @@ public class GenericClient extends BaseClient implements IGenericClient {
private abstract class BaseClientExecutable, Y> implements IClientExecutable {
- protected EncodingEnum myParamEncoding;
- protected Boolean myPrettyPrint;
- protected SummaryEnum mySummaryMode;
- protected CacheControlDirective myCacheControlDirective;
+ EncodingEnum myParamEncoding;
+ Boolean myPrettyPrint;
+ SummaryEnum mySummaryMode;
+ CacheControlDirective myCacheControlDirective;
+ private String myCustomAcceptHeaderValue;
private List> myPreferResponseTypes;
private boolean myQueryLogRequestAndResponse;
private HashSet mySubsetElements;
+ public String getCustomAcceptHeaderValue() {
+ return myCustomAcceptHeaderValue;
+ }
+
+ @Override
+ public T accept(String theHeaderValue) {
+ myCustomAcceptHeaderValue = theHeaderValue;
+ return (T) this;
+ }
+
@Deprecated // override deprecated method
@SuppressWarnings("unchecked")
@Override
@@ -392,7 +360,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
@Override
public T elementsSubset(String... theElements) {
if (theElements != null && theElements.length > 0) {
- mySubsetElements = new HashSet(Arrays.asList(theElements));
+ mySubsetElements = new HashSet<>(Arrays.asList(theElements));
} else {
mySubsetElements = null;
}
@@ -444,7 +412,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
myLastRequest = theInvocation.asHttpRequest(getServerBase(), theParams, getEncoding(), myPrettyPrint);
}
- Z resp = invokeClient(myContext, theHandler, theInvocation, myParamEncoding, myPrettyPrint, myQueryLogRequestAndResponse || myLogRequestAndResponse, mySummaryMode, mySubsetElements, myCacheControlDirective);
+ Z resp = invokeClient(myContext, theHandler, theInvocation, myParamEncoding, myPrettyPrint, myQueryLogRequestAndResponse || myLogRequestAndResponse, mySummaryMode, mySubsetElements, myCacheControlDirective, myCustomAcceptHeaderValue);
return resp;
}
@@ -461,7 +429,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
public T preferResponseType(Class extends IBaseResource> theClass) {
myPreferResponseTypes = null;
if (theClass != null) {
- myPreferResponseTypes = new ArrayList>();
+ myPreferResponseTypes = new ArrayList<>();
myPreferResponseTypes.add(theClass);
}
return (T) this;
@@ -1021,14 +989,14 @@ public class GenericClient extends BaseClient implements IGenericClient {
@SuppressWarnings("unchecked")
@Override
- public T invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map> theHeaders) throws BaseServerResponseException {
+ public T invokeClient(String theResponseMimeType, InputStream theResponseInputStream, int theResponseStatusCode, Map> theHeaders) throws BaseServerResponseException {
EncodingEnum respType = EncodingEnum.forContentType(theResponseMimeType);
if (respType == null) {
- throw NonFhirResponseException.newInstance(theResponseStatusCode, theResponseMimeType, theResponseReader);
+ throw NonFhirResponseException.newInstance(theResponseStatusCode, theResponseMimeType, theResponseInputStream);
}
IParser parser = respType.newParser(myContext);
RuntimeResourceDefinition type = myContext.getResourceDefinition("Parameters");
- IBaseResource retVal = parser.parseResource(type.getImplementingClass(), theResponseReader);
+ IBaseResource retVal = parser.parseResource(type.getImplementingClass(), theResponseInputStream);
BaseRuntimeChildDefinition paramChild = type.getChildByName("parameter");
BaseRuntimeElementCompositeDefinition> paramChildElem = (BaseRuntimeElementCompositeDefinition>) paramChild.getChildByName("parameter");
@@ -1061,6 +1029,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
private Class myReturnResourceType;
private Class extends IBaseResource> myType;
private boolean myUseHttpGet;
+ private boolean myReturnMethodOutcome;
@SuppressWarnings("unchecked")
private void addParam(String theName, IBase theValue) {
@@ -1170,11 +1139,19 @@ public class GenericClient extends BaseClient implements IGenericClient {
Object retVal = invoke(null, handler, invocation);
return retVal;
}
- ResourceResponseHandler handler;
- handler = new ResourceResponseHandler();
- handler.setPreferResponseTypes(getPreferResponseTypes(myType));
+ IClientResponseHandler handler = new ResourceOrBinaryResponseHandler()
+ .setPreferResponseTypes(getPreferResponseTypes(myType));
+
+ if (myReturnMethodOutcome) {
+ handler = new MethodOutcomeResponseHandler(handler);
+ }
Object retVal = invoke(null, handler, invocation);
+
+ if (myReturnMethodOutcome) {
+ return retVal;
+ }
+
if (myContext.getResourceDefinition((IBaseResource) retVal).getName().equals("Parameters")) {
return retVal;
}
@@ -1236,6 +1213,12 @@ public class GenericClient extends BaseClient implements IGenericClient {
return this;
}
+ @Override
+ public IOperationUntypedWithInput returnMethodOutcome() {
+ myReturnMethodOutcome = true;
+ return this;
+ }
+
@SuppressWarnings("unchecked")
@Override
public IOperationProcessMsgMode setMessageBundle(IBaseBundle theMsgBundle) {
@@ -1331,10 +1314,30 @@ public class GenericClient extends BaseClient implements IGenericClient {
}
+
+ private final class MethodOutcomeResponseHandler implements IClientResponseHandler {
+ private final IClientResponseHandler extends IBaseResource> myWrap;
+
+ private MethodOutcomeResponseHandler(IClientResponseHandler extends IBaseResource> theWrap) {
+ myWrap = theWrap;
+ }
+
+ @Override
+ public MethodOutcome invokeClient(String theResponseMimeType, InputStream theResponseInputStream, int theResponseStatusCode, Map> theHeaders) throws IOException, BaseServerResponseException {
+ IBaseResource response = myWrap.invokeClient(theResponseMimeType, theResponseInputStream, theResponseStatusCode, theHeaders);
+
+ MethodOutcome retVal = new MethodOutcome();
+ retVal.setResource(response);
+ retVal.setCreatedUsingStatusCode(theResponseStatusCode);
+ retVal.setResponseHeaders(theHeaders);
+ return retVal;
+ }
+ }
+
private final class OperationOutcomeResponseHandler implements IClientResponseHandler {
@Override
- public IBaseOperationOutcome invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map> theHeaders)
+ public IBaseOperationOutcome invokeClient(String theResponseMimeType, InputStream theResponseInputStream, int theResponseStatusCode, Map> theHeaders)
throws BaseServerResponseException {
EncodingEnum respType = EncodingEnum.forContentType(theResponseMimeType);
if (respType == null) {
@@ -1344,7 +1347,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
IBaseOperationOutcome retVal;
try {
// TODO: handle if something else than OO comes back
- retVal = (IBaseOperationOutcome) parser.parseResource(theResponseReader);
+ retVal = (IBaseOperationOutcome) parser.parseResource(theResponseInputStream);
} catch (DataFormatException e) {
ourLog.warn("Failed to parse OperationOutcome response", e);
return null;
@@ -1368,11 +1371,9 @@ public class GenericClient extends BaseClient implements IGenericClient {
}
@Override
- public MethodOutcome invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map> theHeaders) throws BaseServerResponseException {
- MethodOutcome response = MethodUtil.process2xxResponse(myContext, theResponseStatusCode, theResponseMimeType, theResponseReader, theHeaders);
- if (theResponseStatusCode == Constants.STATUS_HTTP_201_CREATED) {
- response.setCreated(true);
- }
+ public MethodOutcome invokeClient(String theResponseMimeType, InputStream theResponseInputStream, int theResponseStatusCode, Map> theHeaders) throws BaseServerResponseException {
+ MethodOutcome response = MethodUtil.process2xxResponse(myContext, theResponseStatusCode, theResponseMimeType, theResponseInputStream, theHeaders);
+ response.setCreatedUsingStatusCode(theResponseStatusCode);
if (myPrefer == PreferReturnEnum.REPRESENTATION) {
if (response.getResource() == null) {
@@ -1384,6 +1385,8 @@ public class GenericClient extends BaseClient implements IGenericClient {
}
}
+ response.setResponseHeaders(theHeaders);
+
return response;
}
}
@@ -1511,9 +1514,9 @@ public class GenericClient extends BaseClient implements IGenericClient {
@Override
public Object execute() {// AAA
if (myId.hasVersionIdPart()) {
- return doReadOrVRead(myType.getImplementingClass(), myId, true, myNotModifiedHandler, myIfVersionMatches, myPrettyPrint, mySummaryMode, myParamEncoding, getSubsetElements());
+ return doReadOrVRead(myType.getImplementingClass(), myId, true, myNotModifiedHandler, myIfVersionMatches, myPrettyPrint, mySummaryMode, myParamEncoding, getSubsetElements(), getCustomAcceptHeaderValue());
}
- return doReadOrVRead(myType.getImplementingClass(), myId, false, myNotModifiedHandler, myIfVersionMatches, myPrettyPrint, mySummaryMode, myParamEncoding, getSubsetElements());
+ return doReadOrVRead(myType.getImplementingClass(), myId, false, myNotModifiedHandler, myIfVersionMatches, myPrettyPrint, mySummaryMode, myParamEncoding, getSubsetElements(), getCustomAcceptHeaderValue());
}
@Override
@@ -1636,11 +1639,11 @@ public class GenericClient extends BaseClient implements IGenericClient {
@SuppressWarnings("unchecked")
@Override
- public List invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map> theHeaders)
+ public List invokeClient(String theResponseMimeType, InputStream theResponseInputStream, int theResponseStatusCode, Map> theHeaders)
throws BaseServerResponseException {
Class extends IBaseResource> bundleType = myContext.getResourceDefinition("Bundle").getImplementingClass();
- ResourceResponseHandler handler = new ResourceResponseHandler((Class) bundleType);
- IBaseResource response = handler.invokeClient(theResponseMimeType, theResponseReader, theResponseStatusCode, theHeaders);
+ ResourceResponseHandler handler = new ResourceResponseHandler<>((Class) bundleType);
+ IBaseResource response = handler.invokeClient(theResponseMimeType, theResponseInputStream, theResponseStatusCode, theHeaders);
IVersionSpecificBundleFactory bundleFactory = myContext.newBundleFactory();
bundleFactory.initializeWithBundleResource(response);
return bundleFactory.toListOfResources();
@@ -1937,9 +1940,9 @@ public class GenericClient extends BaseClient implements IGenericClient {
private final class StringResponseHandler implements IClientResponseHandler {
@Override
- public String invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map> theHeaders)
+ public String invokeClient(String theResponseMimeType, InputStream theResponseInputStream, int theResponseStatusCode, Map> theHeaders)
throws IOException, BaseServerResponseException {
- return IOUtils.toString(theResponseReader);
+ return IOUtils.toString(theResponseInputStream, Charsets.UTF_8);
}
}
@@ -2251,4 +2254,47 @@ public class GenericClient extends BaseClient implements IGenericClient {
}
+ private static void addParam(Map> params, String parameterName, String parameterValue) {
+ if (!params.containsKey(parameterName)) {
+ params.put(parameterName, new ArrayList<>());
+ }
+ params.get(parameterName).add(parameterValue);
+ }
+
+ private static void addPreferHeader(PreferReturnEnum thePrefer, BaseHttpClientInvocation theInvocation) {
+ if (thePrefer != null) {
+ theInvocation.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RETURN + '=' + thePrefer.getHeaderValue());
+ }
+ }
+
+ private static String validateAndEscapeConditionalUrl(String theSearchUrl) {
+ Validate.notBlank(theSearchUrl, "Conditional URL can not be blank/null");
+ StringBuilder b = new StringBuilder();
+ boolean haveHadQuestionMark = false;
+ for (int i = 0; i < theSearchUrl.length(); i++) {
+ char nextChar = theSearchUrl.charAt(i);
+ if (!haveHadQuestionMark) {
+ if (nextChar == '?') {
+ haveHadQuestionMark = true;
+ } else if (!Character.isLetter(nextChar)) {
+ throw new IllegalArgumentException("Conditional URL must be in the format \"[ResourceType]?[Params]\" and must not have a base URL - Found: " + theSearchUrl);
+ }
+ b.append(nextChar);
+ } else {
+ switch (nextChar) {
+ case '|':
+ case '?':
+ case '$':
+ case ':':
+ b.append(UrlUtil.escapeUrlParam(Character.toString(nextChar)));
+ break;
+ default:
+ b.append(nextChar);
+ break;
+ }
+ }
+ }
+ return b.toString();
+ }
+
}
diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/BaseMethodBinding.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/BaseMethodBinding.java
index 490ee770028..aaee9568521 100644
--- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/BaseMethodBinding.java
+++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/BaseMethodBinding.java
@@ -21,6 +21,7 @@ package ca.uhn.fhir.rest.client.method;
*/
import java.io.IOException;
+import java.io.InputStream;
import java.io.Reader;
import java.lang.reflect.Method;
import java.util.*;
@@ -71,11 +72,11 @@ public abstract class BaseMethodBinding implements IClientResponseHandler
}
- protected IParser createAppropriateParserForParsingResponse(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, List> thePreferTypes) {
+ protected IParser createAppropriateParserForParsingResponse(String theResponseMimeType, InputStream theResponseInputStream, int theResponseStatusCode, List> thePreferTypes) {
EncodingEnum encoding = EncodingEnum.forContentType(theResponseMimeType);
if (encoding == null) {
- NonFhirResponseException ex = NonFhirResponseException.newInstance(theResponseStatusCode, theResponseMimeType, theResponseReader);
- populateException(ex, theResponseReader);
+ NonFhirResponseException ex = NonFhirResponseException.newInstance(theResponseStatusCode, theResponseMimeType, theResponseInputStream);
+ populateException(ex, theResponseInputStream);
throw ex;
}
@@ -139,7 +140,7 @@ public abstract class BaseMethodBinding implements IClientResponseHandler
return mySupportsConditionalMultiple;
}
- protected BaseServerResponseException processNon2xxResponseAndReturnExceptionToThrow(int theStatusCode, String theResponseMimeType, Reader theResponseReader) {
+ protected BaseServerResponseException processNon2xxResponseAndReturnExceptionToThrow(int theStatusCode, String theResponseMimeType, InputStream theResponseInputStream) {
BaseServerResponseException ex;
switch (theStatusCode) {
case Constants.STATUS_HTTP_400_BAD_REQUEST:
@@ -158,9 +159,9 @@ public abstract class BaseMethodBinding implements IClientResponseHandler
ex = new PreconditionFailedException("Server responded with HTTP 412");
break;
case Constants.STATUS_HTTP_422_UNPROCESSABLE_ENTITY:
- IParser parser = createAppropriateParserForParsingResponse(theResponseMimeType, theResponseReader, theStatusCode, null);
+ IParser parser = createAppropriateParserForParsingResponse(theResponseMimeType, theResponseInputStream, theStatusCode, null);
// TODO: handle if something other than OO comes back
- BaseOperationOutcome operationOutcome = (BaseOperationOutcome) parser.parseResource(theResponseReader);
+ BaseOperationOutcome operationOutcome = (BaseOperationOutcome) parser.parseResource(theResponseInputStream);
ex = new UnprocessableEntityException(myContext, operationOutcome);
break;
default:
@@ -168,7 +169,7 @@ public abstract class BaseMethodBinding implements IClientResponseHandler
break;
}
- populateException(ex, theResponseReader);
+ populateException(ex, theResponseInputStream);
return ex;
}
@@ -322,9 +323,9 @@ public abstract class BaseMethodBinding implements IClientResponseHandler
return theReturnTypeFromMethod.equals(IBaseResource.class) || theReturnTypeFromMethod.equals(IResource.class) || theReturnTypeFromMethod.equals(IAnyResource.class);
}
- private static void populateException(BaseServerResponseException theEx, Reader theResponseReader) {
+ private static void populateException(BaseServerResponseException theEx, InputStream theResponseInputStream) {
try {
- String responseText = IOUtils.toString(theResponseReader);
+ String responseText = IOUtils.toString(theResponseInputStream);
theEx.setResponseBody(responseText);
} catch (IOException e) {
ourLog.debug("Failed to read response", e);
diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/BaseOutcomeReturningMethodBinding.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/BaseOutcomeReturningMethodBinding.java
index 19614ef676b..332ba6f7ddb 100644
--- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/BaseOutcomeReturningMethodBinding.java
+++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/BaseOutcomeReturningMethodBinding.java
@@ -20,6 +20,7 @@ package ca.uhn.fhir.rest.client.method;
* #L%
*/
+import java.io.InputStream;
import java.io.Reader;
import java.lang.reflect.Method;
import java.util.*;
@@ -68,15 +69,15 @@ abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding> theHeaders) throws BaseServerResponseException {
+ public MethodOutcome invokeClient(String theResponseMimeType, InputStream theResponseInputStream, int theResponseStatusCode, Map> theHeaders) throws BaseServerResponseException {
if (theResponseStatusCode >= 200 && theResponseStatusCode < 300) {
if (myReturnVoid) {
return null;
}
- MethodOutcome retVal = MethodUtil.process2xxResponse(getContext(), theResponseStatusCode, theResponseMimeType, theResponseReader, theHeaders);
+ MethodOutcome retVal = MethodUtil.process2xxResponse(getContext(), theResponseStatusCode, theResponseMimeType, theResponseInputStream, theHeaders);
return retVal;
}
- throw processNon2xxResponseAndReturnExceptionToThrow(theResponseStatusCode, theResponseMimeType, theResponseReader);
+ throw processNon2xxResponseAndReturnExceptionToThrow(theResponseStatusCode, theResponseMimeType, theResponseInputStream);
}
public boolean isReturnVoid() {
diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/BaseResourceReturningMethodBinding.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/BaseResourceReturningMethodBinding.java
index 0286a09b5e6..b4d2280c4fb 100644
--- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/BaseResourceReturningMethodBinding.java
+++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/BaseResourceReturningMethodBinding.java
@@ -20,6 +20,8 @@ package ca.uhn.fhir.rest.client.method;
* #L%
*/
+import java.io.IOException;
+import java.io.InputStream;
import java.io.Reader;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
@@ -123,21 +125,21 @@ public abstract class BaseResourceReturningMethodBinding extends BaseMethodBindi
public abstract ReturnTypeEnum getReturnType();
@Override
- public Object invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map> theHeaders) {
+ public Object invokeClient(String theResponseMimeType, InputStream theResponseInputStream, int theResponseStatusCode, Map> theHeaders) throws IOException {
if (Constants.STATUS_HTTP_204_NO_CONTENT == theResponseStatusCode) {
return toReturnType(null);
}
- IParser parser = createAppropriateParserForParsingResponse(theResponseMimeType, theResponseReader, theResponseStatusCode, myPreferTypesList);
+ IParser parser = createAppropriateParserForParsingResponse(theResponseMimeType, theResponseInputStream, theResponseStatusCode, myPreferTypesList);
switch (getReturnType()) {
case BUNDLE: {
- IBaseBundle bundle = null;
- List extends IBaseResource> listOfResources = null;
+ IBaseBundle bundle;
+ List extends IBaseResource> listOfResources;
Class extends IBaseResource> type = getContext().getResourceDefinition("Bundle").getImplementingClass();
- bundle = (IBaseBundle) parser.parseResource(type, theResponseReader);
+ bundle = (IBaseBundle) parser.parseResource(type, theResponseInputStream);
listOfResources = BundleUtil.toListOfResources(getContext(), bundle);
switch (getMethodReturnType()) {
@@ -171,9 +173,9 @@ public abstract class BaseResourceReturningMethodBinding extends BaseMethodBindi
case RESOURCE: {
IBaseResource resource;
if (myResourceType != null) {
- resource = parser.parseResource(myResourceType, theResponseReader);
+ resource = parser.parseResource(myResourceType, theResponseInputStream);
} else {
- resource = parser.parseResource(theResponseReader);
+ resource = parser.parseResource(theResponseInputStream);
}
MethodUtil.parseClientRequestResourceHeaders(null, theHeaders, resource);
diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/IClientResponseHandler.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/IClientResponseHandler.java
index 65437ee2d5e..dccaaac6c8a 100644
--- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/IClientResponseHandler.java
+++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/IClientResponseHandler.java
@@ -21,6 +21,7 @@ package ca.uhn.fhir.rest.client.method;
*/
import java.io.IOException;
+import java.io.InputStream;
import java.io.Reader;
import java.util.List;
import java.util.Map;
@@ -29,6 +30,6 @@ import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
public interface IClientResponseHandler {
- T invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map> theHeaders) throws IOException, BaseServerResponseException;
+ T invokeClient(String theResponseMimeType, InputStream theResponseInputStream, int theResponseStatusCode, Map> theHeaders) throws IOException, BaseServerResponseException;
}
diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/MethodUtil.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/MethodUtil.java
index 486158974b9..94a26d60a40 100644
--- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/MethodUtil.java
+++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/MethodUtil.java
@@ -49,15 +49,6 @@ import ca.uhn.fhir.util.*;
public class MethodUtil {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(MethodUtil.class);
- private static final Set ourServletRequestTypes = new HashSet();
- private static final Set ourServletResponseTypes = new HashSet();
-
- static {
- ourServletRequestTypes.add("javax.servlet.ServletRequest");
- ourServletResponseTypes.add("javax.servlet.ServletResponse");
- ourServletRequestTypes.add("javax.servlet.http.HttpServletRequest");
- ourServletResponseTypes.add("javax.servlet.http.HttpServletResponse");
- }
/** Non instantiable */
private MethodUtil() {
@@ -497,8 +488,8 @@ public class MethodUtil {
}
public static MethodOutcome process2xxResponse(FhirContext theContext, int theResponseStatusCode,
- String theResponseMimeType, Reader theResponseReader, Map> theHeaders) {
- List locationHeaders = new ArrayList();
+ String theResponseMimeType, InputStream theResponseReader, Map> theHeaders) {
+ List locationHeaders = new ArrayList<>();
List lh = theHeaders.get(Constants.HEADER_LOCATION_LC);
if (lh != null) {
locationHeaders.addAll(lh);
@@ -509,14 +500,14 @@ public class MethodUtil {
}
MethodOutcome retVal = new MethodOutcome();
- if (locationHeaders != null && locationHeaders.size() > 0) {
+ if (locationHeaders.size() > 0) {
String locationHeader = locationHeaders.get(0);
BaseOutcomeReturningMethodBinding.parseContentLocation(theContext, retVal, locationHeader);
}
if (theResponseStatusCode != Constants.STATUS_HTTP_204_NO_CONTENT) {
EncodingEnum ct = EncodingEnum.forContentType(theResponseMimeType);
if (ct != null) {
- PushbackReader reader = new PushbackReader(theResponseReader);
+ PushbackInputStream reader = new PushbackInputStream(theResponseReader);
try {
int firstByte = reader.read();
diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/servlet/ServletRestfulResponse.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/servlet/ServletRestfulResponse.java
index 969c0fa12b6..da498b2cd10 100644
--- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/servlet/ServletRestfulResponse.java
+++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/servlet/ServletRestfulResponse.java
@@ -50,6 +50,7 @@ public class ServletRestfulResponse extends RestfulResponse 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_JSON + "; charset=UTF-8"));
- when(myHttpResponse.getEntity().getContent()).then(new Answer() {
- @Override
- public InputStream answer(InvocationOnMock theInvocation) throws Throwable {
- return new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"));
- }
- });
-
- IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
- int idx = 0;
-
- client.setEncoding(EncodingEnum.JSON);
- client.search()
- .forResource("Device")
- .returnBundle(Bundle.class)
- .execute();
-
- assertEquals("http://example.com/fhir/Device?_format=json", UrlUtil.unescape(capt.getAllValues().get(idx).getURI().toString()));
- assertEquals("application/fhir+json;q=1.0, application/json+fhir;q=0.9", capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_ACCEPT).getValue());
- idx++;
-
- client.setEncoding(EncodingEnum.XML);
- client.search()
- .forResource("Device")
- .returnBundle(Bundle.class)
- .execute();
-
- assertEquals("http://example.com/fhir/Device?_format=xml", UrlUtil.unescape(capt.getAllValues().get(idx).getURI().toString()));
- assertEquals("application/fhir+xml;q=1.0, application/xml+fhir;q=0.9", capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_ACCEPT).getValue());
- idx++;
-
- }
-
@Test
public void testBinaryCreateWithFhirContentType() throws Exception {
IParser p = ourCtx.newXmlParser();
diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/GenericClientR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/GenericClientR4Test.java
index edd30b3bc63..4fae6470c88 100644
--- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/GenericClientR4Test.java
+++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/GenericClientR4Test.java
@@ -52,6 +52,7 @@ import org.mockito.internal.stubbing.defaultanswers.ReturnsDeepStubs;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
+import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
@@ -113,7 +114,7 @@ public class GenericClientR4Test {
}
@Test
- public void testAcceptHeaderWithEncodingSpecified() throws Exception {
+ public void testAcceptHeaderCustom() throws Exception {
final String msg = "{\"resourceType\":\"Bundle\",\"id\":null,\"base\":\"http://localhost:57931/fhir/contextDev\",\"total\":1,\"link\":[{\"relation\":\"self\",\"url\":\"http://localhost:57931/fhir/contextDev/Patient?identifier=urn%3AMultiFhirVersionTest%7CtestSubmitPatient01&_format=json\"}],\"entry\":[{\"resource\":{\"resourceType\":\"Patient\",\"id\":\"1\",\"meta\":{\"versionId\":\"1\",\"lastUpdated\":\"2014-12-20T18:41:29.706-05:00\"},\"identifier\":[{\"system\":\"urn:MultiFhirVersionTest\",\"value\":\"testSubmitPatient01\"}]}}]}";
ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class);
@@ -130,26 +131,41 @@ public class GenericClientR4Test {
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
int idx = 0;
- client.setEncoding(EncodingEnum.JSON);
- client.search()
- .forResource("Device")
- .returnBundle(Bundle.class)
- .execute();
-
- assertEquals("http://example.com/fhir/Device?_format=json", UrlUtil.unescape(capt.getAllValues().get(idx).getURI().toString()));
- assertEquals("application/fhir+json;q=1.0, application/json+fhir;q=0.9", capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_ACCEPT).getValue());
- idx++;
+ // Custom accept value
client.setEncoding(EncodingEnum.XML);
client.search()
.forResource("Device")
.returnBundle(Bundle.class)
+ .accept("application/json")
.execute();
-
- assertEquals("http://example.com/fhir/Device?_format=xml", UrlUtil.unescape(capt.getAllValues().get(idx).getURI().toString()));
- assertEquals("application/fhir+xml;q=1.0, application/xml+fhir;q=0.9", capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_ACCEPT).getValue());
+ assertEquals("http://example.com/fhir/Device", UrlUtil.unescape(capt.getAllValues().get(idx).getURI().toString()));
+ assertEquals("application/json", capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_ACCEPT).getValue());
idx++;
+ // Empty accept value
+
+ client.setEncoding(EncodingEnum.XML);
+ client.search()
+ .forResource("Device")
+ .returnBundle(Bundle.class)
+ .accept("")
+ .execute();
+ assertEquals("http://example.com/fhir/Device?_format=xml", UrlUtil.unescape(capt.getAllValues().get(idx).getURI().toString()));
+ assertEquals(Constants.HEADER_ACCEPT_VALUE_XML_NON_LEGACY, capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_ACCEPT).getValue());
+ idx++;
+
+ // Null accept value
+
+ client.setEncoding(EncodingEnum.XML);
+ client.search()
+ .forResource("Device")
+ .returnBundle(Bundle.class)
+ .accept(null)
+ .execute();
+ assertEquals("http://example.com/fhir/Device?_format=xml", UrlUtil.unescape(capt.getAllValues().get(idx).getURI().toString()));
+ assertEquals(Constants.HEADER_ACCEPT_VALUE_XML_NON_LEGACY, capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_ACCEPT).getValue());
+ idx++;
}
@Test
@@ -217,7 +233,7 @@ public class GenericClientR4Test {
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
Binary bin = new Binary();
- bin.setContent(new byte[] {0, 1, 2, 3, 4});
+ bin.setContent(new byte[]{0, 1, 2, 3, 4});
client.create().resource(bin).execute();
ourLog.info(Arrays.asList(capt.getAllValues().get(0).getAllHeaders()).toString());
@@ -227,7 +243,7 @@ public class GenericClientR4Test {
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());
+ assertArrayEquals(new byte[]{0, 1, 2, 3, 4}, ourCtx.newXmlParser().parseResource(Binary.class, extractBodyAsString(capt)).getContent());
}
@@ -306,7 +322,7 @@ public class GenericClientR4Test {
when(myHttpResponse.getAllHeaders()).thenAnswer(new Answer() {
@Override
public Header[] answer(InvocationOnMock theInvocation) {
- return new Header[] {new BasicHeader(Constants.HEADER_LOCATION, "http://foo.com/base/Patient/222/_history/3")};
+ return new Header[]{new BasicHeader(Constants.HEADER_LOCATION, "http://foo.com/base/Patient/222/_history/3")};
}
});
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
@@ -355,7 +371,7 @@ public class GenericClientR4Test {
when(myHttpResponse.getAllHeaders()).thenAnswer(new Answer() {
@Override
public Header[] answer(InvocationOnMock theInvocation) {
- return new Header[] {new BasicHeader(Constants.HEADER_LOCATION, "http://foo.com/base/Patient/222/_history/3")};
+ return new Header[]{new BasicHeader(Constants.HEADER_LOCATION, "http://foo.com/base/Patient/222/_history/3")};
}
});
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
@@ -880,6 +896,85 @@ public class GenericClientR4Test {
assertEquals("true", ((IPrimitiveType>) output.getParameterFirstRep().getValue()).getValueAsString());
}
+ /**
+ * Invoke an operation that returns HTML
+ * as a response (a HAPI FHIR server could accomplish this by returning
+ * a Binary resource)
+ */
+ @Test
+ public void testOperationReturningArbitraryBinaryContentTextual() throws Exception {
+ IParser p = ourCtx.newXmlParser();
+
+ Parameters inputParams = new Parameters();
+ inputParams.addParameter().setName("name").setValue(new BooleanType(true));
+
+ final String respString = "VALUE";
+ 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", "text/html"));
+ when(myHttpResponse.getEntity().getContent()).thenAnswer(t -> new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")));
+ when(myHttpResponse.getAllHeaders()).thenReturn(new Header[]{
+ new BasicHeader("content-type", "text/html")
+ });
+
+ IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
+
+ MethodOutcome result = client
+ .operation()
+ .onServer()
+ .named("opname")
+ .withParameters(inputParams)
+ .returnMethodOutcome()
+ .execute();
+
+ assertEquals(Binary.class, result.getResource().getClass());
+ Binary binary = (Binary) result.getResource();
+ assertEquals(respString, new String(binary.getContent(), Charsets.UTF_8));
+ assertEquals("text/html", binary.getContentType());
+
+ assertEquals("http://example.com/fhir/$opname", capt.getAllValues().get(0).getURI().toASCIIString());
+ }
+
+ /**
+ * Invoke an operation that returns HTML
+ * as a response (a HAPI FHIR server could accomplish this by returning
+ * a Binary resource)
+ */
+ @Test
+ public void testOperationReturningArbitraryBinaryContentNonTextual() throws Exception {
+ IParser p = ourCtx.newXmlParser();
+
+ Parameters inputParams = new Parameters();
+ inputParams.addParameter().setName("name").setValue(new BooleanType(true));
+
+ final byte[] respBytes = new byte[]{0,1,2,3,4,5,6,7,8,9,100};
+ 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", "application/weird-numbers"));
+ when(myHttpResponse.getEntity().getContent()).thenAnswer(t -> new ByteArrayInputStream(respBytes));
+ when(myHttpResponse.getAllHeaders()).thenReturn(new Header[]{
+ new BasicHeader("content-Type", "application/weird-numbers")
+ });
+
+ IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
+
+ MethodOutcome result = client
+ .operation()
+ .onServer()
+ .named("opname")
+ .withParameters(inputParams)
+ .returnMethodOutcome()
+ .execute();
+
+ assertEquals(Binary.class, result.getResource().getClass());
+ Binary binary = (Binary) result.getResource();
+ assertEquals("application/weird-numbers", binary.getContentType());
+ assertArrayEquals(respBytes, binary.getContent());
+ assertEquals("http://example.com/fhir/$opname", capt.getAllValues().get(0).getURI().toASCIIString());
+ }
+
@Test
public void testOperationType() throws Exception {
IParser p = ourCtx.newXmlParser();
@@ -2036,7 +2131,7 @@ public class GenericClientR4Test {
when(myHttpResponse.getAllHeaders()).thenAnswer(new Answer() {
@Override
public Header[] answer(InvocationOnMock theInvocation) {
- return new Header[] {new BasicHeader(Constants.HEADER_LOCATION, "http://foo.com/base/Patient/222/_history/3")};
+ return new Header[]{new BasicHeader(Constants.HEADER_LOCATION, "http://foo.com/base/Patient/222/_history/3")};
}
});
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
@@ -2084,7 +2179,7 @@ public class GenericClientR4Test {
when(myHttpResponse.getAllHeaders()).thenAnswer(new Answer() {
@Override
public Header[] answer(InvocationOnMock theInvocation) {
- return new Header[] {new BasicHeader(Constants.HEADER_LOCATION, "http://foo.com/base/Patient/222/_history/3")};
+ return new Header[]{new BasicHeader(Constants.HEADER_LOCATION, "http://foo.com/base/Patient/222/_history/3")};
}
});
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
@@ -2137,7 +2232,7 @@ public class GenericClientR4Test {
Binary bin = new Binary();
bin.setContentType("application/foo");
- bin.setContent(new byte[] {0, 1, 2, 3, 4});
+ bin.setContent(new byte[]{0, 1, 2, 3, 4});
client.create().resource(bin).execute();
ourLog.info(Arrays.asList(capt.getAllValues().get(0).getAllHeaders()).toString());
@@ -2147,7 +2242,7 @@ public class GenericClientR4Test {
assertEquals("application/foo", capt.getAllValues().get(0).getHeaders("Content-Type")[0].getValue());
assertEquals(Constants.HEADER_ACCEPT_VALUE_XML_OR_JSON_NON_LEGACY, capt.getAllValues().get(0).getHeaders("Accept")[0].getValue());
- assertArrayEquals(new byte[] {0, 1, 2, 3, 4}, extractBodyAsByteArray(capt));
+ assertArrayEquals(new byte[]{0, 1, 2, 3, 4}, extractBodyAsByteArray(capt));
}
@@ -2215,7 +2310,7 @@ public class GenericClientR4Test {
when(myHttpResponse.getAllHeaders()).thenAnswer(new Answer() {
@Override
public Header[] answer(InvocationOnMock theInvocation) {
- return new Header[] {};
+ return new Header[]{};
}
});
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/ResponseHighlightingInterceptorTest.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/ResponseHighlightingInterceptorTest.java
index 02d7dfb11ab..d22aa144684 100644
--- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/ResponseHighlightingInterceptorTest.java
+++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/ResponseHighlightingInterceptorTest.java
@@ -2,10 +2,7 @@ package ca.uhn.fhir.rest.server.interceptor;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.api.BundleInclusionRule;
-import ca.uhn.fhir.rest.annotation.IdParam;
-import ca.uhn.fhir.rest.annotation.Read;
-import ca.uhn.fhir.rest.annotation.RequiredParam;
-import ca.uhn.fhir.rest.annotation.Search;
+import ca.uhn.fhir.rest.annotation.*;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.api.RequestTypeEnum;
@@ -64,6 +61,23 @@ public class ResponseHighlightingInterceptorTest {
ourInterceptor.setShowResponseHeaders(new ResponseHighlighterInterceptor().isShowResponseHeaders());
}
+ /**
+ * Return a Binary response type - Client accepts text/html but is not a browser
+ */
+ @Test
+ public void testBinaryOperationHtmlResponseFromProvider() throws Exception {
+ HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/html/$binaryOp");
+ httpGet.addHeader("Accept", "text/html");
+
+ CloseableHttpResponse status = ourClient.execute(httpGet);
+ String responseContent = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
+ status.close();
+ assertEquals(200, status.getStatusLine().getStatusCode());
+ assertEquals("text/html", status.getFirstHeader("content-type").getValue());
+ assertEquals("DATA", responseContent);
+ assertEquals("Attachment;", status.getFirstHeader("Content-Disposition").getValue());
+ }
+
@Test
public void testBinaryReadAcceptBrowser() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Binary/foo");
@@ -848,6 +862,21 @@ public class ResponseHighlightingInterceptorTest {
return Collections.singletonList(p);
}
+ @Operation(name="binaryOp", idempotent = true)
+ public Binary binaryOp(@IdParam IdType theId) {
+ Binary retVal = new Binary();
+ retVal.setId(theId);
+ if (theId.getIdPart().equals("html")) {
+ retVal.setContent("DATA".getBytes(Charsets.UTF_8));
+ retVal.setContentType("text/html");
+ }else {
+ retVal.setContent(new byte[]{1, 2, 3, 4});
+ retVal.setContentType(theId.getIdPart());
+ }
+ return retVal;
+ }
+
+
Map getIdToPatient() {
Map idToPatient = new HashMap<>();
{
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 226cabe18d0..4c0ea680603 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -19,7 +19,7 @@
The JPA server $expunge operation could sometimes fail to expunge if
another resource linked to a resource that was being
- expunged. This has been corrected. In addition, the $expunge operation
+ expunged. This has been corrected. In addition, the $expunge operation
has been refactored to use smaller chunks of work
within a single DB transaction. This improves performance and reduces contention when
performing large expunge workloads.
@@ -41,11 +41,22 @@
The ResponseHighlighterInterceptor now declines to handle Binary responses
- provided as a response from extended operations. In other words if the
+ provided as a response from extended operations. In other words if the
operation $foo returns a Binary resource, the ResponseHighliterInterceptor will
not provide syntax highlighting on the response. This was previously the case for
the /Binary endpoint, but not for other binary responses.
+
+ FHIR Parser now has an additional overload of the
+ parseResource]]> method that accepts
+ an InputStream instead of a Reader as the source.
+
+
+ FHIR Fluent/Generic Client now has a new return option called
+ returnMethodOutcome]]> which can be
+ used to return a raw response. This is handy for invoking operations
+ that might return arbitrary binary content.
+
@@ -77,7 +88,7 @@
The module which deletes stale searches has been modified so that it deletes very large
searches (searches with 10000+ results in the query cache) in smaller batches, in order
- to avoid having very long running delete operations occupying database connections for a
+ to avoid having very long running delete operations occupying database connections for a
long time or timing out.
@@ -89,9 +100,9 @@
A new operation has been added to the JPA server called
$trigger-subscription]]>. This can
be used to cause a transaction to redeliver a resource that previously triggered.
- See
+ See
this link]]>
- for a description of how this feature works. Note that you must add the
+ for a description of how this feature works. Note that you must add the
SubscriptionRetriggeringProvider as shown in the sample project
here.]]>