From 07078e4ce3e6a52a4a8dcb86a5d2a4ba5ca36bc0 Mon Sep 17 00:00:00 2001
From: jamesagnew
Date: Sun, 29 Nov 2015 15:03:28 -0500
Subject: [PATCH] Add GZip support to client and server for client-to-server
uploads
---
.../src/main/java/example/ClientExamples.java | 13 +
.../ca/uhn/fhir/rest/annotation/GetPage.java | 20 +
.../uhn/fhir/rest/annotation/PageIdParam.java | 20 +
.../interceptor/GZipContentInterceptor.java | 56 ++
.../uhn/fhir/rest/method/IRestfulHeader.java | 20 +
.../fhir/rest/method/PageMethodBinding.java | 20 +
.../ca/uhn/fhir/rest/method/ParseAction.java | 20 +
.../uhn/fhir/rest/method/RequestDetails.java | 101 +--
.../fhir/rest/param/ResourceParameter.java | 6 +-
.../fhir/rest/server/IRestfulResponse.java | 20 +
.../rest/server/IRestfulServerDefaults.java | 20 +
.../fhir/rest/server/IRestfulServerUtil.java | 20 +
.../ca/uhn/fhir/rest/server/PageProvider.java | 20 +
.../uhn/fhir/rest/server/RestfulResponse.java | 20 +
.../uhn/fhir/rest/server/RestfulServer.java | 858 +++++++++---------
.../server/RestulfulServerConfiguration.java | 20 +
.../server/servlet/ServletRequestDetails.java | 37 +
.../servlet/ServletRestfulResponse.java | 20 +
.../ca/uhn/fhir/cli/ExampleDataUploader.java | 80 +-
hapi-fhir-cli/tmp.txt.gz | Bin 0 -> 2853844 bytes
...ompressOutgoingContentInterceptorTest.java | 99 ++
hapi-tinder-plugin/pom.xml | 21 +-
.../fhir/model/dstu21/composite/AgeDt.java | 30 +
.../composite/BoundCodeableConceptDt.java | 141 +++
.../dstu21/composite/CodeableConceptDt.java | 229 +++++
.../fhir/model/dstu21/composite/CodingDt.java | 426 +++++++++
.../model/dstu21/composite/ContainedDt.java | 54 ++
.../fhir/model/dstu21/composite/CountDt.java | 29 +
.../model/dstu21/composite/DistanceDt.java | 30 +
.../model/dstu21/composite/DurationDt.java | 29 +
.../fhir/model/dstu21/composite/MoneyDt.java | 29 +
.../model/dstu21/composite/NarrativeDt.java | 142 +++
.../model/dstu21/composite/QuantityDt.java | 523 +++++++++++
.../dstu21/composite/ResourceReferenceDt.java | 256 ++++++
.../dstu21/composite/SimpleQuantityDt.java | 98 ++
src/changes/changes.xml | 6 +
src/site/xdoc/doc_rest_client_interceptor.xml | 16 +
37 files changed, 3057 insertions(+), 492 deletions(-)
create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/interceptor/GZipContentInterceptor.java
create mode 100644 hapi-fhir-cli/tmp.txt.gz
create mode 100644 hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/client/interceptor/CompressOutgoingContentInterceptorTest.java
create mode 100644 hapi-tinder-plugin/src/main/java/ca/uhn/fhir/model/dstu21/composite/AgeDt.java
create mode 100644 hapi-tinder-plugin/src/main/java/ca/uhn/fhir/model/dstu21/composite/BoundCodeableConceptDt.java
create mode 100644 hapi-tinder-plugin/src/main/java/ca/uhn/fhir/model/dstu21/composite/CodeableConceptDt.java
create mode 100644 hapi-tinder-plugin/src/main/java/ca/uhn/fhir/model/dstu21/composite/CodingDt.java
create mode 100644 hapi-tinder-plugin/src/main/java/ca/uhn/fhir/model/dstu21/composite/ContainedDt.java
create mode 100644 hapi-tinder-plugin/src/main/java/ca/uhn/fhir/model/dstu21/composite/CountDt.java
create mode 100644 hapi-tinder-plugin/src/main/java/ca/uhn/fhir/model/dstu21/composite/DistanceDt.java
create mode 100644 hapi-tinder-plugin/src/main/java/ca/uhn/fhir/model/dstu21/composite/DurationDt.java
create mode 100644 hapi-tinder-plugin/src/main/java/ca/uhn/fhir/model/dstu21/composite/MoneyDt.java
create mode 100644 hapi-tinder-plugin/src/main/java/ca/uhn/fhir/model/dstu21/composite/NarrativeDt.java
create mode 100644 hapi-tinder-plugin/src/main/java/ca/uhn/fhir/model/dstu21/composite/QuantityDt.java
create mode 100644 hapi-tinder-plugin/src/main/java/ca/uhn/fhir/model/dstu21/composite/ResourceReferenceDt.java
create mode 100644 hapi-tinder-plugin/src/main/java/ca/uhn/fhir/model/dstu21/composite/SimpleQuantityDt.java
diff --git a/examples/src/main/java/example/ClientExamples.java b/examples/src/main/java/example/ClientExamples.java
index 5725e8b161c..5f191520a59 100644
--- a/examples/src/main/java/example/ClientExamples.java
+++ b/examples/src/main/java/example/ClientExamples.java
@@ -7,6 +7,7 @@ import ca.uhn.fhir.rest.client.api.IBasicClient;
import ca.uhn.fhir.rest.client.interceptor.BasicAuthInterceptor;
import ca.uhn.fhir.rest.client.interceptor.BearerTokenAuthInterceptor;
import ca.uhn.fhir.rest.client.interceptor.CookieInterceptor;
+import ca.uhn.fhir.rest.client.interceptor.GZipContentInterceptor;
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
import ca.uhn.fhir.rest.server.EncodingEnum;
@@ -90,6 +91,18 @@ public class ClientExamples {
// END SNIPPET: cookie
}
+ @SuppressWarnings("unused")
+ public void gzip() {
+ // START SNIPPET: gzip
+ // Create a context and get the client factory so it can be configured
+ FhirContext ctx = FhirContext.forDstu2();
+ IRestfulClientFactory clientFactory = ctx.getRestfulClientFactory();
+
+ // Register the interceptor with your client (either style)
+ IPatientClient annotationClient = ctx.newRestfulClient(IPatientClient.class, "http://localhost:9999/fhir");
+ annotationClient.registerInterceptor(new GZipContentInterceptor());
+ // END SNIPPET: gzip
+ }
@SuppressWarnings("unused")
public void createSecurityBearer() {
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/GetPage.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/GetPage.java
index b347033d74c..bcf1bd87633 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/GetPage.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/GetPage.java
@@ -1,5 +1,25 @@
package ca.uhn.fhir.rest.annotation;
+/*
+ * #%L
+ * HAPI FHIR - Core Library
+ * %%
+ * Copyright (C) 2014 - 2015 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 java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/PageIdParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/PageIdParam.java
index ed5a493fd99..4560eeaa648 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/PageIdParam.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/PageIdParam.java
@@ -1,5 +1,25 @@
package ca.uhn.fhir.rest.annotation;
+/*
+ * #%L
+ * HAPI FHIR - Core Library
+ * %%
+ * Copyright (C) 2014 - 2015 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 java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/interceptor/GZipContentInterceptor.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/interceptor/GZipContentInterceptor.java
new file mode 100644
index 00000000000..c4d73341395
--- /dev/null
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/interceptor/GZipContentInterceptor.java
@@ -0,0 +1,56 @@
+package ca.uhn.fhir.rest.client.interceptor;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.zip.GZIPOutputStream;
+
+import org.apache.http.Header;
+import org.apache.http.HttpEntityEnclosingRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpRequestBase;
+import org.apache.http.entity.ByteArrayEntity;
+
+import ca.uhn.fhir.rest.client.IClientInterceptor;
+import ca.uhn.fhir.rest.server.Constants;
+
+/**
+ * Client interceptor which GZip compresses outgoing (POST/PUT) contents being uploaded
+ * from the client to the server. This can improve performance by reducing network
+ * load time.
+ */
+public class GZipContentInterceptor implements IClientInterceptor {
+ private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(GZipContentInterceptor.class);
+
+ @Override
+ public void interceptRequest(HttpRequestBase theRequest) {
+ if (theRequest instanceof HttpEntityEnclosingRequest) {
+ Header[] encodingHeaders = theRequest.getHeaders(Constants.HEADER_CONTENT_ENCODING);
+ if (encodingHeaders == null || encodingHeaders.length == 0) {
+ HttpEntityEnclosingRequest req = (HttpEntityEnclosingRequest)theRequest;
+
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ GZIPOutputStream gos;
+ try {
+ gos = new GZIPOutputStream(bos);
+ req.getEntity().writeTo(gos);
+ gos.finish();
+ } catch (IOException e) {
+ ourLog.warn("Failed to GZip outgoing content", e);
+ return;
+ }
+
+ byte[] byteArray = bos.toByteArray();
+ ByteArrayEntity newEntity = new ByteArrayEntity(byteArray);
+ req.setEntity(newEntity);
+ req.addHeader(Constants.HEADER_CONTENT_ENCODING, "gzip");
+ }
+ }
+
+ }
+
+ @Override
+ public void interceptResponse(HttpResponse theResponse) throws IOException {
+ // nothing
+ }
+
+}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/IRestfulHeader.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/IRestfulHeader.java
index 2abce5282a8..04b94b032b4 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/IRestfulHeader.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/IRestfulHeader.java
@@ -1,5 +1,25 @@
package ca.uhn.fhir.rest.method;
+/*
+ * #%L
+ * HAPI FHIR - Core Library
+ * %%
+ * Copyright (C) 2014 - 2015 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%
+ */
+
public interface IRestfulHeader {
}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/PageMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/PageMethodBinding.java
index a19d5459790..2eaa9c97a07 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/PageMethodBinding.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/PageMethodBinding.java
@@ -1,5 +1,25 @@
package ca.uhn.fhir.rest.method;
+/*
+ * #%L
+ * HAPI FHIR - Core Library
+ * %%
+ * Copyright (C) 2014 - 2015 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 static org.apache.commons.lang3.StringUtils.isBlank;
import java.lang.reflect.Method;
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ParseAction.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ParseAction.java
index a3b3e29cc14..463c4b3fda8 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ParseAction.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ParseAction.java
@@ -1,5 +1,25 @@
package ca.uhn.fhir.rest.method;
+/*
+ * #%L
+ * HAPI FHIR - Core Library
+ * %%
+ * Copyright (C) 2014 - 2015 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 java.io.IOException;
import java.io.Writer;
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/RequestDetails.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/RequestDetails.java
index 61b00ae6a2d..ff60bbe59f4 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/RequestDetails.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/RequestDetails.java
@@ -39,7 +39,7 @@ import ca.uhn.fhir.rest.server.IRestfulServerDefaults;
public abstract class RequestDetails {
- private byte[] myRequestContents;
+ private byte[] myRequestContents;
private String myCompartmentName;
private String myCompleteUrl;
private String myFhirServerBase;
@@ -54,7 +54,7 @@ public abstract class RequestDetails {
private String mySecondaryOperation;
private Map> myUnqualifiedToQualifiedNames;
private IRestfulResponse myResponse;
-
+
public String getCompartmentName() {
return myCompartmentName;
}
@@ -65,6 +65,7 @@ public abstract class RequestDetails {
/**
* The fhir server base url, independant of the query being executed
+ *
* @return the fhir server base url
*/
public String getFhirServerBase() {
@@ -192,66 +193,64 @@ public abstract class RequestDetails {
mySecondaryOperation = theSecondaryOperation;
}
- public IRestfulResponse getResponse() {
- return myResponse;
- }
+ public IRestfulResponse getResponse() {
+ return myResponse;
+ }
- public void setResponse(IRestfulResponse theResponse) {
- this.myResponse = theResponse;
- }
+ public void setResponse(IRestfulResponse theResponse) {
+ this.myResponse = theResponse;
+ }
- public abstract String getHeader(String name);
-
- public final byte[] loadRequestContents(RequestDetails theRequest) {
+ public abstract String getHeader(String name);
+
+ public final byte[] loadRequestContents() {
if (myRequestContents == null) {
myRequestContents = getByteStreamRequestContents();
}
return myRequestContents;
}
- protected abstract byte[] getByteStreamRequestContents();
+ protected abstract byte[] getByteStreamRequestContents();
- public abstract List getHeaders(String name);
+ public abstract List getHeaders(String name);
- /**
- * Retrieves the body of the request as character data using
- * a BufferedReader. The reader translates the character
- * data according to the character encoding used on the body.
- * Either this method or {@link #getInputStream} may be called to read the
- * body, not both.
- *
- * @return a Reader containing the body of the request
- *
- * @exception UnsupportedEncodingException if the character set encoding
- * used is not supported and the text cannot be decoded
- *
- * @exception IllegalStateException if {@link #getInputStream} method
- * has been called on this request
- *
- * @exception IOException if an input or output exception occurred
- *
- * @see javax.servlet.http.HttpServletRequest#getInputStream
- */
- public abstract Reader getReader() throws IOException;
+ /**
+ * Retrieves the body of the request as character data using a BufferedReader. The reader translates the
+ * character data according to the character encoding used on the body. Either this method or {@link #getInputStream}
+ * may be called to read the body, not both.
+ *
+ * @return a Reader containing the body of the request
+ *
+ * @exception UnsupportedEncodingException
+ * if the character set encoding used is not supported and the text cannot be decoded
+ *
+ * @exception IllegalStateException
+ * if {@link #getInputStream} method has been called on this request
+ *
+ * @exception IOException
+ * if an input or output exception occurred
+ *
+ * @see javax.servlet.http.HttpServletRequest#getInputStream
+ */
+ public abstract Reader getReader() throws IOException;
- /**
- * Retrieves the body of the request as binary data.
- * Either this method or {@link #getReader} may be called to
- * read the body, not both.
- *
- * @return a {@link InputStream} object containing
- * the body of the request
- *
- * @exception IllegalStateException if the {@link #getReader} method
- * has already been called for this request
- *
- * @exception IOException if an input or output exception occurred
- */
- public abstract InputStream getInputStream() throws IOException;
+ /**
+ * Retrieves the body of the request as binary data. Either this method or {@link #getReader} may be called to read
+ * the body, not both.
+ *
+ * @return a {@link InputStream} object containing the body of the request
+ *
+ * @exception IllegalStateException
+ * if the {@link #getReader} method has already been called for this request
+ *
+ * @exception IOException
+ * if an input or output exception occurred
+ */
+ public abstract InputStream getInputStream() throws IOException;
- /**
- * Returns the server base URL (with no trailing '/') for a given request
- */
- public abstract String getServerBaseForRequest();
+ /**
+ * Returns the server base URL (with no trailing '/') for a given request
+ */
+ public abstract String getServerBaseForRequest();
}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ResourceParameter.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ResourceParameter.java
index f626ae783c2..fa86a054849 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ResourceParameter.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ResourceParameter.java
@@ -122,7 +122,7 @@ public class ResourceParameter implements IParameter {
}
public static Reader createRequestReader(RequestDetails theRequest, Charset charset) {
- Reader requestReader = new InputStreamReader(new ByteArrayInputStream(theRequest.loadRequestContents(theRequest)), charset);
+ Reader requestReader = new InputStreamReader(new ByteArrayInputStream(theRequest.loadRequestContents()), charset);
return requestReader;
}
@@ -179,7 +179,7 @@ public class ResourceParameter implements IParameter {
String msg = ctx.getLocalizer().getMessage(ResourceParameter.class, "noContentTypeInRequest", restOperationType);
throw new InvalidRequestException(msg);
} else {
- requestReader = new InputStreamReader(new ByteArrayInputStream(theRequest.loadRequestContents(theRequest)), charset);
+ requestReader = new InputStreamReader(new ByteArrayInputStream(theRequest.loadRequestContents()), charset);
}
} else {
String msg = ctx.getLocalizer().getMessage(ResourceParameter.class, "invalidContentTypeInRequest", ctValue, restOperationType);
@@ -219,7 +219,7 @@ public class ResourceParameter implements IParameter {
String ct = theRequest.getHeader(Constants.HEADER_CONTENT_TYPE);
IBaseBinary binary = (IBaseBinary) ctx.getResourceDefinition("Binary").newInstance();
binary.setContentType(ct);
- binary.setContent(theRequest.loadRequestContents(theRequest));
+ binary.setContent(theRequest.loadRequestContents());
retVal = binary;
} else {
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/IRestfulResponse.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/IRestfulResponse.java
index afbb3000fd3..a88aa73711b 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/IRestfulResponse.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/IRestfulResponse.java
@@ -1,5 +1,25 @@
package ca.uhn.fhir.rest.server;
+/*
+ * #%L
+ * HAPI FHIR - Core Library
+ * %%
+ * Copyright (C) 2014 - 2015 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 java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/IRestfulServerDefaults.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/IRestfulServerDefaults.java
index 6029f2ebeb5..b97395a69d9 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/IRestfulServerDefaults.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/IRestfulServerDefaults.java
@@ -1,5 +1,25 @@
package ca.uhn.fhir.rest.server;
+/*
+ * #%L
+ * HAPI FHIR - Core Library
+ * %%
+ * Copyright (C) 2014 - 2015 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.FhirContext;
public interface IRestfulServerDefaults {
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/IRestfulServerUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/IRestfulServerUtil.java
index b55330d0836..abe86bbfee8 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/IRestfulServerUtil.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/IRestfulServerUtil.java
@@ -1,5 +1,25 @@
package ca.uhn.fhir.rest.server;
+/*
+ * #%L
+ * HAPI FHIR - Core Library
+ * %%
+ * Copyright (C) 2014 - 2015 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 org.hl7.fhir.instance.model.api.IBaseResource;
import ca.uhn.fhir.rest.method.BaseMethodBinding;
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/PageProvider.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/PageProvider.java
index e6dc7af9992..3af59f57946 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/PageProvider.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/PageProvider.java
@@ -1,5 +1,25 @@
package ca.uhn.fhir.rest.server;
+/*
+ * #%L
+ * HAPI FHIR - Core Library
+ * %%
+ * Copyright (C) 2014 - 2015 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.model.api.IResource;
import ca.uhn.fhir.rest.annotation.GetPage;
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulResponse.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulResponse.java
index 7d30b9f7959..e79840480f2 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulResponse.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulResponse.java
@@ -1,5 +1,25 @@
package ca.uhn.fhir.rest.server;
+/*
+ * #%L
+ * HAPI FHIR - Core Library
+ * %%
+ * Copyright (C) 2014 - 2015 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 java.io.IOException;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java
index 9ac8c538d2f..3e2fd30ac04 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java
@@ -80,13 +80,13 @@ import ca.uhn.fhir.util.VersionUtil;
public class RestfulServer extends HttpServlet implements IRestfulServer {
- private static final ExceptionHandlingInterceptor DEFAULT_EXCEPTION_HANDLER = new ExceptionHandlingInterceptor();
- private static final long serialVersionUID = 1L;
/**
* Default setting for {@link #setETagSupport(ETagSupportEnum) ETag Support}: {@link ETagSupportEnum#ENABLED}
*/
public static final ETagSupportEnum DEFAULT_ETAG_SUPPORT = ETagSupportEnum.ENABLED;
+ private static final ExceptionHandlingInterceptor DEFAULT_EXCEPTION_HANDLER = new ExceptionHandlingInterceptor();
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestfulServer.class);
+ private static final long serialVersionUID = 1L;
private AddProfileTagEnum myAddProfileTag;
private BundleInclusionRule myBundleInclusionRule = BundleInclusionRule.BASED_ON_INCLUDES;
private boolean myDefaultPrettyPrint = false;
@@ -97,9 +97,9 @@ public class RestfulServer extends HttpServlet implements IRestfulServer myInterceptors = new ArrayList();
private IPagingProvider myPagingProvider;
private final List
*/
public void addHeadersToResponse(HttpServletResponse theHttpResponse) {
theHttpResponse.addHeader("X-Powered-By", "HAPI FHIR " + VersionUtil.getVersion() + " RESTful Server");
}
+ private void addLocationHeader(RequestDetails theRequest, HttpServletResponse theResponse, MethodOutcome response, String headerLocation, String resourceName) {
+ StringBuilder b = new StringBuilder();
+ b.append(theRequest.getFhirServerBase());
+ b.append('/');
+ b.append(resourceName);
+ b.append('/');
+ b.append(response.getId().getIdPart());
+ if (response.getId().hasVersionIdPart()) {
+ b.append("/" + Constants.PARAM_HISTORY + "/");
+ b.append(response.getId().getVersionIdPart());
+ } else if (response.getVersionId() != null && response.getVersionId().isEmpty() == false) {
+ b.append("/" + Constants.PARAM_HISTORY + "/");
+ b.append(response.getVersionId().getValue());
+ }
+ theResponse.addHeader(headerLocation, b.toString());
+
+ }
+
private void assertProviderIsValid(Object theNext) throws ConfigurationException {
if (Modifier.isPublic(theNext.getClass().getModifiers()) == false) {
throw new ConfigurationException("Can not use provider '" + theNext.getClass() + "' - Class must be public");
}
}
+ public RestulfulServerConfiguration createConfiguration() {
+ RestulfulServerConfiguration result = new RestulfulServerConfiguration();
+ result.setResourceBindings(getResourceBindings());
+ result.setServerBindings(getServerBindings());
+ result.setImplementationDescription(getImplementationDescription());
+ result.setServerVersion(getServerVersion());
+ result.setServerName(getServerName());
+ result.setFhirContext(getFhirContext());
+ result.setServerAddressStrategy(myServerAddressStrategy);
+ InputStream inputStream = null;
+ try {
+ inputStream = getClass().getResourceAsStream("/META-INF/MANIFEST.MF");
+ if (inputStream != null) {
+ Manifest manifest = new Manifest(inputStream);
+ result.setConformanceDate(manifest.getMainAttributes().getValue("Build-Time"));
+ }
+ } catch (IOException e) {
+ // fall through
+ } finally {
+ if (inputStream != null) {
+ IOUtils.closeQuietly(inputStream);
+ }
+ }
+ return result;
+ }
+
@Override
public void destroy() {
if (getResourceProviders() != null) {
@@ -159,6 +213,38 @@ public class RestfulServer extends HttpServlet implements IRestfulServer determineResourceMethod(RequestDetails requestDetails, String requestPath) {
+ RequestTypeEnum requestType = requestDetails.getRequestType();
+
+ ResourceBinding resourceBinding = null;
+ BaseMethodBinding> resourceMethod = null;
+ String resourceName = requestDetails.getResourceName();
+ if (Constants.URL_TOKEN_METADATA.equals(resourceName) || requestType == RequestTypeEnum.OPTIONS) {
+ resourceMethod = myServerConformanceMethod;
+ } else if (resourceName == null) {
+ resourceBinding = myServerBinding;
+ } else {
+ resourceBinding = myResourceNameToBinding.get(resourceName);
+ if (resourceBinding == null) {
+ throw new InvalidRequestException("Unknown resource type '" + resourceName + "' - Server knows how to handle: " + myResourceNameToBinding.keySet());
+ }
+ }
+
+ if (resourceMethod == null) {
+ if (resourceBinding != null) {
+ resourceMethod = resourceBinding.getMethod(requestDetails);
+ }
+ }
+ if (resourceMethod == null) {
+ if (isBlank(requestPath)) {
+ throw new InvalidRequestException(myFhirContext.getLocalizer().getMessage(RestfulServer.class, "rootRequest"));
+ } else {
+ throw new InvalidRequestException(myFhirContext.getLocalizer().getMessage(RestfulServer.class, "unknownMethod", requestType.name(), requestPath, requestDetails.getParameters().keySet()));
+ }
+ }
+ return resourceMethod;
+ }
+
@Override
protected void doDelete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
handleRequest(RequestTypeEnum.DELETE, request, response);
@@ -198,46 +284,6 @@ public class RestfulServer extends HttpServlet implements IRestfulServer clazz = theProvider.getClass();
- Class> supertype = clazz.getSuperclass();
- Collection resourceNames = new ArrayList();
- while (!Object.class.equals(supertype)) {
- removeResourceMethods(theProvider, supertype, resourceNames);
- supertype = supertype.getSuperclass();
- }
- removeResourceMethods(theProvider, clazz, resourceNames);
- for (String resourceName : resourceNames) {
- myResourceNameToBinding.remove(resourceName);
- }
- }
-
- /*
- * Collect the set of RESTful methods for a single class
- * when it is being unregistered
- */
- private void removeResourceMethods(Object theProvider, Class> clazz, Collection resourceNames) throws ConfigurationException {
- for (Method m : ReflectionUtil.getDeclaredMethods(clazz)) {
- BaseMethodBinding> foundMethodBinding = BaseMethodBinding.bindMethod(m, getFhirContext(), theProvider);
- if (foundMethodBinding == null) {
- continue; // not a bound method
- }
- if (foundMethodBinding instanceof ConformanceMethodBinding) {
- myServerConformanceMethod = null;
- continue;
- }
- String resourceName = foundMethodBinding.getResourceName();
- if (!resourceNames.contains(resourceName)) {
- resourceNames.add(resourceName);
- }
- }
- }
-
private void findResourceMethods(Object theProvider) throws Exception {
ourLog.info("Scanning type for RESTful methods: {}", theProvider.getClass());
@@ -330,8 +376,9 @@ public class RestfulServer extends HttpServlet implements IRestfulServer_format URL parameter, or with an Accept header
- * in the request. The default is {@link EncodingEnum#XML}. Will not return null.
+ * Returns the default encoding to return (XML/JSON) if an incoming request does not specify a preference (either
+ * with the _format URL parameter, or with an Accept header in the request. The default is
+ * {@link EncodingEnum#XML}. Will not return null.
*/
@Override
public EncodingEnum getDefaultResponseEncoding() {
@@ -344,8 +391,8 @@ public class RestfulServer extends HttpServlet implements IRestfulServer> getServerBindings() {
return myServerBinding.getMethodBindings();
}
/**
- * Returns the server conformance provider, which is the provider that is used to generate the server's conformance (metadata) statement if one has been explicitly defined.
+ * Returns the server conformance provider, which is the provider that is used to generate the server's conformance
+ * (metadata) statement if one has been explicitly defined.
*
- * By default, the ServerConformanceProvider for the declared version of FHIR is used, but this can be changed, or set to null to use the appropriate one for the given FHIR version.
+ * By default, the ServerConformanceProvider for the declared version of FHIR is used, but this can be changed, or
+ * set to null to use the appropriate one for the given FHIR version.
*
*/
public Object getServerConformanceProvider() {
@@ -446,7 +497,8 @@ public class RestfulServer extends HttpServlet implements IRestfulServer 0 && requestPath.charAt(0) == '/') {
@@ -540,19 +591,21 @@ public class RestfulServer extends HttpServlet implements IRestfulServer resourceMethod = determineResourceMethod(requestDetails, requestPath);
@@ -568,10 +621,12 @@ public class RestfulServer extends HttpServlet implements IRestfulServer= 0; i--) {
@@ -621,8 +678,8 @@ public class RestfulServer extends HttpServlet implements IRestfulServer determineResourceMethod(RequestDetails requestDetails, String requestPath) {
- RequestTypeEnum requestType = requestDetails.getRequestType();
-
- ResourceBinding resourceBinding = null;
- BaseMethodBinding> resourceMethod = null;
- String resourceName = requestDetails.getResourceName();
- if (Constants.URL_TOKEN_METADATA.equals(resourceName) || requestType == RequestTypeEnum.OPTIONS) {
- resourceMethod = myServerConformanceMethod;
- } else if (resourceName == null) {
- resourceBinding = myServerBinding;
- } else {
- resourceBinding = myResourceNameToBinding.get(resourceName);
- if (resourceBinding == null) {
- throw new InvalidRequestException("Unknown resource type '" + resourceName + "' - Server knows how to handle: " + myResourceNameToBinding.keySet());
+ /**
+ * Initializes the server. Note that this method is final to avoid accidentally introducing bugs in implementations,
+ * but subclasses may put initialization code in {@link #initialize()}, which is called immediately before beginning
+ * initialization of the restful server's internal init.
+ */
+ @Override
+ public final void init() throws ServletException {
+ myProviderRegistrationMutex.lock();
+ try {
+ initialize();
+
+ Object confProvider;
+ try {
+ ourLog.info("Initializing HAPI FHIR restful server running in " + getFhirContext().getVersion().getVersion().name() + " mode");
+
+ ProvidedResourceScanner providedResourceScanner = new ProvidedResourceScanner(getFhirContext());
+ providedResourceScanner.scanForProvidedResources(this);
+
+ Collection resourceProvider = getResourceProviders();
+ // 'true' tells registerProviders() that
+ // this call is part of initialization
+ registerProviders(resourceProvider, true);
+
+ Collection providers = getPlainProviders();
+ // 'true' tells registerProviders() that
+ // this call is part of initialization
+ registerProviders(providers, true);
+
+ findResourceMethods(getServerProfilesProvider());
+
+ confProvider = getServerConformanceProvider();
+ if (confProvider == null) {
+ confProvider = getFhirContext().getVersion().createServerConformanceProvider(this);
+ }
+ // findSystemMethods(confProvider);
+ findResourceMethods(confProvider);
+
+ } catch (Exception ex) {
+ ourLog.error("An error occurred while loading request handlers!", ex);
+ throw new ServletException("Failed to initialize FHIR Restful server", ex);
+ }
+
+ ourLog.trace("Invoking provider initialize methods");
+ if (getResourceProviders() != null) {
+ for (IResourceProvider iResourceProvider : getResourceProviders()) {
+ invokeInitialize(iResourceProvider);
+ }
+ }
+ if (confProvider != null) {
+ invokeInitialize(confProvider);
+ }
+ if (getPlainProviders() != null) {
+ for (Object next : getPlainProviders()) {
+ invokeInitialize(next);
+ }
+ }
+
+ try {
+ findResourceMethods(new PageProvider());
+ } catch (Exception ex) {
+ ourLog.error("An error occurred while loading request handlers!", ex);
+ throw new ServletException("Failed to initialize FHIR Restful server", ex);
+ }
+
+ myStarted = true;
+ ourLog.info("A FHIR has been lit on this server");
+ } finally {
+ myProviderRegistrationMutex.unlock();
+ }
+ }
+
+ /**
+ * This method may be overridden by subclasses to do perform initialization that needs to be performed prior to the
+ * server being used.
+ *
+ * @throws ServletException
+ * If the initialization failed. Note that you should consider throwing {@link UnavailableException}
+ * (which extends {@link ServletException}), as this is a flag to the servlet container that the servlet
+ * is not usable.
+ */
+ protected void initialize() throws ServletException {
+ // nothing by default
+ }
+
+ private void invokeDestroy(Object theProvider) {
+ invokeDestroy(theProvider, theProvider.getClass());
+ }
+
+ private void invokeDestroy(Object theProvider, Class> clazz) {
+ for (Method m : ReflectionUtil.getDeclaredMethods(clazz)) {
+ Destroy destroy = m.getAnnotation(Destroy.class);
+ if (destroy != null) {
+ try {
+ m.invoke(theProvider);
+ } catch (IllegalAccessException e) {
+ ourLog.error("Exception occurred in destroy ", e);
+ } catch (InvocationTargetException e) {
+ ourLog.error("Exception occurred in destroy ", e);
+ }
+ return;
}
}
- if (resourceMethod == null) {
- if (resourceBinding != null) {
- resourceMethod = resourceBinding.getMethod(requestDetails);
+ Class> supertype = clazz.getSuperclass();
+ if (!Object.class.equals(supertype)) {
+ invokeDestroy(theProvider, supertype);
+ }
+ }
+
+ private void invokeInitialize(Object theProvider) {
+ invokeInitialize(theProvider, theProvider.getClass());
+ }
+
+ private void invokeInitialize(Object theProvider, Class> clazz) {
+ for (Method m : ReflectionUtil.getDeclaredMethods(clazz)) {
+ Initialize initialize = m.getAnnotation(Initialize.class);
+ if (initialize != null) {
+ try {
+ m.invoke(theProvider);
+ } catch (IllegalAccessException e) {
+ ourLog.error("Exception occurred in destroy ", e);
+ } catch (InvocationTargetException e) {
+ ourLog.error("Exception occurred in destroy ", e);
+ }
+ return;
}
}
- if (resourceMethod == null) {
- if (isBlank(requestPath)) {
- throw new InvalidRequestException(myFhirContext.getLocalizer().getMessage(RestfulServer.class, "rootRequest"));
- } else {
- throw new InvalidRequestException(myFhirContext.getLocalizer().getMessage(RestfulServer.class, "unknownMethod", requestType.name(), requestPath, requestDetails.getParameters().keySet()));
- }
+
+ Class> supertype = clazz.getSuperclass();
+ if (!Object.class.equals(supertype)) {
+ invokeInitialize(theProvider, supertype);
}
- return resourceMethod;
+ }
+
+ /**
+ * Should the server "pretty print" responses by default (requesting clients can always override this default by
+ * supplying an Accept header in the request, or a _pretty parameter in the request URL.
+ *
+ * The default is false
+ *
+ *
+ * @return Returns the default pretty print setting
+ */
+ @Override
+ public boolean isDefaultPrettyPrint() {
+ return myDefaultPrettyPrint;
+ }
+
+ /**
+ * Should the server attempt to decompress incoming request contents (default is true). Typically this
+ * should be set to true unless the server has other configuration to deal with decompressing request
+ * bodies (e.g. a filter applied to the whole server).
+ */
+ public boolean isUncompressIncomingContents() {
+ return myUncompressIncomingContents;
+ }
+
+ @Override
+ public boolean isUseBrowserFriendlyContentTypes() {
+ return myUseBrowserFriendlyContentTypes;
}
public void populateRequestDetailsFromRequestPath(RequestDetails theRequestDetails, String theRequestPath) {
StringTokenizer tok = new StringTokenizer(theRequestPath, "/");
String resourceName = null;
-
+
IdDt id = null;
String operation = null;
String compartment = null;
@@ -752,118 +939,41 @@ public class RestfulServer extends HttpServlet implements IRestfulServer resourceProvider = getResourceProviders();
- // 'true' tells registerProviders() that
- // this call is part of initialization
- registerProviders(resourceProvider, true);
-
- Collection providers = getPlainProviders();
- // 'true' tells registerProviders() that
- // this call is part of initialization
- registerProviders(providers, true);
-
- findResourceMethods(getServerProfilesProvider());
-
- confProvider = getServerConformanceProvider();
- if (confProvider == null) {
- confProvider = getFhirContext().getVersion().createServerConformanceProvider(this);
- }
-// findSystemMethods(confProvider);
- findResourceMethods(confProvider);
-
- } catch (Exception ex) {
- ourLog.error("An error occurred while loading request handlers!", ex);
- throw new ServletException("Failed to initialize FHIR Restful server", ex);
- }
-
- ourLog.trace("Invoking provider initialize methods");
- if (getResourceProviders() != null) {
- for (IResourceProvider iResourceProvider : getResourceProviders()) {
- invokeInitialize(iResourceProvider);
- }
- }
- if (confProvider != null) {
- invokeInitialize(confProvider);
- }
- if (getPlainProviders() != null) {
- for (Object next : getPlainProviders()) {
- invokeInitialize(next);
- }
- }
-
- try {
- findResourceMethods(new PageProvider());
- } catch (Exception ex) {
- ourLog.error("An error occurred while loading request handlers!", ex);
- throw new ServletException("Failed to initialize FHIR Restful server", ex);
- }
-
- myStarted = true;
- ourLog.info("A FHIR has been lit on this server");
- } finally {
- myProviderRegistrationMutex.unlock();
- }
+ public void registerInterceptor(IServerInterceptor theInterceptor) {
+ Validate.notNull(theInterceptor, "Interceptor can not be null");
+ myInterceptors.add(theInterceptor);
}
/**
- * This method may be overridden by subclasses to do perform initialization that needs to be performed prior to the server being used.
- *
- * @throws ServletException
- * If the initialization failed. Note that you should consider throwing {@link UnavailableException} (which extends {@link ServletException}), as this is a flag to the servlet container
- * that the servlet is not usable.
- */
- protected void initialize() throws ServletException {
- // nothing by default
- }
-
- /**
- * Register a single provider. This could be a Resource Provider
- * or a "plain" provider not associated with any resource.
+ * Register a single provider. This could be a Resource Provider or a "plain" provider not associated with any
+ * resource.
*
* @param provider
* @throws Exception
*/
- public void registerProvider (Object provider) throws Exception {
+ public void registerProvider(Object provider) throws Exception {
if (provider != null) {
Collection providerList = new ArrayList(1);
providerList.add(provider);
registerProviders(providerList);
}
}
-
+
/**
- * Register a group of providers. These could be Resource Providers,
- * "plain" providers or a mixture of the two.
+ * Register a group of providers. These could be Resource Providers, "plain" providers or a mixture of the two.
*
- * @param providers a {@code Collection} of providers. The parameter
- * could be null or an empty {@code Collection}
+ * @param providers
+ * a {@code Collection} of providers. The parameter could be null or an empty {@code Collection}
* @throws Exception
*/
- public void registerProviders (Collection extends Object> providers) throws Exception {
+ public void registerProviders(Collection extends Object> providers) throws Exception {
myProviderRegistrationMutex.lock();
try {
if (!myStarted) {
for (Object provider : providers) {
- ourLog.info("Registration of provider ["+provider.getClass().getName()+"] will be delayed until FHIR server startup");
+ ourLog.info("Registration of provider [" + provider.getClass().getName() + "] will be delayed until FHIR server startup");
if (provider instanceof IResourceProvider) {
- myResourceProviders.add((IResourceProvider)provider);
+ myResourceProviders.add((IResourceProvider) provider);
} else {
myPlainProviders.add(provider);
}
@@ -875,11 +985,11 @@ public class RestfulServer extends HttpServlet implements IRestfulServer providers, boolean inInit) throws Exception {
+ protected void registerProviders(Collection extends Object> providers, boolean inInit) throws Exception {
List newResourceProviders = new ArrayList();
List newPlainProviders = new ArrayList();
ProvidedResourceScanner providedResourceScanner = new ProvidedResourceScanner(getFhirContext());
@@ -887,7 +997,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer resourceType = rsrcProvider.getResourceType();
if (resourceType == null) {
throw new NullPointerException("getResourceType() on class '" + rsrcProvider.getClass().getCanonicalName() + "' returned null");
@@ -908,7 +1018,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer providerList = new ArrayList(1);
- providerList.add(provider);
- unregisterProviders(providerList);
- }
- }
-
- /**
- * Unregister a {@code Collection} of providers
- *
- * @param providers
- * @throws Exception
- */
- public void unregisterProviders (Collection extends Object> providers) throws Exception {
- ProvidedResourceScanner providedResourceScanner = new ProvidedResourceScanner(getFhirContext());
- if (providers != null) {
- for (Object provider : providers) {
- removeResourceMethods(provider);
- if (provider instanceof IResourceProvider) {
- myResourceProviders.remove(provider);
- IResourceProvider rsrcProvider = (IResourceProvider)provider;
- Class extends IBaseResource> resourceType = rsrcProvider.getResourceType();
- String resourceName = getFhirContext().getResourceDefinition(resourceType).getName();
- myTypeToProvider.remove(resourceName);
- providedResourceScanner.removeProvidedResources(rsrcProvider);
- } else {
- myPlainProviders.remove(provider);
- }
- invokeDestroy(provider);
- }
- }
- }
-
- private void invokeDestroy(Object theProvider) {
- invokeDestroy(theProvider, theProvider.getClass());
- }
-
- private void invokeInitialize(Object theProvider) {
- invokeInitialize(theProvider, theProvider.getClass());
- }
-
- private void invokeDestroy(Object theProvider, Class> clazz) {
- for (Method m : ReflectionUtil.getDeclaredMethods(clazz)) {
- Destroy destroy = m.getAnnotation(Destroy.class);
- if (destroy != null) {
- try {
- m.invoke(theProvider);
- } catch (IllegalAccessException e) {
- ourLog.error("Exception occurred in destroy ", e);
- } catch (InvocationTargetException e) {
- ourLog.error("Exception occurred in destroy ", e);
- }
- return;
- }
- }
-
+ private void removeResourceMethods(Object theProvider) throws Exception {
+ ourLog.info("Removing RESTful methods for: {}", theProvider.getClass());
+ Class> clazz = theProvider.getClass();
Class> supertype = clazz.getSuperclass();
- if (!Object.class.equals(supertype)) {
- invokeDestroy(theProvider, supertype);
+ Collection resourceNames = new ArrayList();
+ while (!Object.class.equals(supertype)) {
+ removeResourceMethods(theProvider, supertype, resourceNames);
+ supertype = supertype.getSuperclass();
+ }
+ removeResourceMethods(theProvider, clazz, resourceNames);
+ for (String resourceName : resourceNames) {
+ myResourceNameToBinding.remove(resourceName);
}
}
- private void invokeInitialize(Object theProvider, Class> clazz) {
+ /*
+ * Collect the set of RESTful methods for a single class when it is being unregistered
+ */
+ private void removeResourceMethods(Object theProvider, Class> clazz, Collection resourceNames) throws ConfigurationException {
for (Method m : ReflectionUtil.getDeclaredMethods(clazz)) {
- Initialize initialize = m.getAnnotation(Initialize.class);
- if (initialize != null) {
- try {
- m.invoke(theProvider);
- } catch (IllegalAccessException e) {
- ourLog.error("Exception occurred in destroy ", e);
- } catch (InvocationTargetException e) {
- ourLog.error("Exception occurred in destroy ", e);
- }
- return;
+ BaseMethodBinding> foundMethodBinding = BaseMethodBinding.bindMethod(m, getFhirContext(), theProvider);
+ if (foundMethodBinding == null) {
+ continue; // not a bound method
+ }
+ if (foundMethodBinding instanceof ConformanceMethodBinding) {
+ myServerConformanceMethod = null;
+ continue;
+ }
+ String resourceName = foundMethodBinding.getResourceName();
+ if (!resourceNames.contains(resourceName)) {
+ resourceNames.add(resourceName);
}
}
+ }
- Class> supertype = clazz.getSuperclass();
- if (!Object.class.equals(supertype)) {
- invokeInitialize(theProvider, supertype);
+ public Object returnResponse(ServletRequestDetails theRequest, ParseAction> outcome, int operationStatus, boolean allowPrefer, MethodOutcome response, String resourceName) throws IOException {
+ HttpServletResponse servletResponse = theRequest.getServletResponse();
+ servletResponse.setStatus(operationStatus);
+ servletResponse.setCharacterEncoding(Constants.CHARSET_NAME_UTF8);
+ addHeadersToResponse(servletResponse);
+ if (allowPrefer) {
+ addContentLocationHeaders(theRequest, servletResponse, response, resourceName);
}
+ if (outcome != null) {
+ EncodingEnum encoding = RestfulServerUtils.determineResponseEncodingWithDefault(theRequest);
+ servletResponse.setContentType(encoding.getResourceContentType());
+ Writer writer = servletResponse.getWriter();
+ IParser parser = encoding.newParser(getFhirContext());
+ parser.setPrettyPrint(RestfulServerUtils.prettyPrintResponse(this, theRequest));
+ try {
+ outcome.execute(parser, writer);
+ } finally {
+ writer.close();
+ }
+ } else {
+ servletResponse.setContentType(Constants.CT_TEXT_WITH_UTF8);
+ Writer writer = servletResponse.getWriter();
+ writer.close();
+ }
+ // getMethod().in
+ return null;
}
/**
- * Should the server "pretty print" responses by default (requesting clients can always override this default by supplying an Accept header in the request, or a _pretty
- * parameter in the request URL.
- *
- * The default is false
- *
- *
- * @return Returns the default pretty print setting
- */
- @Override
- public boolean isDefaultPrettyPrint() {
- return myDefaultPrettyPrint;
- }
-
- @Override
- public boolean isUseBrowserFriendlyContentTypes() {
- return myUseBrowserFriendlyContentTypes;
- }
-
- public void registerInterceptor(IServerInterceptor theInterceptor) {
- Validate.notNull(theInterceptor, "Interceptor can not be null");
- myInterceptors.add(theInterceptor);
- }
-
- /**
- * Sets the profile tagging behaviour for the server. When set to a value other than {@link AddProfileTagEnum#NEVER} (which is the default), the server will automatically add a profile tag based on
- * the class of the resource(s) being returned.
+ * Sets the profile tagging behaviour for the server. When set to a value other than {@link AddProfileTagEnum#NEVER}
+ * (which is the default), the server will automatically add a profile tag based on the class of the resource(s)
+ * being returned.
*
* @param theAddProfileTag
* The behaviour enum (must not be null)
@@ -1077,8 +1140,8 @@ public class RestfulServer extends HttpServlet implements IRestfulServerAccept header in the request, or a _pretty
- * parameter in the request URL.
+ * Should the server "pretty print" responses by default (requesting clients can always override this default by
+ * supplying an Accept header in the request, or a _pretty parameter in the request URL.
*
* The default is false
*
@@ -1091,10 +1154,12 @@ public class RestfulServer extends HttpServlet implements IRestfulServer_format URL parameter, or with an Accept header in
- * the request. The default is {@link EncodingEnum#XML}.
+ * Sets the default encoding to return (XML/JSON) if an incoming request does not specify a preference (either with
+ * the _format URL parameter, or with an Accept header in the request. The default is
+ * {@link EncodingEnum#XML}.
*
- * Note when testing this feature: Some browsers will include "application/xml" in their Accept header, which means that the
+ * Note when testing this feature: Some browsers will include "application/xml" in their Accept header, which means
+ * that the
*
*/
public void setDefaultResponseEncoding(EncodingEnum theDefaultResponseEncoding) {
@@ -1103,7 +1168,8 @@ public class RestfulServer extends HttpServlet implements IRestfulServernull. Default is {@link #DEFAULT_ETAG_SUPPORT}
+ * Sets (enables/disables) the server support for ETags. Must not be null. Default is
+ * {@link #DEFAULT_ETAG_SUPPORT}
*
* @param theETagSupport
* The ETag support mode
@@ -1211,7 +1277,8 @@ public class RestfulServer extends HttpServlet implements IRestfulServer
- * By default, the ServerConformanceProvider implementation for the declared version of FHIR is used, but this can be changed, or set to null if you do not wish to export a conformance
- * statement.
+ * By default, the ServerConformanceProvider implementation for the declared version of FHIR is used, but this can be
+ * changed, or set to null if you do not wish to export a conformance statement.
*
* Note that this method can only be called before the server is initialized.
*
* @throws IllegalStateException
- * Note that this method can only be called prior to {@link #init() initialization} and will throw an {@link IllegalStateException} if called after that.
+ * Note that this method can only be called prior to {@link #init() initialization} and will throw an
+ * {@link IllegalStateException} if called after that.
*/
public void setServerConformanceProvider(Object theServerConformanceProvider) {
if (myStarted) {
@@ -1237,35 +1306,46 @@ public class RestfulServer extends HttpServlet implements IRestfulServertrue (default is false), the server will use browser friendly content-types (instead of standard FHIR ones) when it detects that the request is coming from a browser
- * instead of a FHIR
+ * Should the server attempt to decompress incoming request contents (default is true). Typically this
+ * should be set to true unless the server has other configuration to deal with decompressing request
+ * bodies (e.g. a filter applied to the whole server).
+ */
+ public void setUncompressIncomingContents(boolean theUncompressIncomingContents) {
+ myUncompressIncomingContents = theUncompressIncomingContents;
+ }
+
+ /**
+ * If set to true (default is false), the server will use browser friendly content-types (instead of
+ * standard FHIR ones) when it detects that the request is coming from a browser instead of a FHIR
*/
public void setUseBrowserFriendlyContentTypes(boolean theUseBrowserFriendlyContentTypes) {
myUseBrowserFriendlyContentTypes = theUseBrowserFriendlyContentTypes;
@@ -1276,6 +1356,46 @@ public class RestfulServer extends HttpServlet implements IRestfulServer providerList = new ArrayList(1);
+ providerList.add(provider);
+ unregisterProviders(providerList);
+ }
+ }
+
+ /**
+ * Unregister a {@code Collection} of providers
+ *
+ * @param providers
+ * @throws Exception
+ */
+ public void unregisterProviders(Collection extends Object> providers) throws Exception {
+ ProvidedResourceScanner providedResourceScanner = new ProvidedResourceScanner(getFhirContext());
+ if (providers != null) {
+ for (Object provider : providers) {
+ removeResourceMethods(provider);
+ if (provider instanceof IResourceProvider) {
+ myResourceProviders.remove(provider);
+ IResourceProvider rsrcProvider = (IResourceProvider) provider;
+ Class extends IBaseResource> resourceType = rsrcProvider.getResourceType();
+ String resourceName = getFhirContext().getResourceDefinition(resourceType).getName();
+ myTypeToProvider.remove(resourceName);
+ providedResourceScanner.removeProvidedResources(rsrcProvider);
+ } else {
+ myPlainProviders.remove(provider);
+ }
+ invokeDestroy(provider);
+ }
+ }
+ }
+
private void writeExceptionToResponse(HttpServletResponse theResponse, BaseServerResponseException theException) throws IOException {
theResponse.setStatus(theException.getStatusCode());
addHeadersToResponse(theResponse);
@@ -1293,86 +1413,4 @@ public class RestfulServer extends HttpServlet implements IRestfulServer outcome, int operationStatus, boolean allowPrefer, MethodOutcome response,
- String resourceName) throws IOException {
- HttpServletResponse servletResponse = theRequest.getServletResponse();
- servletResponse.setStatus(operationStatus);
- servletResponse.setCharacterEncoding(Constants.CHARSET_NAME_UTF8);
- addHeadersToResponse(servletResponse);
- if(allowPrefer) {
- addContentLocationHeaders(theRequest, servletResponse, response, resourceName);
- }
- if (outcome != null) {
- EncodingEnum encoding = RestfulServerUtils.determineResponseEncodingWithDefault(theRequest);
- servletResponse.setContentType(encoding.getResourceContentType());
- Writer writer = servletResponse.getWriter();
- IParser parser = encoding.newParser(getFhirContext());
- parser.setPrettyPrint(RestfulServerUtils.prettyPrintResponse(this, theRequest));
- try {
- outcome.execute(parser, writer);
- } finally {
- writer.close();
- }
- } else {
- servletResponse.setContentType(Constants.CT_TEXT_WITH_UTF8);
- Writer writer = servletResponse.getWriter();
- writer.close();
- }
- // getMethod().in
- return null;
- }
-
- private void addContentLocationHeaders(RequestDetails theRequest, HttpServletResponse servletResponse, MethodOutcome response, String resourceName) {
- if (response != null && response.getId() != null) {
- addLocationHeader(theRequest, servletResponse, response, Constants.HEADER_LOCATION, resourceName);
- addLocationHeader(theRequest, servletResponse, response, Constants.HEADER_CONTENT_LOCATION, resourceName);
- }
- }
-
- private void addLocationHeader(RequestDetails theRequest, HttpServletResponse theResponse, MethodOutcome response, String headerLocation, String resourceName) {
- StringBuilder b = new StringBuilder();
- b.append(theRequest.getFhirServerBase());
- b.append('/');
- b.append(resourceName);
- b.append('/');
- b.append(response.getId().getIdPart());
- if (response.getId().hasVersionIdPart()) {
- b.append("/" + Constants.PARAM_HISTORY + "/");
- b.append(response.getId().getVersionIdPart());
- } else if (response.getVersionId() != null && response.getVersionId().isEmpty() == false) {
- b.append("/" + Constants.PARAM_HISTORY + "/");
- b.append(response.getVersionId().getValue());
- }
- theResponse.addHeader(headerLocation, b.toString());
-
- }
-
- public RestulfulServerConfiguration createConfiguration() {
- RestulfulServerConfiguration result = new RestulfulServerConfiguration();
- result.setResourceBindings(getResourceBindings());
- result.setServerBindings(getServerBindings());
- result.setImplementationDescription(getImplementationDescription());
- result.setServerVersion(getServerVersion());
- result.setServerName(getServerName());
- result.setFhirContext(getFhirContext());
- result.setServerAddressStrategy(myServerAddressStrategy);
- InputStream inputStream = null;
- try {
- inputStream = getClass().getResourceAsStream("/META-INF/MANIFEST.MF");
- if (inputStream != null) {
- Manifest manifest = new Manifest(inputStream);
- result.setConformanceDate(manifest.getMainAttributes().getValue("Build-Time"));
- }
- }
- catch (IOException e) {
- // fall through
- }
- finally {
- if (inputStream != null) {
- IOUtils.closeQuietly(inputStream);
- }
- }
- return result;
- }
-
}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestulfulServerConfiguration.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestulfulServerConfiguration.java
index 195a77b62fe..909ee90b844 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestulfulServerConfiguration.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestulfulServerConfiguration.java
@@ -1,5 +1,25 @@
package ca.uhn.fhir.rest.server;
+/*
+ * #%L
+ * HAPI FHIR - Core Library
+ * %%
+ * Copyright (C) 2014 - 2015 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 java.util.Collection;
import java.util.List;
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/servlet/ServletRequestDetails.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/servlet/ServletRequestDetails.java
index 86d4e83e765..9f30c592882 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/servlet/ServletRequestDetails.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/servlet/ServletRequestDetails.java
@@ -1,5 +1,28 @@
package ca.uhn.fhir.rest.server.servlet;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+
+/*
+ * #%L
+ * HAPI FHIR - Core Library
+ * %%
+ * Copyright (C) 2014 - 2015 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 java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
@@ -9,17 +32,21 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
+import org.apache.tools.ant.taskdefs.GUnzip;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.rest.api.RequestTypeEnum;
import ca.uhn.fhir.rest.method.BaseMethodBinding;
import ca.uhn.fhir.rest.method.BaseMethodBinding.IRequestReader;
import ca.uhn.fhir.rest.method.RequestDetails;
+import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
@@ -72,6 +99,16 @@ public class ServletRequestDetails extends RequestDetails {
try {
InputStream inputStream = reader.getInputStream(this);
requestContents = IOUtils.toByteArray(inputStream);
+
+ if (myServer.isUncompressIncomingContents()) {
+ String contentEncoding = myServletRequest.getHeader(Constants.HEADER_CONTENT_ENCODING);
+ if ("gzip".equals(contentEncoding)) {
+ ourLog.debug("Uncompressing (GZip) incoming content");
+ GZIPInputStream gis = new GZIPInputStream(new ByteArrayInputStream(requestContents));
+ requestContents = IOUtils.toByteArray(gis);
+ }
+ }
+
return requestContents;
} catch (IOException e) {
ourLog.error("Could not load request resource", e);
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/servlet/ServletRestfulResponse.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/servlet/ServletRestfulResponse.java
index 97ce9f5ebfb..c5fecb39235 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/servlet/ServletRestfulResponse.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/servlet/ServletRestfulResponse.java
@@ -1,5 +1,25 @@
package ca.uhn.fhir.rest.server.servlet;
+/*
+ * #%L
+ * HAPI FHIR - Core Library
+ * %%
+ * Copyright (C) 2014 - 2015 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 java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
diff --git a/hapi-fhir-cli/src/main/java/ca/uhn/fhir/cli/ExampleDataUploader.java b/hapi-fhir-cli/src/main/java/ca/uhn/fhir/cli/ExampleDataUploader.java
index d771b94e514..a8d27f67973 100644
--- a/hapi-fhir-cli/src/main/java/ca/uhn/fhir/cli/ExampleDataUploader.java
+++ b/hapi-fhir-cli/src/main/java/ca/uhn/fhir/cli/ExampleDataUploader.java
@@ -7,7 +7,7 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileWriter;
-import java.net.URL;
+import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -25,21 +25,24 @@ import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
+import org.fusesource.jansi.Ansi;
+import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseResource;
import com.phloc.commons.io.file.FileUtils;
+import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
+import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu2.resource.Bundle;
import ca.uhn.fhir.model.dstu2.resource.Bundle.Entry;
import ca.uhn.fhir.model.dstu2.resource.Bundle.EntryRequest;
-import ca.uhn.fhir.model.dstu2.resource.SearchParameter;
-import ca.uhn.fhir.model.dstu2.valueset.BundleTypeEnum;
import ca.uhn.fhir.model.dstu2.valueset.HTTPVerbEnum;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.rest.client.IGenericClient;
+import ca.uhn.fhir.rest.client.interceptor.GZipContentInterceptor;
import ca.uhn.fhir.util.ResourceReferenceInfo;
public class ExampleDataUploader extends BaseCommand {
@@ -75,6 +78,10 @@ public class ExampleDataUploader extends BaseCommand {
opt.setRequired(false);
options.addOption(opt);
+ opt = new Option("c", "cache", true, "Store a copy of the downloaded example pack on the local disk using a file of the given name. Use this file instead of fetching it from the internet if the file already exists.");
+ opt.setRequired(false);
+ options.addOption(opt);
+
return options;
}
@@ -121,8 +128,8 @@ public class ExampleDataUploader extends BaseCommand {
}
Bundle bundle = new Bundle();
{
-
- byte[] inputBytes = IOUtils.toByteArray(result.getEntity().getContent());
+ byte[] inputBytes = readStreamFromInternet(result);
+
IOUtils.closeQuietly(result.getEntity().getContent());
ourLog.info("Successfully Loaded example pack ({} bytes)", inputBytes.length);
@@ -163,26 +170,23 @@ public class ExampleDataUploader extends BaseCommand {
}
ourLog.info("Found example {} - {} - {} chars", nextEntry.getName(), parsed.getClass().getSimpleName(), exampleString.length());
- if (parsed instanceof Bundle) {
- Bundle b = (Bundle) parsed;
- if (b.getTypeElement().getValueAsEnum() != BundleTypeEnum.DOCUMENT) {
- continue;
- }
+ if (ctx.getResourceDefinition(parsed).getName().equals("Bundle")) {
+ BaseRuntimeChildDefinition entryChildDef = ctx.getResourceDefinition(parsed).getChildByName("entry");
+ BaseRuntimeElementCompositeDefinition> entryDef = (BaseRuntimeElementCompositeDefinition>) entryChildDef.getChildByName("entry");
- for (Entry nextEntry1 : b.getEntry()) {
- if (nextEntry1.getResource() == null) {
+ for (IBase nextEntry1 : entryChildDef.getAccessor().getValues(parsed)) {
+ List resources = entryDef.getChildByName("resource").getAccessor().getValues(nextEntry1);
+ if (resources == null) {
continue;
}
- if (nextEntry1.getResource() instanceof Bundle) {
- continue;
+ for (IBase nextResource : resources) {
+ if (!ctx.getResourceDefinition(parsed).getName().equals("Bundle") && ctx.getResourceDefinition(parsed).getName().equals("SearchParameter")) {
+ bundle.addEntry().setRequest(new EntryRequest().setMethod(HTTPVerbEnum.POST)).setResource((IResource) nextResource);
+ }
}
- if (nextEntry1.getResource() instanceof SearchParameter) {
- continue;
- }
- bundle.addEntry().setRequest(new EntryRequest().setMethod(HTTPVerbEnum.POST)).setResource(nextEntry1.getResource());
}
} else {
- if (parsed instanceof SearchParameter) {
+ if (ctx.getResourceDefinition(parsed).getName().equals("SearchParameter")) {
continue;
}
bundle.addEntry().setRequest(new EntryRequest().setMethod(HTTPVerbEnum.POST)).setResource((IResource) parsed);
@@ -242,6 +246,7 @@ public class ExampleDataUploader extends BaseCommand {
}
next.getRequest().setMethod(HTTPVerbEnum.PUT);
next.getRequest().setUrl(nextId);
+ next.getResource().setId("");
renames.put(originalId, nextId);
}
}
@@ -302,6 +307,7 @@ public class ExampleDataUploader extends BaseCommand {
ourLog.info("Uploading bundle to server: " + targetServer);
IGenericClient fhirClient = newClient(ctx, targetServer);
+ fhirClient.registerInterceptor(new GZipContentInterceptor());
long start = System.currentTimeMillis();
;
@@ -312,4 +318,40 @@ public class ExampleDataUploader extends BaseCommand {
}
}
+ private byte[] readStreamFromInternet(CloseableHttpResponse result) throws IOException {
+ byte[] inputBytes;
+ {
+ long maxLength = result.getEntity().getContentLength();
+ int nextLog = -1;
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+ int nRead;
+ byte[] data = new byte[16384];
+ while ((nRead = result.getEntity().getContent().read(data, 0, data.length)) != -1) {
+ buffer.write(data, 0, nRead);
+ if (buffer.size() > nextLog) {
+ System.err.print("\r" + Ansi.ansi().eraseLine());
+ System.err.print(FileUtils.getFileSizeDisplay(buffer.size(), 1));
+ if (maxLength > 0) {
+ System.err.print(" [");
+ int stars = (int)(50.0f * ((float)buffer.size() / (float)maxLength));
+ for (int i = 0; i < stars; i++) {
+ System.err.print("*");
+ }
+ for (int i = stars; i < 50; i++) {
+ System.err.print(" ");
+ }
+ System.err.print("]");
+ }
+ System.err.flush();
+ nextLog += 100000;
+ }
+ }
+ buffer.flush();
+ inputBytes = buffer.toByteArray();
+ }
+ System.err.println();
+ System.err.flush();
+ return inputBytes;
+ }
+
}
diff --git a/hapi-fhir-cli/tmp.txt.gz b/hapi-fhir-cli/tmp.txt.gz
new file mode 100644
index 0000000000000000000000000000000000000000..773d5921a0e3da24ae1142b2e55335cc234dd37c
GIT binary patch
literal 2853844
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