> myUnqualifiedToQualifiedNames;
-
+ private IRestfulResponse myResponse;
+
public String getCompartmentName() {
return myCompartmentName;
}
@@ -64,6 +63,10 @@ public class RequestDetails {
return myCompleteUrl;
}
+ /**
+ * The fhir server base url, independant of the query being executed
+ * @return the fhir server base url
+ */
public String getFhirServerBase() {
return myFhirServerBase;
}
@@ -80,10 +83,6 @@ public class RequestDetails {
return myParameters;
}
- public byte[] getRawRequest() {
- return myRawRequest;
- }
-
/**
* The part of the request URL that comes after the server base.
*
@@ -110,17 +109,7 @@ public class RequestDetails {
return mySecondaryOperation;
}
- public RestfulServer getServer() {
- return myServer;
- }
-
- public HttpServletRequest getServletRequest() {
- return myServletRequest;
- }
-
- public HttpServletResponse getServletResponse() {
- return myServletResponse;
- }
+ public abstract IRestfulServerDefaults getServer();
public Map> getUnqualifiedToQualifiedNames() {
return myUnqualifiedToQualifiedNames;
@@ -178,10 +167,6 @@ public class RequestDetails {
}
- public void setRawRequest(byte[] theRawRequest) {
- myRawRequest = theRawRequest;
- }
-
public void setRequestPath(String theRequestPath) {
assert theRequestPath.length() == 0 || theRequestPath.charAt(0) != '/';
myRequestPath = theRequestPath;
@@ -207,46 +192,66 @@ public class RequestDetails {
mySecondaryOperation = theSecondaryOperation;
}
- public void setServer(RestfulServer theServer) {
- myServer = theServer;
- }
+ public IRestfulResponse getResponse() {
+ return myResponse;
+ }
- public void setServletRequest(HttpServletRequest theRequest) {
- myServletRequest = theRequest;
- }
+ public void setResponse(IRestfulResponse theResponse) {
+ this.myResponse = theResponse;
+ }
- public void setServletResponse(HttpServletResponse theServletResponse) {
- myServletResponse = theServletResponse;
- }
-
- public static RequestDetails withResourceAndParams(String theResourceName, RequestTypeEnum theRequestType, Set theParamNames) {
- RequestDetails retVal = new RequestDetails();
- retVal.setResourceName(theResourceName);
- retVal.setRequestType(theRequestType);
- Map paramNames = new HashMap();
- for (String next : theParamNames) {
- paramNames.put(next, new String[0]);
+ public abstract String getHeader(String name);
+
+ public final byte[] loadRequestContents(RequestDetails theRequest) {
+ if (myRequestContents == null) {
+ myRequestContents = getByteStreamRequestContents();
}
- retVal.setParameters(paramNames);
- return retVal;
+ return myRequestContents;
}
- public void addHeader(String theName, String theValue) {
- String lowerCase = theName.toLowerCase();
- ArrayList list = myHeaders.get(lowerCase);
- if (list == null) {
- list = new ArrayList();
- myHeaders.put(lowerCase, list);
- }
- list.add(theValue);
- }
+ protected abstract byte[] getByteStreamRequestContents();
- public String getFirstHeader(String theName) {
- ArrayList list = myHeaders.get(theName.toLowerCase());
- if (list == null || list.isEmpty()) {
- return null;
- }
- return list.get(0);
- }
+ 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 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();
}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/RequestDetailsParameter.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/RequestDetailsParameter.java
index adab1dcb613..f4c1a15ed14 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/RequestDetailsParameter.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/RequestDetailsParameter.java
@@ -43,7 +43,7 @@ class RequestDetailsParameter implements IParameter {
}
@Override
- public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, byte[] theRequestContents, BaseMethodBinding> theMethodBinding) throws InternalErrorException, InvalidRequestException {
+ public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, BaseMethodBinding> theMethodBinding) throws InternalErrorException, InvalidRequestException {
return theRequest;
}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SearchMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SearchMethodBinding.java
index 78e49f612ee..5e7b14ada08 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SearchMethodBinding.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SearchMethodBinding.java
@@ -47,7 +47,7 @@ import ca.uhn.fhir.rest.client.BaseHttpClientInvocation;
import ca.uhn.fhir.rest.param.BaseQueryParameter;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.IBundleProvider;
-import ca.uhn.fhir.rest.server.RestfulServer;
+import ca.uhn.fhir.rest.server.IRestfulServer;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
@@ -266,7 +266,7 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
}
@Override
- public IBundleProvider invokeServer(RestfulServer theServer, RequestDetails theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException {
+ public IBundleProvider invokeServer(IRestfulServer theServer, RequestDetails theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException {
if (myIdParamIndex != null) {
theMethodParams[myIdParamIndex] = theRequest.getId();
}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ServerBaseParamBinder.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ServerBaseParamBinder.java
index 8e7f44af757..3b3de0328d1 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ServerBaseParamBinder.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ServerBaseParamBinder.java
@@ -43,7 +43,7 @@ class ServerBaseParamBinder implements IParameter {
}
@Override
- public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, byte[] theRequestContents, BaseMethodBinding> theMethodBinding) throws InternalErrorException, InvalidRequestException {
+ public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, BaseMethodBinding> theMethodBinding) throws InternalErrorException, InvalidRequestException {
return theRequest.getFhirServerBase();
}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ServletRequestParameter.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ServletRequestParameter.java
index 31b132a9340..cff46c8bb1a 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ServletRequestParameter.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ServletRequestParameter.java
@@ -30,6 +30,7 @@ import org.hl7.fhir.instance.model.api.IBaseResource;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
+import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
class ServletRequestParameter implements IParameter {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ServletRequestParameter.class);
@@ -43,8 +44,8 @@ class ServletRequestParameter implements IParameter {
}
@Override
- public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, byte[] theRequestContents, BaseMethodBinding> theMethodBinding) throws InternalErrorException, InvalidRequestException {
- return theRequest.getServletRequest();
+ public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, BaseMethodBinding> theMethodBinding) throws InternalErrorException, InvalidRequestException {
+ return ((ServletRequestDetails) theRequest).getServletRequest();
}
@Override
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ServletResponseParameter.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ServletResponseParameter.java
index 89e571e8b1b..8137b5009c4 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ServletResponseParameter.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ServletResponseParameter.java
@@ -30,6 +30,7 @@ import org.hl7.fhir.instance.model.api.IBaseResource;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
+import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
class ServletResponseParameter implements IParameter {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ServletResponseParameter.class);
@@ -43,8 +44,8 @@ class ServletResponseParameter implements IParameter {
}
@Override
- public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, byte[] theRequestContents, BaseMethodBinding> theMethodBinding) throws InternalErrorException, InvalidRequestException {
- return theRequest.getServletResponse();
+ public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, BaseMethodBinding> theMethodBinding) throws InternalErrorException, InvalidRequestException {
+ return ((ServletRequestDetails) theRequest).getServletResponse();
}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SinceParameter.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SinceParameter.java
index 5e3a3f90a0d..d1cbd1e0b07 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SinceParameter.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SinceParameter.java
@@ -54,7 +54,7 @@ class SinceParameter implements IParameter {
}
@Override
- public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, byte[] theRequestContents, BaseMethodBinding> theMethodBinding) throws InternalErrorException, InvalidRequestException {
+ public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, BaseMethodBinding> theMethodBinding) throws InternalErrorException, InvalidRequestException {
String[] sinceParams = theRequest.getParameters().remove(Constants.PARAM_SINCE);
if (sinceParams != null) {
if (sinceParams.length > 0) {
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SortParameter.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SortParameter.java
index 27b21ee28a5..38e8bc51bd0 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SortParameter.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SortParameter.java
@@ -65,7 +65,7 @@ public class SortParameter implements IParameter {
}
@Override
- public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, byte[] theRequestContents, BaseMethodBinding> theMethodBinding) throws InternalErrorException, InvalidRequestException {
+ public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, BaseMethodBinding> theMethodBinding) throws InternalErrorException, InvalidRequestException {
if (!theRequest.getParameters().containsKey(Constants.PARAM_SORT)) {
if (!theRequest.getParameters().containsKey(Constants.PARAM_SORT_ASC)) {
if (!theRequest.getParameters().containsKey(Constants.PARAM_SORT_DESC)) {
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SummaryEnumParameter.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SummaryEnumParameter.java
index b656e9ae106..d76a9362b12 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SummaryEnumParameter.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SummaryEnumParameter.java
@@ -67,7 +67,7 @@ public class SummaryEnumParameter implements IParameter {
@Override
@SuppressWarnings({ "rawtypes", "unchecked" })
- public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, byte[] theRequestContents, BaseMethodBinding> theMethodBinding) throws InternalErrorException, InvalidRequestException {
+ public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, BaseMethodBinding> theMethodBinding) throws InternalErrorException, InvalidRequestException {
Set value = getSummaryValueOrNull(theRequest);
if (value == null || value.isEmpty()) {
return null;
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/TransactionMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/TransactionMethodBinding.java
index bab344c93b9..4439e895cbe 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/TransactionMethodBinding.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/TransactionMethodBinding.java
@@ -44,6 +44,7 @@ import ca.uhn.fhir.rest.client.BaseHttpClientInvocation;
import ca.uhn.fhir.rest.param.TransactionParameter;
import ca.uhn.fhir.rest.param.TransactionParameter.ParamStyle;
import ca.uhn.fhir.rest.server.IBundleProvider;
+import ca.uhn.fhir.rest.server.IRestfulServer;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
@@ -119,7 +120,7 @@ public class TransactionMethodBinding extends BaseResourceReturningMethodBinding
@SuppressWarnings("unchecked")
@Override
- public Object invokeServer(RestfulServer theServer, RequestDetails theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException {
+ public Object invokeServer(IRestfulServer theServer, RequestDetails theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException {
/*
* The design of HAPI's transaction method for DSTU1 support assumed that a transaction was just an update on a
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/UpdateMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/UpdateMethodBinding.java
index 0a4a5d9eac5..d463e9c7dc6 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/UpdateMethodBinding.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/UpdateMethodBinding.java
@@ -37,7 +37,7 @@ import ca.uhn.fhir.rest.client.BaseHttpClientInvocation;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
-class UpdateMethodBinding extends BaseOutcomeReturningMethodBindingWithResourceParam {
+public class UpdateMethodBinding extends BaseOutcomeReturningMethodBindingWithResourceParam {
private Integer myIdParameterIndex;
@@ -58,7 +58,7 @@ class UpdateMethodBinding extends BaseOutcomeReturningMethodBindingWithResourceP
* We are being a bit lenient here, since technically the client is supposed to include the version in the
* Content-Location header, but we allow it in the PUT URL as well..
*/
- String locationHeader = theRequest.getServletRequest().getHeader(Constants.HEADER_CONTENT_LOCATION);
+ String locationHeader = theRequest.getHeader(Constants.HEADER_CONTENT_LOCATION);
IdDt id = theRequest.getId();
if (isNotBlank(locationHeader)) {
id = new IdDt(locationHeader);
@@ -69,7 +69,7 @@ class UpdateMethodBinding extends BaseOutcomeReturningMethodBindingWithResourceP
}
}
- String ifMatchValue = theRequest.getServletRequest().getHeader(Constants.HEADER_IF_MATCH);
+ String ifMatchValue = theRequest.getHeader(Constants.HEADER_IF_MATCH);
if (isNotBlank(ifMatchValue)) {
ifMatchValue = MethodUtil.parseETagValue(ifMatchValue);
if (id != null && id.hasVersionIdPart() == false) {
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/BaseQueryParameter.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/BaseQueryParameter.java
index c27715a72c2..f38412016d4 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/BaseQueryParameter.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/BaseQueryParameter.java
@@ -133,7 +133,7 @@ public abstract class BaseQueryParameter implements IParameter {
}
@Override
- public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, byte[] theRequestContents, BaseMethodBinding> theMethodBinding) throws InternalErrorException, InvalidRequestException {
+ public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, BaseMethodBinding> theMethodBinding) throws InternalErrorException, InvalidRequestException {
List paramList = new ArrayList();
String name = getName();
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 c4e941218e6..f626ae783c2 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
@@ -30,7 +30,6 @@ import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.nio.charset.Charset;
import java.util.Collection;
-import java.util.Enumeration;
import java.util.List;
import java.util.Map;
@@ -102,38 +101,37 @@ public class ResourceParameter implements IParameter {
}
@Override
- public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, byte[] theRequestContents, BaseMethodBinding> theMethodBinding)
+ public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, BaseMethodBinding> theMethodBinding)
throws InternalErrorException, InvalidRequestException {
- switch (myMode) {
- case BODY:
- try {
- return IOUtils.toString(createRequestReader(theRequest, theRequestContents));
- } catch (IOException e) {
- // Shouldn't happen since we're reading from a byte array
- throw new InternalErrorException("Failed to load request");
+ switch (myMode) {
+ case BODY:
+ try {
+ return IOUtils.toString(createRequestReader(theRequest));
+ }
+ catch (IOException e) {
+ // Shouldn't happen since we're reading from a byte array
+ throw new InternalErrorException("Failed to load request");
+ }
+ case ENCODING:
+ return RestfulServerUtils.determineRequestEncoding(theRequest);
+ case RESOURCE:
+ default:
+ return parseResourceFromRequest(theRequest, theMethodBinding, myResourceType);
}
- case ENCODING:
- return RestfulServerUtils.determineRequestEncoding(theRequest);
- case RESOURCE:
- break;
- }
-
- IBaseResource retVal = parseResourceFromRequest(theRequest, theMethodBinding, myResourceType);
-
- return retVal;
+// }
}
-
- static Reader createRequestReader(byte[] theRequestContents, Charset charset) {
- Reader requestReader = new InputStreamReader(new ByteArrayInputStream(theRequestContents), charset);
+
+ public static Reader createRequestReader(RequestDetails theRequest, Charset charset) {
+ Reader requestReader = new InputStreamReader(new ByteArrayInputStream(theRequest.loadRequestContents(theRequest)), charset);
return requestReader;
}
- static Reader createRequestReader(RequestDetails theRequest, byte[] theRequestContents) {
- return createRequestReader(theRequestContents, determineRequestCharset(theRequest));
- }
+ public static Reader createRequestReader(RequestDetails theRequest) throws IOException {
+ return createRequestReader(theRequest, determineRequestCharset(theRequest));
+ }
- static Charset determineRequestCharset(RequestDetails theRequest) {
- String ct = theRequest.getServletRequest().getHeader(Constants.HEADER_CONTENT_TYPE);
+ public static Charset determineRequestCharset(RequestDetails theRequest) {
+ String ct = theRequest.getHeader(Constants.HEADER_CONTENT_TYPE);
Charset charset = null;
if (isNotBlank(ct)) {
@@ -144,20 +142,21 @@ public class ResourceParameter implements IParameter {
charset = Charset.forName("UTF-8");
}
return charset;
- }
-
+ }
+
@SuppressWarnings("unchecked")
- public static T loadResourceFromRequest(RequestDetails theRequest, BaseMethodBinding> theMethodBinding, Class theResourceType) {
+ public static T loadResourceFromRequest(RequestDetails theRequest, BaseMethodBinding> theMethodBinding,
+ Class theResourceType) {
FhirContext ctx = theRequest.getServer().getFhirContext();
final Charset charset = determineRequestCharset(theRequest);
- Reader requestReader = createRequestReader(theRequest.getRawRequest(), charset);
+ Reader requestReader = createRequestReader(theRequest, charset);
RestOperationTypeEnum restOperationType = theMethodBinding != null ? theMethodBinding.getRestOperationType() : null;
EncodingEnum encoding = RestfulServerUtils.determineRequestEncodingNoDefault(theRequest);
if (encoding == null) {
- String ctValue = theRequest.getServletRequest().getHeader(Constants.HEADER_CONTENT_TYPE);
+ String ctValue = theRequest.getHeader(Constants.HEADER_CONTENT_TYPE);
if (ctValue != null) {
if (ctValue.startsWith("application/x-www-form-urlencoded")) {
String msg = theRequest.getServer().getFhirContext().getLocalizer().getMessage(ResourceParameter.class, "invalidContentTypeInRequest", ctValue, theMethodBinding.getRestOperationType());
@@ -171,7 +170,7 @@ public class ResourceParameter implements IParameter {
String body;
try {
body = IOUtils.toString(requestReader);
- } catch (IOException e) {
+ } catch (IOException e) {
// This shouldn't happen since we're reading from a byte array..
throw new InternalErrorException(e);
}
@@ -180,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.getRawRequest()), charset);
+ requestReader = new InputStreamReader(new ByteArrayInputStream(theRequest.loadRequestContents(theRequest)), charset);
}
} else {
String msg = ctx.getLocalizer().getMessage(ResourceParameter.class, "invalidContentTypeInRequest", ctValue, restOperationType);
@@ -203,8 +202,7 @@ public class ResourceParameter implements IParameter {
if (theRequest.getServer().getFhirContext().getVersion().getVersion().equals(FhirVersionEnum.DSTU1)) {
TagList tagList = new TagList();
- for (Enumeration enumeration = theRequest.getServletRequest().getHeaders(Constants.HEADER_CATEGORY); enumeration.hasMoreElements();) {
- String nextTagComplete = enumeration.nextElement();
+ for (String nextTagComplete : theRequest.getHeaders(Constants.HEADER_CATEGORY)) {
MethodUtil.parseTagValue(tagList, nextTagComplete);
}
if (tagList.isEmpty() == false) {
@@ -212,23 +210,23 @@ public class ResourceParameter implements IParameter {
}
}
return retVal;
- }
-
+ }
+
public static IBaseResource parseResourceFromRequest(RequestDetails theRequest, BaseMethodBinding> theMethodBinding, Class extends IBaseResource> theResourceType) {
IBaseResource retVal;
if (IBaseBinary.class.isAssignableFrom(theResourceType)) {
FhirContext ctx = theRequest.getServer().getFhirContext();
- String ct = theRequest.getServletRequest().getHeader(Constants.HEADER_CONTENT_TYPE);
+ String ct = theRequest.getHeader(Constants.HEADER_CONTENT_TYPE);
IBaseBinary binary = (IBaseBinary) ctx.getResourceDefinition("Binary").newInstance();
binary.setContentType(ct);
- binary.setContent(theRequest.getRawRequest());
+ binary.setContent(theRequest.loadRequestContents(theRequest));
retVal = binary;
} else {
retVal = loadResourceFromRequest(theRequest, theMethodBinding, theResourceType);
}
return retVal;
- }
+ }
public enum Mode {
BODY, ENCODING, RESOURCE
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/TransactionParameter.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/TransactionParameter.java
index 5cc912672d7..2c56a61110f 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/TransactionParameter.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/TransactionParameter.java
@@ -1,5 +1,7 @@
package ca.uhn.fhir.rest.param;
+import java.io.IOException;
+
/*
* #%L
* HAPI FHIR - Core Library
@@ -48,6 +50,7 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
public class TransactionParameter implements IParameter {
+ private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TransactionParameter.class);
private FhirContext myContext;
private ParamStyle myParamStyle;
private Class extends IBaseResource> myResourceBundleType;
@@ -96,35 +99,41 @@ public class TransactionParameter implements IParameter {
// nothing
}
-
+
@Override
- public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, byte[] theRequestContents, BaseMethodBinding> theMethodBinding) throws InternalErrorException, InvalidRequestException {
-
+ public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, BaseMethodBinding> theMethodBinding) throws InternalErrorException, InvalidRequestException {
// TODO: don't use a default encoding, just fail!
EncodingEnum encoding = RestfulServerUtils.determineRequestEncoding(theRequest);
- IParser parser = encoding.newParser(myContext);
+ IParser parser = encoding.newParser(theRequest.getServer().getFhirContext());
+
+ Reader reader;
+ try {
+ reader = ResourceParameter.createRequestReader(theRequest);
+ }
+ catch (IOException e) {
+ ourLog.error("Could not load request resource", e);
+ throw new InvalidRequestException(String.format("Could not load request resource: %s", e.getMessage()));
+ }
- Reader reader = ResourceParameter.createRequestReader(theRequest, theRequestContents);
-
switch (myParamStyle) {
- case DSTU1_BUNDLE: {
- Bundle bundle;
- bundle = parser.parseBundle(reader);
- return bundle;
- }
- case RESOURCE_LIST: {
- Bundle bundle = parser.parseBundle(reader);
- ArrayList resourceList = new ArrayList();
- for (BundleEntry next : bundle.getEntries()) {
- if (next.getResource() != null) {
- resourceList.add(next.getResource());
- }
+ case DSTU1_BUNDLE: {
+ Bundle bundle;
+ bundle = parser.parseBundle(reader);
+ return bundle;
}
- return resourceList;
- }
- case RESOURCE_BUNDLE:
- return parser.parseResource(myResourceBundleType, reader);
+ case RESOURCE_LIST: {
+ Bundle bundle = parser.parseBundle(reader);
+ ArrayList resourceList = new ArrayList();
+ for (BundleEntry next : bundle.getEntries()) {
+ if (next.getResource() != null) {
+ resourceList.add(next.getResource());
+ }
+ }
+ return resourceList;
+ }
+ case RESOURCE_BUNDLE:
+ return parser.parseResource(myResourceBundleType, reader);
}
throw new IllegalStateException("Unknown type: " + myParamStyle); // should not happen
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
new file mode 100644
index 00000000000..afbb3000fd3
--- /dev/null
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/IRestfulResponse.java
@@ -0,0 +1,31 @@
+package ca.uhn.fhir.rest.server;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+import java.util.Set;
+
+import org.hl7.fhir.instance.model.api.IBaseBinary;
+import org.hl7.fhir.instance.model.api.IBaseResource;
+
+import ca.uhn.fhir.model.api.Bundle;
+import ca.uhn.fhir.rest.api.MethodOutcome;
+import ca.uhn.fhir.rest.api.SummaryEnum;
+import ca.uhn.fhir.rest.method.ParseAction;
+
+public interface IRestfulResponse {
+
+ Object streamResponseAsResource(IBaseResource resource, boolean prettyPrint, Set summaryMode, int operationStatus, boolean respondGzip, boolean addContentLocationHeader)
+ throws IOException;
+ Object streamResponseAsBundle(Bundle bundle, Set summaryMode, boolean respondGzip, boolean requestIsBrowser)
+ throws IOException;
+
+ Object returnResponse(ParseAction> outcome, int operationStatus, boolean allowPrefer, MethodOutcome response, String resourceName) throws IOException;
+
+ Writer getResponseWriter(int statusCode, String contentType, String charset, boolean respondGzip) throws UnsupportedEncodingException, IOException;
+ Object sendWriterResponse(int status, String contentType, String charset, Writer writer) throws IOException;
+
+ void addHeader(String headerKey, String headerValue);
+
+ Object sendAttachmentResponse(IBaseBinary bin, int stausCode, String contentType) throws IOException;
+}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/IRestfulServer.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/IRestfulServer.java
new file mode 100644
index 00000000000..da77bd1c9c2
--- /dev/null
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/IRestfulServer.java
@@ -0,0 +1,35 @@
+package ca.uhn.fhir.rest.server;
+
+import java.util.List;
+
+import ca.uhn.fhir.rest.method.RequestDetails;
+import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
+
+/*
+ * #%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 IRestfulServer extends IRestfulServerDefaults {
+
+ List getInterceptors();
+
+ IPagingProvider getPagingProvider();
+
+ BundleInclusionRule getBundleInclusionRule();
+
+}
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
new file mode 100644
index 00000000000..6029f2ebeb5
--- /dev/null
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/IRestfulServerDefaults.java
@@ -0,0 +1,46 @@
+package ca.uhn.fhir.rest.server;
+
+import ca.uhn.fhir.context.FhirContext;
+
+public interface IRestfulServerDefaults {
+
+ /**
+ * Gets the {@link FhirContext} associated with this server. For efficient processing, resource providers and plain providers should generally use this context if one is needed, as opposed to
+ * creating their own.
+ */
+ FhirContext getFhirContext();
+
+ /**
+ * 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
+ */
+ boolean isDefaultPrettyPrint();
+
+ /**
+ * @return Returns the server support for ETags (will not be null
). Default is {@link RestfulServer#DEFAULT_ETAG_SUPPORT}
+ */
+ ETagSupportEnum getETagSupport();
+
+ /**
+ * @return Returns the setting for automatically adding profile tags
+ */
+ AddProfileTagEnum getAddProfileTag();
+
+ /**
+ * @return 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.
+ */
+ EncodingEnum getDefaultResponseEncoding();
+
+ /**
+ * @return If true
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
+ */
+ boolean isUseBrowserFriendlyContentTypes();
+
+}
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
new file mode 100644
index 00000000000..b55330d0836
--- /dev/null
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/IRestfulServerUtil.java
@@ -0,0 +1,24 @@
+package ca.uhn.fhir.rest.server;
+
+import org.hl7.fhir.instance.model.api.IBaseResource;
+
+import ca.uhn.fhir.rest.method.BaseMethodBinding;
+import ca.uhn.fhir.rest.method.RequestDetails;
+import ca.uhn.fhir.rest.param.ResourceParameter.Mode;
+import ca.uhn.fhir.rest.param.TransactionParameter.ParamStyle;
+
+public interface IRestfulServerUtil {
+
+ Object getResourceParameter(
+ RequestDetails requestDetails,
+ Mode myMode,
+ BaseMethodBinding> theMethodBinding,
+ Class extends IBaseResource> myResourceType);
+
+ Object getRequestResource(RequestDetails theRequest, ParamStyle myParamStyle, Class extends IBaseResource> myResourceBundleType);
+
+ T loadResourceFromRequest(RequestDetails theRequest, BaseMethodBinding> theMethodBinding, Class theResourceType);
+
+ IBaseResource parseResourceFromRequest(RequestDetails theRequest, BaseMethodBinding> theMethodBinding, Class extends IBaseResource> theResourceType);
+
+}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/IVersionSpecificBundleFactory.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/IVersionSpecificBundleFactory.java
index 77394f88f84..d2298ee5394 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/IVersionSpecificBundleFactory.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/IVersionSpecificBundleFactory.java
@@ -40,7 +40,7 @@ public interface IVersionSpecificBundleFactory {
void addRootPropertiesToBundle(String theAuthor, String theServerBase, String theCompleteUrl, Integer theTotalResults, BundleTypeEnum theBundleType, IPrimitiveType theLastUpdated);
- void initializeBundleFromBundleProvider(RestfulServer theServer, IBundleProvider theResult, EncodingEnum theResponseEncoding, String theServerBase, String theCompleteUrl, boolean thePrettyPrint,
+ void initializeBundleFromBundleProvider(IRestfulServer theServer, IBundleProvider theResult, EncodingEnum theResponseEncoding, String theServerBase, String theCompleteUrl, boolean thePrettyPrint,
int theOffset, Integer theCount, String theSearchId, BundleTypeEnum theBundleType, Set theIncludes);
Bundle getDstu1Bundle();
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
new file mode 100644
index 00000000000..e6dc7af9992
--- /dev/null
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/PageProvider.java
@@ -0,0 +1,18 @@
+package ca.uhn.fhir.rest.server;
+
+import ca.uhn.fhir.model.api.IResource;
+import ca.uhn.fhir.rest.annotation.GetPage;
+
+public class PageProvider {
+
+// @GetPage(dstu1=true)
+// public Bundle getPageDstu1() {
+// return null;
+// }
+
+ @GetPage()
+ public IResource getPage() {
+ return null;
+ }
+
+}
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
new file mode 100644
index 00000000000..7d30b9f7959
--- /dev/null
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulResponse.java
@@ -0,0 +1,66 @@
+package ca.uhn.fhir.rest.server;
+
+import java.io.IOException;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.hl7.fhir.instance.model.api.IBaseResource;
+
+import ca.uhn.fhir.model.api.Bundle;
+import ca.uhn.fhir.rest.api.SummaryEnum;
+import ca.uhn.fhir.rest.method.RequestDetails;
+
+public abstract class RestfulResponse implements IRestfulResponse {
+
+ private T theRequestDetails;
+ private ConcurrentHashMap theHeaders = new ConcurrentHashMap();
+
+ public RestfulResponse(T requestDetails) {
+ this.theRequestDetails = requestDetails;
+ }
+
+ @Override
+ public final Object streamResponseAsResource(IBaseResource resource, boolean prettyPrint, Set summaryMode,
+ int statusCode, boolean respondGzip, boolean addContentLocationHeader)
+ throws IOException {
+ return RestfulServerUtils.streamResponseAsResource(theRequestDetails.getServer(), resource, prettyPrint, summaryMode, statusCode, respondGzip, addContentLocationHeader,
+ respondGzip, getRequestDetails());
+
+ }
+
+ @Override
+ public Object streamResponseAsBundle(Bundle bundle, Set summaryMode, boolean respondGzip, boolean requestIsBrowser)
+ throws IOException {
+ return RestfulServerUtils.streamResponseAsBundle(theRequestDetails.getServer(), bundle, summaryMode, requestIsBrowser, respondGzip, getRequestDetails());
+ }
+
+ @Override
+ public void addHeader(String headerKey, String headerValue) {
+ this.getHeaders().put(headerKey, headerValue);
+ }
+
+ /**
+ * Get the http headers
+ * @return the headers
+ */
+ public ConcurrentHashMap getHeaders() {
+ return theHeaders;
+ }
+
+ /**
+ * Get the requestDetails
+ * @return the requestDetails
+ */
+ public T getRequestDetails() {
+ return theRequestDetails;
+ }
+
+ /**
+ * Set the requestDetails
+ * @param requestDetails the requestDetails to set
+ */
+ public void setRequestDetails(T requestDetails) {
+ this.theRequestDetails = requestDetails;
+ }
+
+}
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 c55159994f2..9ac8c538d2f 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
@@ -20,9 +20,10 @@ package ca.uhn.fhir.rest.server;
* #L%
*/
import static org.apache.commons.lang3.StringUtils.isBlank;
-import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.io.IOException;
+import java.io.InputStream;
+import java.io.Writer;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@@ -31,15 +32,13 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
-import java.util.Enumeration;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
import java.util.Map;
-import java.util.Set;
import java.util.StringTokenizer;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
+import java.util.jar.Manifest;
import javax.servlet.ServletException;
import javax.servlet.UnavailableException;
@@ -47,6 +46,7 @@ import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+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.IBaseResource;
@@ -55,18 +55,17 @@ import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.ProvidedResourceScanner;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
-import ca.uhn.fhir.model.api.Bundle;
-import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.model.primitive.IdDt;
-import ca.uhn.fhir.model.valueset.BundleTypeEnum;
+import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.annotation.Destroy;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Initialize;
+import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.api.RequestTypeEnum;
-import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
-import ca.uhn.fhir.rest.api.SummaryEnum;
import ca.uhn.fhir.rest.method.BaseMethodBinding;
import ca.uhn.fhir.rest.method.ConformanceMethodBinding;
+import ca.uhn.fhir.rest.method.PageMethodBinding;
+import ca.uhn.fhir.rest.method.ParseAction;
import ca.uhn.fhir.rest.method.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
@@ -74,11 +73,12 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.NotModifiedException;
import ca.uhn.fhir.rest.server.interceptor.ExceptionHandlingInterceptor;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
+import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.util.ReflectionUtil;
import ca.uhn.fhir.util.UrlUtil;
import ca.uhn.fhir.util.VersionUtil;
-public class RestfulServer extends HttpServlet {
+public class RestfulServer extends HttpServlet implements IRestfulServer {
private static final ExceptionHandlingInterceptor DEFAULT_EXCEPTION_HANDLER = new ExceptionHandlingInterceptor();
private static final long serialVersionUID = 1L;
@@ -319,15 +319,12 @@ public class RestfulServer extends HttpServlet {
return count;
}
- /**
- * Returns the setting for automatically adding profile tags
- *
- * @see #setAddProfileTag(AddProfileTagEnum)
- */
+ @Override
public AddProfileTagEnum getAddProfileTag() {
return myAddProfileTag;
}
+ @Override
public BundleInclusionRule getBundleInclusionRule() {
return myBundleInclusionRule;
}
@@ -336,13 +333,12 @@ public class RestfulServer extends HttpServlet {
* 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() {
return myDefaultResponseEncoding;
}
- /**
- * Returns the server support for ETags (will not be null
). Default is {@link #DEFAULT_ETAG_SUPPORT}
- */
+ @Override
public ETagSupportEnum getETagSupport() {
return myETagSupport;
}
@@ -351,6 +347,7 @@ public class RestfulServer extends HttpServlet {
* Gets the {@link FhirContext} associated with this server. For efficient processing, resource providers and plain providers should generally use this context if one is needed, as opposed to
* creating their own.
*/
+ @Override
public FhirContext getFhirContext() {
if (myFhirContext == null) {
myFhirContext = new FhirContext();
@@ -365,10 +362,12 @@ public class RestfulServer extends HttpServlet {
/**
* Returns a ist of all registered server interceptors
*/
+ @Override
public List getInterceptors() {
return Collections.unmodifiableList(myInterceptors);
}
+ @Override
public IPagingProvider getPagingProvider() {
return myPagingProvider;
}
@@ -414,7 +413,7 @@ public class RestfulServer extends HttpServlet {
public IServerAddressStrategy getServerAddressStrategy() {
return myServerAddressStrategy;
}
-
+
/**
* Returns the server base URL (with no trailing '/') for a given request
*/
@@ -466,99 +465,15 @@ public class RestfulServer extends HttpServlet {
return myServerVersion;
}
- private void handlePagingRequest(RequestDetails theRequest, HttpServletResponse theResponse, String thePagingAction) throws IOException {
- IBundleProvider resultList = getPagingProvider().retrieveResultList(thePagingAction);
- if (resultList == null) {
- ourLog.info("Client requested unknown paging ID[{}]", thePagingAction);
- theResponse.setStatus(Constants.STATUS_HTTP_410_GONE);
- addHeadersToResponse(theResponse);
- theResponse.setContentType("text/plain");
- theResponse.setCharacterEncoding("UTF-8");
- theResponse.getWriter().append("Search ID[" + thePagingAction + "] does not exist and may have expired.");
- theResponse.getWriter().close();
- return;
- }
-
- Integer count = RestfulServerUtils.extractCountParameter(theRequest);
- if (count == null) {
- count = getPagingProvider().getDefaultPageSize();
- } else if (count > getPagingProvider().getMaximumPageSize()) {
- count = getPagingProvider().getMaximumPageSize();
- }
-
- Integer offsetI = RestfulServerUtils.tryToExtractNamedParameter(theRequest, Constants.PARAM_PAGINGOFFSET);
- if (offsetI == null || offsetI < 0) {
- offsetI = 0;
- }
-
- int start = Math.min(offsetI, resultList.size() - 1);
-
- EncodingEnum responseEncoding = RestfulServerUtils.determineResponseEncodingNoDefault(theRequest.getServletRequest(), getDefaultResponseEncoding());
- boolean prettyPrint = RestfulServerUtils.prettyPrintResponse(this, theRequest);
- boolean requestIsBrowser = requestIsBrowser(theRequest.getServletRequest());
- Set summaryMode = RestfulServerUtils.determineSummaryMode(theRequest);
- boolean respondGzip = theRequest.isRespondGzip();
-
- IVersionSpecificBundleFactory bundleFactory = getFhirContext().newBundleFactory();
-
- Set includes = new HashSet();
- String[] reqIncludes = theRequest.getServletRequest().getParameterValues(Constants.PARAM_INCLUDE);
- if (reqIncludes != null) {
- for (String nextInclude : reqIncludes) {
- includes.add(new Include(nextInclude));
- }
- }
-
- String linkSelfBase = getServerAddressStrategy().determineServerBase(getServletContext(), theRequest.getServletRequest());
- String linkSelf = linkSelfBase + theRequest.getCompleteUrl().substring(theRequest.getCompleteUrl().indexOf('?'));
-
- BundleTypeEnum bundleType = null;
- String[] bundleTypeValues = theRequest.getParameters().get(Constants.PARAM_BUNDLETYPE);
- if (bundleTypeValues != null) {
- bundleType = BundleTypeEnum.VALUESET_BINDER.fromCodeString(bundleTypeValues[0]);
- }
-
- bundleFactory.initializeBundleFromBundleProvider(this, resultList, responseEncoding, theRequest.getFhirServerBase(), linkSelf, prettyPrint, start, count, thePagingAction, bundleType, includes);
-
- Bundle bundle = bundleFactory.getDstu1Bundle();
- if (bundle != null) {
- for (int i = getInterceptors().size() - 1; i >= 0; i--) {
- IServerInterceptor next = getInterceptors().get(i);
- boolean continueProcessing = next.outgoingResponse(theRequest, bundle, theRequest.getServletRequest(), theRequest.getServletResponse());
- if (!continueProcessing) {
- ourLog.debug("Interceptor {} returned false, not continuing processing");
- return;
- }
- }
- RestfulServerUtils.streamResponseAsBundle(this, theResponse, bundle, theRequest.getFhirServerBase(), summaryMode, respondGzip, requestIsBrowser, theRequest);
- } else {
- IBaseResource resBundle = bundleFactory.getResourceBundle();
- for (int i = getInterceptors().size() - 1; i >= 0; i--) {
- IServerInterceptor next = getInterceptors().get(i);
- boolean continueProcessing = next.outgoingResponse(theRequest, resBundle, theRequest.getServletRequest(), theRequest.getServletResponse());
- if (!continueProcessing) {
- ourLog.debug("Interceptor {} returned false, not continuing processing");
- return;
- }
- }
- RestfulServerUtils.streamResponseAsResource(this, theResponse, resBundle, prettyPrint, summaryMode, Constants.STATUS_HTTP_200_OK, theRequest.isRespondGzip(), false, theRequest);
- }
- }
protected void handleRequest(RequestTypeEnum theRequestType, HttpServletRequest theRequest, HttpServletResponse theResponse) throws ServletException, IOException {
String fhirServerBase = null;
boolean requestIsBrowser = requestIsBrowser(theRequest);
- RequestDetails requestDetails = new RequestDetails();
+ ServletRequestDetails requestDetails = new ServletRequestDetails();
requestDetails.setServer(this);
requestDetails.setRequestType(theRequestType);
requestDetails.setServletRequest(theRequest);
requestDetails.setServletResponse(theResponse);
- for (Enumeration iter = theRequest.getHeaderNames(); iter.hasMoreElements(); ) {
- String nextName = iter.nextElement();
- for (Enumeration valueIter = theRequest.getHeaders(nextName); valueIter.hasMoreElements();) {
- requestDetails.addHeader(nextName, valueIter.nextElement());
- }
- }
try {
@@ -625,19 +540,19 @@ public class RestfulServer extends HttpServlet {
requestDetails.setFhirServerBase(fhirServerBase);
requestDetails.setCompleteUrl(completeUrl);
- String pagingAction = theRequest.getParameter(Constants.PARAM_PAGINGACTION);
- if (getPagingProvider() != null && isNotBlank(pagingAction)) {
- requestDetails.setRestOperationType(RestOperationTypeEnum.GET_PAGE);
- if (theRequestType != RequestTypeEnum.GET) {
- /*
- * We reconstruct the link-self URL using the request parameters, and this would break if the parameters came in using a POST. We could probably work around that but why bother unless
- * someone comes up with a reason for needing it.
- */
- throw new InvalidRequestException(getFhirContext().getLocalizer().getMessage(RestfulServer.class, "getPagesNonHttpGet"));
- }
- handlePagingRequest(requestDetails, theResponse, pagingAction);
- return;
- }
+// String pagingAction = theRequest.getParameter(Constants.PARAM_PAGINGACTION);
+// if (getPagingProvider() != null && isNotBlank(pagingAction)) {
+// requestDetails.setRestOperationType(RestOperationTypeEnum.GET_PAGE);
+// if (theRequestType != RequestTypeEnum.GET) {
+// /*
+// * We reconstruct the link-self URL using the request parameters, and this would break if the parameters came in using a POST. We could probably work around that but why bother unless
+// * someone comes up with a reason for needing it.
+// */
+// throw new InvalidRequestException(getFhirContext().getLocalizer().getMessage(RestfulServer.class, "getPagesNonHttpGet"));
+// }
+// handlePagingRequest(requestDetails, theResponse, pagingAction);
+// return;
+// }
BaseMethodBinding> resourceMethod = determineResourceMethod(requestDetails, requestPath);
@@ -892,6 +807,13 @@ public class RestfulServer extends HttpServlet {
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");
@@ -1117,10 +1039,12 @@ public class RestfulServer extends HttpServlet {
*
* @return Returns the default pretty print setting
*/
+ @Override
public boolean isDefaultPrettyPrint() {
return myDefaultPrettyPrint;
}
+ @Override
public boolean isUseBrowserFriendlyContentTypes() {
return myUseBrowserFriendlyContentTypes;
}
@@ -1369,4 +1293,86 @@ public class RestfulServer extends HttpServlet {
return userAgent != null && userAgent.contains("Mozilla");
}
+ 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;
+ }
+
+ 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/RestfulServerUtils.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java
index 23e3ae7e32b..5b919784fe5 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java
@@ -23,7 +23,6 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.io.IOException;
-import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.net.URLEncoder;
@@ -33,17 +32,15 @@ import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import java.util.zip.GZIPOutputStream;
-import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.utils.DateUtils;
@@ -215,10 +212,10 @@ public class RestfulServerUtils {
public static EncodingEnum determineRequestEncodingNoDefault(RequestDetails theReq) {
EncodingEnum retVal = null;
- Enumeration acceptValues = theReq.getServletRequest().getHeaders(Constants.HEADER_CONTENT_TYPE);
+ Iterator acceptValues = theReq.getHeaders(Constants.HEADER_CONTENT_TYPE).iterator();
if (acceptValues != null) {
- while (acceptValues.hasMoreElements() && retVal == null) {
- String nextAcceptHeaderValue = acceptValues.nextElement();
+ while (acceptValues.hasNext() && retVal == null) {
+ String nextAcceptHeaderValue = acceptValues.next();
if (nextAcceptHeaderValue != null && isNotBlank(nextAcceptHeaderValue)) {
for (String nextPart : nextAcceptHeaderValue.split(",")) {
int scIdx = nextPart.indexOf(';');
@@ -243,8 +240,8 @@ public class RestfulServerUtils {
/**
* Returns null if the request doesn't express that it wants FHIR. If it expresses that it wants XML and JSON equally, returns thePrefer.
*/
- public static EncodingEnum determineResponseEncodingNoDefault(HttpServletRequest theReq, EncodingEnum thePrefer) {
- String[] format = theReq.getParameterValues(Constants.PARAM_FORMAT);
+ public static EncodingEnum determineResponseEncodingNoDefault(RequestDetails theReq, EncodingEnum thePrefer) {
+ String[] format = theReq.getParameters().get(Constants.PARAM_FORMAT);
if (format != null) {
for (String nextFormat : format) {
EncodingEnum retVal = Constants.FORMAT_VAL_TO_ENCODING.get(nextFormat);
@@ -259,12 +256,11 @@ public class RestfulServerUtils {
*/
// text/xml, application/xml, application/xhtml+xml, text/html;q=0.9, text/plain;q=0.8, image/png, */*;q=0.5
- Enumeration acceptValues = theReq.getHeaders(Constants.HEADER_ACCEPT);
- if (acceptValues != null) {
- float bestQ = -1f;
- EncodingEnum retVal = null;
- while (acceptValues.hasMoreElements()) {
- String nextAcceptHeaderValue = acceptValues.nextElement();
+ List acceptValues = theReq.getHeaders(Constants.HEADER_ACCEPT);
+ float bestQ = -1f;
+ EncodingEnum retVal = null;
+ if (acceptValues != null) {
+ for (String nextAcceptHeaderValue : acceptValues) {
StringTokenizer tok = new StringTokenizer(nextAcceptHeaderValue, ",");
while (tok.hasMoreTokens()) {
String nextToken = tok.nextToken();
@@ -375,10 +371,10 @@ public class RestfulServerUtils {
/**
* Determine whether a response should be given in JSON or XML format based on the incoming HttpServletRequest's "_format"
parameter and "Accept:"
HTTP header.
*/
- public static EncodingEnum determineResponseEncodingWithDefault(RestfulServer theServer, HttpServletRequest theReq) {
- EncodingEnum retVal = determineResponseEncodingNoDefault(theReq, theServer.getDefaultResponseEncoding());
+ public static EncodingEnum determineResponseEncodingWithDefault(RequestDetails theReq) {
+ EncodingEnum retVal = determineResponseEncodingNoDefault(theReq, theReq.getServer().getDefaultResponseEncoding());
if (retVal == null) {
- retVal = theServer.getDefaultResponseEncoding();
+ retVal = theReq.getServer().getDefaultResponseEncoding();
}
return retVal;
}
@@ -420,14 +416,13 @@ public class RestfulServerUtils {
}
public static Integer extractCountParameter(RequestDetails theRequest) {
- String paramName = Constants.PARAM_COUNT;
- return tryToExtractNamedParameter(theRequest, paramName);
+ return RestfulServerUtils.tryToExtractNamedParameter(theRequest, Constants.PARAM_COUNT);
}
public static IParser getNewParser(FhirContext theContext, RequestDetails theRequestDetails) {
// Determine response encoding
- EncodingEnum responseEncoding = RestfulServerUtils.determineResponseEncodingWithDefault(theRequestDetails.getServer(), theRequestDetails.getServletRequest());
+ EncodingEnum responseEncoding = RestfulServerUtils.determineResponseEncodingWithDefault(theRequestDetails);
IParser parser;
switch (responseEncoding) {
case JSON:
@@ -444,17 +439,6 @@ public class RestfulServerUtils {
return parser;
}
- static Writer getWriter(HttpServletResponse theHttpResponse, boolean theRespondGzip) throws UnsupportedEncodingException, IOException {
- Writer writer;
- if (theRespondGzip) {
- theHttpResponse.addHeader(Constants.HEADER_CONTENT_ENCODING, Constants.ENCODING_GZIP);
- writer = new OutputStreamWriter(new GZIPOutputStream(theHttpResponse.getOutputStream()), "UTF-8");
- } else {
- writer = theHttpResponse.getWriter();
- }
- return writer;
- }
-
public static Set parseAcceptHeaderAndReturnHighestRankedOptions(HttpServletRequest theRequest) {
Set retVal = new HashSet();
@@ -476,7 +460,8 @@ public class RestfulServerUtils {
try {
q = Float.parseFloat(value);
q = Math.max(q, 0.0f);
- } catch (NumberFormatException e) {
+ }
+ catch (NumberFormatException e) {
ourLog.debug("Invalid Accept header q value: {}", value);
}
}
@@ -536,7 +521,7 @@ public class RestfulServerUtils {
return null;
}
- public static boolean prettyPrintResponse(RestfulServer theServer, RequestDetails theRequest) {
+ public static boolean prettyPrintResponse(IRestfulServerDefaults theServer, RequestDetails theRequest) {
Map requestParams = theRequest.getParameters();
String[] pretty = requestParams.get(Constants.PARAM_PRETTY);
boolean prettyPrint;
@@ -548,10 +533,9 @@ public class RestfulServerUtils {
}
} else {
prettyPrint = theServer.isDefaultPrettyPrint();
- Enumeration acceptValues = theRequest.getServletRequest().getHeaders(Constants.HEADER_ACCEPT);
+ List acceptValues = theRequest.getHeaders(Constants.HEADER_ACCEPT);
if (acceptValues != null) {
- while (acceptValues.hasMoreElements()) {
- String nextAcceptHeaderValue = acceptValues.nextElement();
+ for (String nextAcceptHeaderValue : acceptValues) {
if (nextAcceptHeaderValue.contains("pretty=true")) {
prettyPrint = true;
}
@@ -561,54 +545,59 @@ public class RestfulServerUtils {
return prettyPrint;
}
- public static void streamResponseAsBundle(RestfulServer theServer, HttpServletResponse theHttpResponse, Bundle bundle, String theServerBase, Set theSummaryMode, boolean theRespondGzip,
- boolean theRequestIsBrowser, RequestDetails theRequestDetails) throws IOException {
- assert !theServerBase.endsWith("/");
+ public static Object streamResponseAsBundle(IRestfulServerDefaults theServer, Bundle bundle, Set theSummaryMode,
+ boolean theRequestIsBrowser, boolean respondGzip, RequestDetails theRequestDetails)
+ throws IOException {
- theHttpResponse.setStatus(200);
+ int status = 200;
// Determine response encoding
- EncodingEnum responseEncoding = RestfulServerUtils.determineResponseEncodingWithDefault(theServer, theRequestDetails.getServletRequest());
+ EncodingEnum responseEncoding = RestfulServerUtils.determineResponseEncodingWithDefault(theRequestDetails);
+ String contentType;
if (theRequestIsBrowser && theServer.isUseBrowserFriendlyContentTypes()) {
- theHttpResponse.setContentType(responseEncoding.getBrowserFriendlyBundleContentType());
+ contentType = responseEncoding.getBrowserFriendlyBundleContentType();
} else {
- theHttpResponse.setContentType(responseEncoding.getBundleContentType());
+ contentType = responseEncoding.getBundleContentType();
}
- theHttpResponse.setCharacterEncoding(Constants.CHARSET_NAME_UTF8);
+ String charset = Constants.CHARSET_NAME_UTF8;
+ Writer writer = theRequestDetails.getResponse().getResponseWriter(status, contentType, charset, respondGzip);
- theServer.addHeadersToResponse(theHttpResponse);
-
- Writer writer = RestfulServerUtils.getWriter(theHttpResponse, theRespondGzip);
try {
IParser parser = RestfulServerUtils.getNewParser(theServer.getFhirContext(), theRequestDetails);
if (theSummaryMode.contains(SummaryEnum.TEXT)) {
parser.setEncodeElements(TEXT_ENCODE_ELEMENTS);
}
parser.encodeBundleToWriter(bundle, writer);
- } finally {
- writer.close();
}
+ catch (Exception e) {
+ //always send a response, even if the parsing went wrong
+ }
+ return theRequestDetails.getResponse().sendWriterResponse(status, contentType, charset, writer);
}
- public static void streamResponseAsResource(RestfulServer theServer, HttpServletResponse theHttpResponse, IBaseResource theResource, boolean theRequestIsBrowser, Set theSummaryMode,
- int stausCode, boolean theRespondGzip, boolean theAddContentLocationHeader, RequestDetails theRequestDetails) throws IOException {
- theHttpResponse.setStatus(stausCode);
+ public static Object streamResponseAsResource(IRestfulServerDefaults theServer, IBaseResource theResource, boolean theRequestIsBrowser, Set theSummaryMode,
+ int stausCode, boolean theRespondGzip, boolean theAddContentLocationHeader, boolean respondGzip,
+ RequestDetails theRequestDetails)
+ throws IOException {
+ IRestfulResponse restUtil = theRequestDetails.getResponse();
// Determine response encoding
- EncodingEnum responseEncoding = RestfulServerUtils.determineResponseEncodingNoDefault(theRequestDetails.getServletRequest(), theServer.getDefaultResponseEncoding());
+ EncodingEnum responseEncoding = RestfulServerUtils.determineResponseEncodingNoDefault(theRequestDetails,
+ theServer.getDefaultResponseEncoding());
String serverBase = theRequestDetails.getFhirServerBase();
- if (theAddContentLocationHeader && theResource.getIdElement() != null && theResource.getIdElement().hasIdPart() && isNotBlank(serverBase)) {
+ if (theAddContentLocationHeader && theResource.getIdElement() != null && theResource.getIdElement().hasIdPart()
+ && isNotBlank(serverBase)) {
String resName = theServer.getFhirContext().getResourceDefinition(theResource).getName();
IIdType fullId = theResource.getIdElement().withServerBase(serverBase, resName);
- theHttpResponse.addHeader(Constants.HEADER_CONTENT_LOCATION, fullId.getValue());
+ restUtil.addHeader(Constants.HEADER_CONTENT_LOCATION, fullId.getValue());
}
if (theServer.getETagSupport() == ETagSupportEnum.ENABLED) {
if (theResource.getIdElement().hasVersionIdPart()) {
- theHttpResponse.addHeader(Constants.HEADER_ETAG, "W/\"" + theResource.getIdElement().getVersionIdPart() + '"');
+ restUtil.addHeader(Constants.HEADER_ETAG, "W/\"" + theResource.getIdElement().getVersionIdPart() + '"');
}
}
@@ -619,26 +608,19 @@ public class RestfulServerUtils {
}
}
+ String contentType;
if (theResource instanceof IBaseBinary && responseEncoding == null) {
IBaseBinary bin = (IBaseBinary) theResource;
if (isNotBlank(bin.getContentType())) {
- theHttpResponse.setContentType(bin.getContentType());
+ contentType = bin.getContentType();
} else {
- theHttpResponse.setContentType(Constants.CT_OCTET_STREAM);
+ contentType = Constants.CT_OCTET_STREAM;
}
- if (bin.getContent() == null || bin.getContent().length == 0) {
- return;
- }
-
// Force binary resources to download - This is a security measure to prevent
// malicious images or HTML blocks being served up as content.
- theHttpResponse.addHeader(Constants.HEADER_CONTENT_DISPOSITION, "Attachment;");
+ restUtil.addHeader(Constants.HEADER_CONTENT_DISPOSITION, "Attachment;");
- theHttpResponse.setContentLength(bin.getContent().length);
- ServletOutputStream oos = theHttpResponse.getOutputStream();
- oos.write(bin.getContent());
- oos.close();
- return;
+ return restUtil.sendAttachmentResponse(bin, stausCode, contentType);
}
// Ok, we're not serving a binary resource, so apply default encoding
@@ -656,38 +638,36 @@ public class RestfulServerUtils {
}
if (theRequestIsBrowser && theServer.isUseBrowserFriendlyContentTypes()) {
- theHttpResponse.setContentType(responseEncoding.getBrowserFriendlyBundleContentType());
+ contentType = responseEncoding.getBrowserFriendlyBundleContentType();
} else if (encodingDomainResourceAsText) {
- theHttpResponse.setContentType(Constants.CT_HTML);
+ contentType = Constants.CT_HTML;
} else {
- theHttpResponse.setContentType(responseEncoding.getResourceContentType());
+ contentType = responseEncoding.getResourceContentType();
}
- theHttpResponse.setCharacterEncoding(Constants.CHARSET_NAME_UTF8);
-
- theServer.addHeadersToResponse(theHttpResponse);
+ String charset = Constants.CHARSET_NAME_UTF8;
if (theResource instanceof IResource) {
InstantDt lastUpdated = ResourceMetadataKeyEnum.UPDATED.get((IResource) theResource);
if (lastUpdated != null && lastUpdated.isEmpty() == false) {
- theHttpResponse.addHeader(Constants.HEADER_LAST_MODIFIED, DateUtils.formatDate(lastUpdated.getValue()));
+ restUtil.addHeader(Constants.HEADER_LAST_MODIFIED, DateUtils.formatDate(lastUpdated.getValue()));
}
TagList list = (TagList) ((IResource) theResource).getResourceMetadata().get(ResourceMetadataKeyEnum.TAG_LIST);
if (list != null) {
for (Tag tag : list) {
if (StringUtils.isNotBlank(tag.getTerm())) {
- theHttpResponse.addHeader(Constants.HEADER_CATEGORY, tag.toHeaderValue());
+ restUtil.addHeader(Constants.HEADER_CATEGORY, tag.toHeaderValue());
}
}
}
} else {
Date lastUpdated = ((IAnyResource) theResource).getMeta().getLastUpdated();
if (lastUpdated != null) {
- theHttpResponse.addHeader(Constants.HEADER_LAST_MODIFIED, DateUtils.formatDate(lastUpdated));
+ restUtil.addHeader(Constants.HEADER_LAST_MODIFIED, DateUtils.formatDate(lastUpdated));
}
}
- Writer writer = getWriter(theHttpResponse, theRespondGzip);
+ Writer writer = restUtil.getResponseWriter(stausCode, contentType, charset, respondGzip);
try {
if (encodingDomainResourceAsText && theResource instanceof IResource) {
writer.append(((IResource) theResource).getText().getDiv().getValueAsString());
@@ -695,9 +675,11 @@ public class RestfulServerUtils {
IParser parser = getNewParser(theServer.getFhirContext(), theRequestDetails);
parser.encodeResourceToWriter(theResource, writer);
}
- } finally {
- writer.close();
+ } catch (Exception e) {
+ //always send a response, even if the parsing went wrong
}
+ return restUtil.sendWriterResponse(stausCode, contentType, charset, writer);
+
}
// static Integer tryToExtractNamedParameter(HttpServletRequest theRequest, String name) {
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
new file mode 100644
index 00000000000..195a77b62fe
--- /dev/null
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestulfulServerConfiguration.java
@@ -0,0 +1,155 @@
+package ca.uhn.fhir.rest.server;
+
+import java.util.Collection;
+import java.util.List;
+
+import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.rest.method.BaseMethodBinding;
+
+public class RestulfulServerConfiguration {
+
+ private Collection resourceBindings;
+ private List> serverBindings;
+ private String implementationDescription;
+ private String serverVersion;
+ private String serverName;
+ private FhirContext fhirContext;
+ private IServerAddressStrategy serverAddressStrategy;
+ private String conformanceDate;
+
+ /**
+ * Get the resourceBindings
+ * @return the resourceBindings
+ */
+ public Collection getResourceBindings() {
+ return resourceBindings;
+ }
+
+ /**
+ * Set the resourceBindings
+ * @param resourceBindings the resourceBindings to set
+ */
+ public RestulfulServerConfiguration setResourceBindings(Collection resourceBindings) {
+ this.resourceBindings = resourceBindings;
+ return this;
+ }
+
+ /**
+ * Get the serverBindings
+ * @return the serverBindings
+ */
+ public List> getServerBindings() {
+ return serverBindings;
+ }
+
+ /**
+ * Set the serverBindings
+ * @param serverBindings the serverBindings to set
+ */
+ public RestulfulServerConfiguration setServerBindings(List> serverBindings) {
+ this.serverBindings = serverBindings;
+ return this;
+ }
+
+ /**
+ * Get the implementationDescription
+ * @return the implementationDescription
+ */
+ public String getImplementationDescription() {
+ return implementationDescription;
+ }
+
+ /**
+ * Set the implementationDescription
+ * @param implementationDescription the implementationDescription to set
+ */
+ public RestulfulServerConfiguration setImplementationDescription(String implementationDescription) {
+ this.implementationDescription = implementationDescription;
+ return this;
+ }
+
+ /**
+ * Get the serverVersion
+ * @return the serverVersion
+ */
+ public String getServerVersion() {
+ return serverVersion;
+ }
+
+ /**
+ * Set the serverVersion
+ * @param serverVersion the serverVersion to set
+ */
+ public RestulfulServerConfiguration setServerVersion(String serverVersion) {
+ this.serverVersion = serverVersion;
+ return this;
+ }
+
+ /**
+ * Get the serverName
+ * @return the serverName
+ */
+ public String getServerName() {
+ return serverName;
+ }
+
+ /**
+ * Set the serverName
+ * @param serverName the serverName to set
+ */
+ public RestulfulServerConfiguration setServerName(String serverName) {
+ this.serverName = serverName;
+ return this;
+ }
+
+ /**
+ * Gets the {@link FhirContext} associated with this server. For efficient processing, resource providers and plain providers should generally use this context if one is needed, as opposed to
+ * creating their own.
+ */
+ public FhirContext getFhirContext() {
+ return this.fhirContext;
+ }
+
+ /**
+ * Set the fhirContext
+ * @param fhirContext the fhirContext to set
+ */
+ public RestulfulServerConfiguration setFhirContext(FhirContext fhirContext) {
+ this.fhirContext = fhirContext;
+ return this;
+ }
+
+ /**
+ * Get the serverAddressStrategy
+ * @return the serverAddressStrategy
+ */
+ public IServerAddressStrategy getServerAddressStrategy() {
+ return serverAddressStrategy;
+ }
+
+ /**
+ * Set the serverAddressStrategy
+ * @param serverAddressStrategy the serverAddressStrategy to set
+ */
+ public void setServerAddressStrategy(IServerAddressStrategy serverAddressStrategy) {
+ this.serverAddressStrategy = serverAddressStrategy;
+ }
+
+
+ /**
+ * Get the conformanceDate
+ * @return the conformanceDate
+ */
+ public String getConformanceDate() {
+ return conformanceDate;
+ }
+
+ /**
+ * Set the conformanceDate
+ * @param conformanceDate the conformanceDate to set
+ */
+ public void setConformanceDate(String conformanceDate) {
+ this.conformanceDate = conformanceDate;
+ }
+
+}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/ExceptionHandlingInterceptor.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/ExceptionHandlingInterceptor.java
index b9ef3b6751a..82b650bac45 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/ExceptionHandlingInterceptor.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/ExceptionHandlingInterceptor.java
@@ -36,7 +36,7 @@ import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.api.SummaryEnum;
import ca.uhn.fhir.rest.method.RequestDetails;
-import ca.uhn.fhir.rest.server.RestfulServerUtils;
+import ca.uhn.fhir.rest.server.IRestfulResponse;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.util.OperationOutcomeUtil;
@@ -48,8 +48,14 @@ public class ExceptionHandlingInterceptor extends InterceptorAdapter {
private Class>[] myReturnStackTracesForExceptionTypes;
@Override
- public boolean handleException(RequestDetails theRequestDetails, BaseServerResponseException theException, HttpServletRequest theRequest, HttpServletResponse theResponse)
+ public boolean handleException(RequestDetails theRequestDetails, BaseServerResponseException theException, HttpServletRequest theRequest, HttpServletResponse theResponse) throws ServletException, IOException {
+ handleException(theRequestDetails, theException);
+ return false;
+ }
+
+ public Object handleException(RequestDetails theRequestDetails, BaseServerResponseException theException)
throws ServletException, IOException {
+ IRestfulResponse response = theRequestDetails.getResponse();
FhirContext ctx = theRequestDetails.getServer().getFhirContext();
@@ -67,22 +73,19 @@ public class ExceptionHandlingInterceptor extends InterceptorAdapter {
if (isNotBlank(next.getKey()) && next.getValue() != null) {
String nextKey = next.getKey();
for (String nextValue : next.getValue()) {
- theResponse.addHeader(nextKey, nextValue);
+ response.addHeader(nextKey, nextValue);
}
}
}
}
- RestfulServerUtils.streamResponseAsResource(theRequestDetails.getServer(), theResponse, oo, true, Collections.singleton(SummaryEnum.FALSE), statusCode, false, false, theRequestDetails);
-
+ return response.streamResponseAsResource(oo, true, Collections.singleton(SummaryEnum.FALSE), statusCode, false, false);
// theResponse.setStatus(statusCode);
// theRequestDetails.getServer().addHeadersToResponse(theResponse);
// theResponse.setContentType("text/plain");
// theResponse.setCharacterEncoding("UTF-8");
// theResponse.getWriter().append(theException.getMessage());
// theResponse.getWriter().close();
-
- return false;
}
@Override
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/IServerInterceptor.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/IServerInterceptor.java
index 5e0e495b556..bf18100668f 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/IServerInterceptor.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/IServerInterceptor.java
@@ -148,6 +148,26 @@ public interface IServerInterceptor {
* client.
*/
boolean outgoingResponse(RequestDetails theRequestDetails, Bundle theResponseObject, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException;
+
+ /**
+ * This method is called after the server implementation method has been called, but before any attempt to stream the response back to the client
+ *
+ * @param theRequestDetails
+ * A bean containing details about the request that is about to be processed, including
+ * @param theResponseObject
+ * The actual object which is being streamed to the client as a response
+ * @param theServletRequest
+ * The incoming request
+ * @param theServletResponse
+ * The response. Note that interceptors may choose to provide a response (i.e. by calling {@link HttpServletResponse#getWriter()}) but in that case it is important to return
+ * false
to indicate that the server itself should not also provide a response.
+ * @return Return true
if processing should continue normally. This is generally the right thing to do. If your interceptor is providing a response rather than letting HAPI handle the
+ * response normally, you must return false
. In this case, no further processing will occur and no further interceptors will be called.
+ * @throws AuthenticationException
+ * This exception may be thrown to indicate that the interceptor has detected an unauthorized access attempt. If thrown, processing will stop and an HTTP 401 will be returned to the
+ * client.
+ */
+ boolean outgoingResponse(RequestDetails theRequest, Bundle bundle);
/**
* This method is called after the server implementation method has been called, but before any attempt to stream the response back to the client
@@ -167,6 +187,20 @@ public interface IServerInterceptor {
* client.
*/
boolean outgoingResponse(RequestDetails theRequestDetails, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException;
+
+ /**
+ * This method is called after the server implementation method has been called, but before any attempt to stream the response back to the client
+ *
+ * @param theRequestDetails
+ * A bean containing details about the request that is about to be processed, including details such as the resource type and logical ID (if any) and other FHIR-specific aspects of the
+ * request which have been pulled out of the {@link HttpServletRequest servlet request}.
+ * @return Return true
if processing should continue normally. This is generally the right thing to do. If your interceptor is providing a response rather than letting HAPI handle the
+ * response normally, you must return false
. In this case, no further processing will occur and no further interceptors will be called.
+ * @throws AuthenticationException
+ * This exception may be thrown to indicate that the interceptor has detected an unauthorized access attempt. If thrown, processing will stop and an HTTP 401 will be returned to the
+ * client.
+ */
+ boolean outgoingResponse(RequestDetails theRequestDetails);
/**
* This method is called after the server implementation method has been called, but before any attempt to stream the response back to the client
@@ -189,6 +223,22 @@ public interface IServerInterceptor {
*/
boolean outgoingResponse(RequestDetails theRequestDetails, IBaseResource theResponseObject, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse)
throws AuthenticationException;
+
+ /**
+ * This method is called after the server implementation method has been called, but before any attempt to stream the response back to the client
+ *
+ * @param theRequestDetails
+ * A bean containing details about the request that is about to be processed, including details such as the resource type and logical ID (if any) and other FHIR-specific aspects of the
+ * request which have been pulled out of the {@link HttpServletRequest servlet request}.
+ * @param theResponseObject
+ * The actual object which is being streamed to the client as a response
+ * @return Return true
if processing should continue normally. This is generally the right thing to do. If your interceptor is providing a response rather than letting HAPI handle the
+ * response normally, you must return false
. In this case, no further processing will occur and no further interceptors will be called.
+ * @throws AuthenticationException
+ * This exception may be thrown to indicate that the interceptor has detected an unauthorized access attempt. If thrown, processing will stop and an HTTP 401 will be returned to the
+ * client.
+ */
+ boolean outgoingResponse(RequestDetails theRequestDetails, IBaseResource theResponseObject);
/**
* This method is called after the server implementation method has been called, but before any attempt to stream the response back to the client
@@ -211,6 +261,22 @@ public interface IServerInterceptor {
*/
boolean outgoingResponse(RequestDetails theRequestDetails, TagList theResponseObject, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException;
+ /**
+ * This method is called after the server implementation method has been called, but before any attempt to stream the response back to the client
+ *
+ * @param theRequestDetails
+ * A bean containing details about the request that is about to be processed, including details such as the resource type and logical ID (if any) and other FHIR-specific aspects of the
+ * request which have been pulled out of the {@link HttpServletRequest servlet request}.
+ * @param theResponseObject
+ * The actual object which is being streamed to the client as a response
+ * @return Return true
if processing should continue normally. This is generally the right thing to do. If your interceptor is providing a response rather than letting HAPI handle the
+ * response normally, you must return false
. In this case, no further processing will occur and no further interceptors will be called.
+ * @throws AuthenticationException
+ * This exception may be thrown to indicate that the interceptor has detected an unauthorized access attempt. If thrown, processing will stop and an HTTP 401 will be returned to the
+ * client.
+ */
+ boolean outgoingResponse(RequestDetails theRequestDetails, TagList theResponseObject);
+
/**
* This method is called upon any exception being thrown within the server's request processing code. This includes any exceptions thrown within resource provider methods (e.g. {@link Search} and
* {@link Read} methods) as well as any runtime exceptions thrown by the server itself. This method is invoked for each interceptor (until one of them returns a non-null
response or
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/InterceptorAdapter.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/InterceptorAdapter.java
index b3a21db983c..dbc9e4278b6 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/InterceptorAdapter.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/InterceptorAdapter.java
@@ -34,6 +34,7 @@ import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.method.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
+import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
/**
* Base class for {@link IServerInterceptor} implementations. Provides a No-op implementation
@@ -66,21 +67,45 @@ public class InterceptorAdapter implements IServerInterceptor {
public boolean outgoingResponse(RequestDetails theRequestDetails, Bundle theResponseObject, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException {
return true;
}
+
+ @Override
+ public boolean outgoingResponse(RequestDetails theRequestDetails, Bundle bundle) {
+ ServletRequestDetails details = (ServletRequestDetails) theRequestDetails;
+ return outgoingResponse(details, bundle, details.getServletRequest(), details.getServletResponse());
+ }
@Override
public boolean outgoingResponse(RequestDetails theRequestDetails, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException {
return true;
}
+
+ @Override
+ public boolean outgoingResponse(RequestDetails theRequestDetails) {
+ ServletRequestDetails details = (ServletRequestDetails) theRequestDetails;
+ return outgoingResponse(theRequestDetails, details.getServletRequest(), details.getServletResponse());
+ }
@Override
public boolean outgoingResponse(RequestDetails theRequestDetails, IBaseResource theResponseObject, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException {
return true;
}
+ @Override
+ public boolean outgoingResponse(RequestDetails theRequestDetails, IBaseResource theResponseObject) {
+ ServletRequestDetails details = (ServletRequestDetails) theRequestDetails;
+ return outgoingResponse(details, theResponseObject, details.getServletRequest(), details.getServletResponse());
+ }
+
@Override
public boolean outgoingResponse(RequestDetails theRequestDetails, TagList theResponseObject, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException {
return true;
}
+
+ @Override
+ public boolean outgoingResponse(RequestDetails theRequestDetails, TagList theResponseObject) {
+ ServletRequestDetails details = (ServletRequestDetails) theRequestDetails;
+ return outgoingResponse(details, theResponseObject, details.getServletRequest(), details.getServletResponse());
+ }
@Override
public BaseServerResponseException preProcessOutgoingException(RequestDetails theRequestDetails, Throwable theException, HttpServletRequest theServletRequest) throws ServletException {
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/LoggingInterceptor.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/LoggingInterceptor.java
index 8a98133dee1..06151a32c23 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/LoggingInterceptor.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/LoggingInterceptor.java
@@ -269,7 +269,7 @@ public class LoggingInterceptor extends InterceptorAdapter {
} else if (theKey.startsWith("remoteAddr")) {
return StringUtils.defaultString(myRequest.getRemoteAddr());
} else if (theKey.equals("responseEncodingNoDefault")) {
- EncodingEnum encoding = RestfulServerUtils.determineResponseEncodingNoDefault(myRequest, myRequestDetails.getServer().getDefaultResponseEncoding());
+ EncodingEnum encoding = RestfulServerUtils.determineResponseEncodingNoDefault(myRequestDetails, myRequestDetails.getServer().getDefaultResponseEncoding());
if (encoding != null) {
return encoding.name();
} else {
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/ResponseHighlighterInterceptor.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/ResponseHighlighterInterceptor.java
index 390687d8ad9..864aa0c4d13 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/ResponseHighlighterInterceptor.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/ResponseHighlighterInterceptor.java
@@ -171,7 +171,7 @@ public class ResponseHighlighterInterceptor extends InterceptorAdapter {
/*
* It's not a browser...
*/
- Set highestRankedAcceptValues = RestfulServerUtils.parseAcceptHeaderAndReturnHighestRankedOptions(theRequestDetails.getServletRequest());
+ Set highestRankedAcceptValues = RestfulServerUtils.parseAcceptHeaderAndReturnHighestRankedOptions(theServletRequest);
if (highestRankedAcceptValues.contains(Constants.CT_HTML) == false) {
return super.outgoingResponse(theRequestDetails, theResponseObject, theServletRequest, theServletResponse);
}
@@ -284,7 +284,7 @@ public class ResponseHighlighterInterceptor extends InterceptorAdapter {
/*
* It's not a browser...
*/
- Set accept = RestfulServerUtils.parseAcceptHeaderAndReturnHighestRankedOptions(theRequestDetails.getServletRequest());
+ Set accept = RestfulServerUtils.parseAcceptHeaderAndReturnHighestRankedOptions(theServletRequest);
if (!accept.contains(Constants.CT_HTML)) {
return super.handleException(theRequestDetails, theException, theServletRequest, theServletResponse);
}
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
new file mode 100644
index 00000000000..86d4e83e765
--- /dev/null
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/servlet/ServletRequestDetails.java
@@ -0,0 +1,145 @@
+package ca.uhn.fhir.rest.server.servlet;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.io.IOUtils;
+
+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.RestfulServer;
+import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
+
+public class ServletRequestDetails extends RequestDetails {
+
+ private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ServletRequestDetails.class);
+ /**
+ * @see BaseMethodBinding#loadRequestContents(RequestDetails)
+ */
+ private static volatile IRequestReader ourRequestReader;
+ private RestfulServer myServer;
+ private HttpServletRequest myServletRequest;
+ private HttpServletResponse myServletResponse;
+ private byte[] requestContents;
+
+ public ServletRequestDetails() {
+ super();
+ setResponse(new ServletRestfulResponse(this));
+ }
+
+ @Override
+ protected byte[] getByteStreamRequestContents() {
+ /*
+ * This is weird, but this class is used both in clients and in servers, and we want to avoid needing to depend on
+ * servlet-api in clients since there is no point. So we dynamically load a class that does the servlet processing
+ * in servers. Down the road it may make sense to just split the method binding classes into server and client
+ * versions, but this isn't actually a huge deal I don't think.
+ */
+ IRequestReader reader = ourRequestReader;
+ if (reader == null) {
+ try {
+ Class.forName("javax.servlet.ServletInputStream");
+ String className = BaseMethodBinding.class.getName() + "$" + "ActiveRequestReader";
+ try {
+ reader = (IRequestReader) Class.forName(className).newInstance();
+ } catch (Exception e1) {
+ throw new ConfigurationException("Failed to instantiate class " + className, e1);
+ }
+ } catch (ClassNotFoundException e) {
+ String className = BaseMethodBinding.class.getName() + "$" + "InactiveRequestReader";
+ try {
+ reader = (IRequestReader) Class.forName(className).newInstance();
+ } catch (Exception e1) {
+ throw new ConfigurationException("Failed to instantiate class " + className, e1);
+ }
+ }
+ ourRequestReader = reader;
+ }
+
+ try {
+ InputStream inputStream = reader.getInputStream(this);
+ requestContents = IOUtils.toByteArray(inputStream);
+ return requestContents;
+ } catch (IOException e) {
+ ourLog.error("Could not load request resource", e);
+ throw new InvalidRequestException(String.format("Could not load request resource: %s", e.getMessage()));
+ }
+ }
+
+ @Override
+ public String getHeader(String name) {
+ return getServletRequest().getHeader(name);
+ }
+
+ @Override
+ public List getHeaders(String name) {
+ Enumeration headers = getServletRequest().getHeaders(name);
+ return headers == null ? Collections. emptyList() : Collections.list(getServletRequest().getHeaders(name));
+ }
+
+ @Override
+ public InputStream getInputStream() throws IOException {
+ return getServletRequest().getInputStream();
+ }
+
+ @Override
+ public Reader getReader() throws IOException {
+ return getServletRequest().getReader();
+ }
+
+ @Override
+ public RestfulServer getServer() {
+ return myServer;
+ }
+
+ @Override
+ public String getServerBaseForRequest() {
+ return getServer().getServerBaseForRequest(getServletRequest());
+ }
+
+ public HttpServletRequest getServletRequest() {
+ return myServletRequest;
+ }
+
+ public HttpServletResponse getServletResponse() {
+ return myServletResponse;
+ }
+
+ public void setServer(RestfulServer theServer) {
+ this.myServer = theServer;
+ }
+
+ public void setServletRequest(HttpServletRequest myServletRequest) {
+ this.myServletRequest = myServletRequest;
+ }
+
+ public void setServletResponse(HttpServletResponse myServletResponse) {
+ this.myServletResponse = myServletResponse;
+ }
+
+ public static RequestDetails withResourceAndParams(String theResourceName, RequestTypeEnum theRequestType, Set theParamNames) {
+ RequestDetails retVal = new ServletRequestDetails();
+ retVal.setResourceName(theResourceName);
+ retVal.setRequestType(theRequestType);
+ Map paramNames = new HashMap();
+ for (String next : theParamNames) {
+ paramNames.put(next, new String[0]);
+ }
+ retVal.setParameters(paramNames);
+ return retVal;
+ }
+
+}
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
new file mode 100644
index 00000000000..97ce9f5ebfb
--- /dev/null
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/servlet/ServletRestfulResponse.java
@@ -0,0 +1,77 @@
+package ca.uhn.fhir.rest.server.servlet;
+
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+import java.util.Map.Entry;
+import java.util.zip.GZIPOutputStream;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletResponse;
+
+import org.hl7.fhir.instance.model.api.IBaseBinary;
+
+import ca.uhn.fhir.rest.api.MethodOutcome;
+import ca.uhn.fhir.rest.method.ParseAction;
+import ca.uhn.fhir.rest.server.Constants;
+import ca.uhn.fhir.rest.server.RestfulResponse;
+
+public class ServletRestfulResponse extends RestfulResponse {
+
+ public ServletRestfulResponse(ServletRequestDetails servletRequestDetails) {
+ super(servletRequestDetails);
+ }
+
+ @Override
+ public Object sendAttachmentResponse(IBaseBinary bin, int stausCode, String contentType) throws IOException {
+ addHeaders();
+ HttpServletResponse theHttpResponse = getRequestDetails().getServletResponse();
+ theHttpResponse.setStatus(stausCode);
+ theHttpResponse.setContentType(contentType);
+ if (bin.getContent() == null || bin.getContent().length == 0) {
+ return null;
+ } else {
+ theHttpResponse.setContentLength(bin.getContent().length);
+ ServletOutputStream oos = theHttpResponse.getOutputStream();
+ oos.write(bin.getContent());
+ oos.close();
+ return null;
+ }
+ }
+
+ @Override
+ public Writer getResponseWriter(int statusCode, String contentType, String charset, boolean theRespondGzip) throws UnsupportedEncodingException, IOException {
+ addHeaders();
+ HttpServletResponse theHttpResponse = getRequestDetails().getServletResponse();
+ theHttpResponse.setCharacterEncoding(charset);
+ theHttpResponse.setStatus(statusCode);
+ theHttpResponse.setContentType(contentType);
+ if (theRespondGzip) {
+ theHttpResponse.addHeader(Constants.HEADER_CONTENT_ENCODING, Constants.ENCODING_GZIP);
+ return new OutputStreamWriter(new GZIPOutputStream(theHttpResponse.getOutputStream()), Constants.CHARSET_NAME_UTF8);
+ } else {
+ return theHttpResponse.getWriter();
+ }
+ }
+
+
+ private void addHeaders() {
+ HttpServletResponse theHttpResponse = getRequestDetails().getServletResponse();
+ getRequestDetails().getServer().addHeadersToResponse(theHttpResponse);
+ for (Entry header : getHeaders().entrySet()) {
+ theHttpResponse.setHeader(header.getKey(), header.getValue());
+ }
+ }
+
+ public final Object sendWriterResponse(int status, String contentType, String charset, Writer writer) throws IOException {
+ writer.close();
+ return null;
+ }
+
+ @Override
+ public Object returnResponse(ParseAction> outcome, int operationStatus, boolean allowPrefer, MethodOutcome response,
+ String resourceName) throws IOException {
+ return getRequestDetails().getServer().returnResponse(getRequestDetails(), outcome, operationStatus, allowPrefer, response, resourceName);
+ }
+}
diff --git a/hapi-fhir-jaxrsserver-base/pom.xml b/hapi-fhir-jaxrsserver-base/pom.xml
new file mode 100644
index 00000000000..f19b179b093
--- /dev/null
+++ b/hapi-fhir-jaxrsserver-base/pom.xml
@@ -0,0 +1,110 @@
+
+ 4.0.0
+
+
+ ca.uhn.hapi.fhir
+ hapi-deployable-pom
+ 1.4-SNAPSHOT
+ ../hapi-deployable-pom/pom.xml
+
+
+
+
+ maven.java.net
+
+ true
+
+
+ false
+
+ https://maven.java.net/service/local/repositories/snapshots/content/
+
+
+
+ hapi-fhir-jaxrsserver-base
+ jar
+
+ HAPI FHIR JAX-RS Server
+
+
+
+
+ ca.uhn.hapi.fhir
+ hapi-fhir-base
+ 1.4-SNAPSHOT
+
+
+ commons-logging
+ commons-logging
+
+
+
+
+
+ ca.uhn.hapi.fhir
+ hapi-fhir-structures-dstu2
+ 1.4-SNAPSHOT
+
+
+
+ javax.ws.rs
+ javax.ws.rs-api
+ 2.0
+ provided
+
+
+ javax.ejb
+ ejb-api
+ 3.0
+ provided
+
+
+
+ org.eclipse.jetty
+ jetty-server
+ ${jetty_version}
+ test
+
+
+ org.eclipse.jetty
+ jetty-servlet
+ ${jetty_version}
+ test
+
+
+ org.glassfish.jersey.core
+ jersey-server
+ ${jersey_version}
+ test
+
+
+ org.glassfish.jersey.containers
+ jersey-container-servlet-core
+ ${jersey_version}
+ test
+
+
+ org.glassfish.jersey.containers
+ jersey-container-jetty-http
+ ${jersey_version}
+ test
+
+
+ org.glassfish.jersey.media
+ jersey-media-moxy
+ ${jersey_version}
+ test
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-jxr-plugin
+
+
+
+
+
diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProvider.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProvider.java
new file mode 100644
index 00000000000..bbef3d408a4
--- /dev/null
+++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProvider.java
@@ -0,0 +1,205 @@
+package ca.uhn.fhir.jaxrs.server;
+
+import java.io.IOException;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.annotation.PostConstruct;
+import javax.ws.rs.GET;
+import javax.ws.rs.OPTIONS;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.LoggerFactory;
+
+import ca.uhn.fhir.context.ConfigurationException;
+import ca.uhn.fhir.context.RuntimeResourceDefinition;
+import ca.uhn.fhir.jaxrs.server.util.JaxRsRequest.Builder;
+import ca.uhn.fhir.model.dstu2.resource.Conformance;
+import ca.uhn.fhir.rest.annotation.IdParam;
+import ca.uhn.fhir.rest.api.RequestTypeEnum;
+import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
+import ca.uhn.fhir.rest.method.BaseMethodBinding;
+import ca.uhn.fhir.rest.method.ParseAction;
+import ca.uhn.fhir.rest.server.Constants;
+import ca.uhn.fhir.rest.server.HardcodedServerAddressStrategy;
+import ca.uhn.fhir.rest.server.IResourceProvider;
+import ca.uhn.fhir.rest.server.IRestfulResponse;
+import ca.uhn.fhir.rest.server.ResourceBinding;
+import ca.uhn.fhir.rest.server.RestulfulServerConfiguration;
+import ca.uhn.fhir.rest.server.provider.dstu2.ServerConformanceProvider;
+import ca.uhn.fhir.util.ReflectionUtil;
+
+/**
+ * This is the conformance provider for the jax rs servers. It requires all providers to be registered
+ * during startup because the conformance profile is generated during the postconstruct phase.
+ *
+ * @author Peter Van Houte | peter.vanhoute@agfa.com | Agfa Healthcare
+ */
+@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+public abstract class AbstractJaxRsConformanceProvider extends AbstractJaxRsProvider implements IResourceProvider {
+
+ /** the logger */
+ private static final org.slf4j.Logger ourLog = LoggerFactory.getLogger(AbstractJaxRsConformanceProvider.class);
+ /** the server bindings */
+ private ResourceBinding myServerBinding = new ResourceBinding();
+ /** the resource bindings */
+ private ConcurrentHashMap myResourceNameToBinding = new ConcurrentHashMap();
+ /** the server configuration */
+ private RestulfulServerConfiguration serverConfiguration = new RestulfulServerConfiguration();
+
+ /** the conformance. It is created once during startup */
+ private Conformance myConformance;
+
+ /**
+ * Constructor allowing the description, servername and server to be set
+ * @param implementationDescription the implementation description. If null, "" is used
+ * @param serverName the server name. If null, "" is used
+ * @param serverVersion the server version. If null, "" is used
+ */
+ protected AbstractJaxRsConformanceProvider(String implementationDescription, String serverName, String serverVersion) {
+ serverConfiguration.setFhirContext(getFhirContext());
+ serverConfiguration.setImplementationDescription(StringUtils.defaultIfEmpty(implementationDescription, ""));
+ serverConfiguration.setServerName(StringUtils.defaultIfEmpty(serverName, ""));
+ serverConfiguration.setServerVersion(StringUtils.defaultIfEmpty(serverVersion, ""));
+ }
+
+ /**
+ * This method will set the conformance during the postconstruct phase. The
+ * method {@link AbstractJaxRsConformanceProvider#getProviders()} is used to
+ * get all the resource providers include in the conformance
+ */
+ @PostConstruct
+ protected void setUpPostConstruct() {
+ for (Entry, IResourceProvider> provider : getProviders().entrySet()) {
+ addProvider(provider.getValue(), provider.getKey());
+ }
+ List> serverBindings = new ArrayList>();
+ for (ResourceBinding baseMethodBinding : myResourceNameToBinding.values()) {
+ serverBindings.addAll(baseMethodBinding.getMethodBindings());
+ }
+ serverConfiguration.setServerBindings(serverBindings);
+ serverConfiguration.setResourceBindings(new LinkedList(myResourceNameToBinding.values()));
+ HardcodedServerAddressStrategy hardcodedServerAddressStrategy = new HardcodedServerAddressStrategy();
+ hardcodedServerAddressStrategy.setValue(getBaseForServer());
+ serverConfiguration.setServerAddressStrategy(hardcodedServerAddressStrategy);
+ ServerConformanceProvider serverConformanceProvider = new ServerConformanceProvider(serverConfiguration);
+ serverConformanceProvider.initializeOperations();
+ myConformance = serverConformanceProvider.getServerConformance(null);
+ }
+
+ /**
+ * This method must return all the resource providers which need to be included in the conformance
+ * @return a map of the resource providers and their corresponding classes. This class needs to be given
+ * explicitly because retrieving the interface using {@link Object#getClass()} may not give the correct
+ * interface in a jee environment.
+ */
+ protected abstract ConcurrentHashMap, IResourceProvider> getProviders();
+
+ /**
+ * This method will retrieve the conformance using the http OPTIONS method
+ * @return the response containing the conformance
+ */
+ @OPTIONS
+ @Path("/metadata")
+ public Response conformanceUsingOptions() throws IOException {
+ return conformance();
+ }
+
+ /**
+ * This method will retrieve the conformance using the http GET method
+ * @return the response containing the conformance
+ */
+ @GET
+ @Path("/metadata")
+ public Response conformance() throws IOException {
+ Builder request = getRequest(RequestTypeEnum.OPTIONS, RestOperationTypeEnum.METADATA);
+ IRestfulResponse response = request.build().getResponse();
+ response.addHeader(Constants.HEADER_CORS_ALLOW_ORIGIN, "*");
+ return (Response) response.returnResponse(ParseAction.create(myConformance), Constants.STATUS_HTTP_200_OK, true, null, getResourceType().getSimpleName());
+ }
+
+ /**
+ * This method will add a provider to the conformance. This method is almost an exact copy of {@link ca.uhn.fhir.rest.server.RestfulServer#findResourceMethods }
+ * @param theProvider an instance of the provider interface
+ * @param theProviderInterface the class describing the providers interface
+ * @return the numbers of basemethodbindings added
+ * @see ca.uhn.fhir.rest.server.RestfulServer#findResourceMethods
+ */
+ public int addProvider(IResourceProvider theProvider, Class extends IResourceProvider> theProviderInterface) throws ConfigurationException {
+ int count = 0;
+
+ for (Method m : ReflectionUtil.getDeclaredMethods(theProviderInterface)) {
+ BaseMethodBinding> foundMethodBinding = BaseMethodBinding.bindMethod(m, getFhirContext(), theProvider);
+ if (foundMethodBinding == null) {
+ continue;
+ }
+
+ count++;
+
+// if (foundMethodBinding instanceof ConformanceMethodBinding) {
+// myServerConformanceMethod = foundMethodBinding;
+// continue;
+// }
+
+ if (!Modifier.isPublic(m.getModifiers())) {
+ throw new ConfigurationException("Method '" + m.getName() + "' is not public, FHIR RESTful methods must be public");
+ } else {
+ if (Modifier.isStatic(m.getModifiers())) {
+ throw new ConfigurationException("Method '" + m.getName() + "' is static, FHIR RESTful methods must not be static");
+ } else {
+ ourLog.debug("Scanning public method: {}#{}", theProvider.getClass(), m.getName());
+
+ String resourceName = foundMethodBinding.getResourceName();
+ ResourceBinding resourceBinding;
+ if (resourceName == null) {
+ resourceBinding = myServerBinding;
+ } else {
+ RuntimeResourceDefinition definition = getFhirContext().getResourceDefinition(resourceName);
+ if (myResourceNameToBinding.containsKey(definition.getName())) {
+ resourceBinding = myResourceNameToBinding.get(definition.getName());
+ } else {
+ resourceBinding = new ResourceBinding();
+ resourceBinding.setResourceName(resourceName);
+ myResourceNameToBinding.put(resourceName, resourceBinding);
+ }
+ }
+
+ List> allowableParams = foundMethodBinding.getAllowableParamAnnotations();
+ if (allowableParams != null) {
+ for (Annotation[] nextParamAnnotations : m.getParameterAnnotations()) {
+ for (Annotation annotation : nextParamAnnotations) {
+ Package pack = annotation.annotationType().getPackage();
+ if (pack.equals(IdParam.class.getPackage())) {
+ if (!allowableParams.contains(annotation.annotationType())) {
+ throw new ConfigurationException("Method[" + m.toString() + "] is not allowed to have a parameter annotated with " + annotation);
+ }
+ }
+ }
+ }
+ }
+
+ resourceBinding.addMethod(foundMethodBinding);
+ ourLog.debug(" * Method: {}#{} is a handler", theProvider.getClass(), m.getName());
+ }
+ }
+ }
+
+ return count;
+ }
+
+ @Override
+ public Class getResourceType() {
+ return Conformance.class;
+ }
+
+}
diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsPageProvider.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsPageProvider.java
new file mode 100644
index 00000000000..b37fa58a482
--- /dev/null
+++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsPageProvider.java
@@ -0,0 +1,97 @@
+package ca.uhn.fhir.jaxrs.server;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+
+import javax.interceptor.Interceptors;
+import javax.ws.rs.GET;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import ca.uhn.fhir.jaxrs.server.interceptor.JaxRsExceptionInterceptor;
+import ca.uhn.fhir.jaxrs.server.interceptor.JaxRsResponseException;
+import ca.uhn.fhir.jaxrs.server.util.JaxRsRequest;
+import ca.uhn.fhir.rest.api.RequestTypeEnum;
+import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
+import ca.uhn.fhir.rest.method.PageMethodBinding;
+import ca.uhn.fhir.rest.server.BundleInclusionRule;
+import ca.uhn.fhir.rest.server.Constants;
+import ca.uhn.fhir.rest.server.IPagingProvider;
+import ca.uhn.fhir.rest.server.IRestfulServer;
+import ca.uhn.fhir.rest.server.PageProvider;
+import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
+
+/**
+ * Base class for a provider to provide the [baseUrl]?_getpages=foo
request, which is a request to the
+ * server to retrieve the next page of a set of paged results.
+ */
+@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN })
+@Interceptors(JaxRsExceptionInterceptor.class)
+public abstract class AbstractJaxRsPageProvider extends AbstractJaxRsProvider implements IRestfulServer {
+
+ private PageMethodBinding myBinding;
+
+ /**
+ * The default constructor.
+ */
+ protected AbstractJaxRsPageProvider() {
+ try {
+ myBinding = new PageMethodBinding(getFhirContext(), PageProvider.class.getMethod("getPage"));
+ } catch (Exception e) {
+ throw new ca.uhn.fhir.context.ConfigurationException(e);
+ }
+ }
+
+ @Override
+ public String getBaseForRequest() {
+ try {
+ return getUriInfo().getBaseUri().toURL().toExternalForm();
+ } catch (Exception e) {
+ // cannot happen
+ return null;
+ }
+ }
+
+ /**
+ * This method implements the "getpages" action
+ */
+ @GET
+ public Response getPages(@QueryParam(Constants.PARAM_PAGINGACTION) String thePageId) throws IOException {
+ JaxRsRequest theRequest = getRequest(RequestTypeEnum.GET, RestOperationTypeEnum.GET_PAGE).build();
+ try {
+ return (Response) myBinding.invokeServer(this, theRequest);
+ } catch (JaxRsResponseException theException) {
+ return new JaxRsExceptionInterceptor().convertExceptionIntoResponse(theRequest, theException);
+ }
+ }
+
+ /**
+ * Default: an empty list of interceptors
+ *
+ * @see ca.uhn.fhir.rest.server.IRestfulServer#getInterceptors()
+ */
+ @Override
+ public List getInterceptors() {
+ return Collections.emptyList();
+ }
+
+ /**
+ * Default: no paging provider
+ */
+ @Override
+ public IPagingProvider getPagingProvider() {
+ return null;
+ }
+
+ /**
+ * Default: BundleInclusionRule.BASED_ON_INCLUDES
+ */
+ @Override
+ public BundleInclusionRule getBundleInclusionRule() {
+ return BundleInclusionRule.BASED_ON_INCLUDES;
+ }
+
+}
diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsProvider.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsProvider.java
new file mode 100644
index 00000000000..40613f02104
--- /dev/null
+++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsProvider.java
@@ -0,0 +1,172 @@
+package ca.uhn.fhir.jaxrs.server;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.UriInfo;
+
+import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.jaxrs.server.util.JaxRsRequest;
+import ca.uhn.fhir.jaxrs.server.util.JaxRsRequest.Builder;
+import ca.uhn.fhir.rest.api.RequestTypeEnum;
+import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
+import ca.uhn.fhir.rest.server.AddProfileTagEnum;
+import ca.uhn.fhir.rest.server.ETagSupportEnum;
+import ca.uhn.fhir.rest.server.EncodingEnum;
+import ca.uhn.fhir.rest.server.HardcodedServerAddressStrategy;
+import ca.uhn.fhir.rest.server.IRestfulServerDefaults;
+import ca.uhn.fhir.rest.server.IServerAddressStrategy;
+
+/**
+ * This is the abstract superclass for all jaxrs providers. It contains some defaults implementing
+ * the IRestfulServerDefaults interface and exposes the uri and headers.
+ *
+ * @author Peter Van Houte | peter.vanhoute@agfa.com | Agfa Healthcare
+ */
+public abstract class AbstractJaxRsProvider implements IRestfulServerDefaults {
+
+ /** a static initialization for the fhircontext. Only DSTU2 is supported */
+ private static final FhirContext CTX = FhirContext.forDstu2();
+
+ /** the uri info */
+ @Context
+ private UriInfo theUriInfo;
+ /** the http headers */
+ @Context
+ private HttpHeaders theHeaders;
+
+ @Override
+ public FhirContext getFhirContext() {
+ return CTX;
+ }
+
+ /**
+ * This method returns the query parameters
+ * @return the query parameters
+ */
+ public Map getParameters() {
+ MultivaluedMap queryParameters = getUriInfo().getQueryParameters();
+ HashMap params = new HashMap();
+ for (Entry> paramEntry : queryParameters.entrySet()) {
+ params.put(paramEntry.getKey(), paramEntry.getValue().toArray(new String[paramEntry.getValue().size()]));
+ }
+ return params;
+ }
+
+ /**
+ * This method returns the default server address strategy. The default strategy return the
+ * base uri for the request {@link AbstractJaxRsProvider#getBaseForRequest() getBaseForRequest()}
+ * @return
+ */
+ public IServerAddressStrategy getServerAddressStrategy() {
+ HardcodedServerAddressStrategy addressStrategy = new HardcodedServerAddressStrategy();
+ addressStrategy.setValue(getBaseForRequest());
+ return addressStrategy;
+ }
+
+ /**
+ * This method returns the server base, independent of the request or resource.
+ * @see javax.ws.rs.core.UriInfo#getBaseUri()
+ * @return the ascii string for the server base
+ */
+ public String getBaseForServer() {
+ return getUriInfo().getBaseUri().toASCIIString();
+ }
+
+ /**
+ * This method returns the server base, including the resource path.
+ * {@link javax.ws.rs.core.UriInfo#getBaseUri() UriInfo#getBaseUri()}
+ * @return the ascii string for the base resource provider path
+ */
+ public String getBaseForRequest() {
+ return getBaseForServer();
+ }
+
+ /**
+ * Get the uriInfo
+ * @return the uri info
+ */
+ public UriInfo getUriInfo() {
+ return this.theUriInfo;
+ }
+
+ /**
+ * Set the Uri Info
+ * @param uriInfo the uri info
+ */
+ public void setUriInfo(UriInfo uriInfo) {
+ this.theUriInfo = uriInfo;
+ }
+
+ /**
+ * Get the headers
+ * @return the headers
+ */
+ public HttpHeaders getHeaders() {
+ return this.theHeaders;
+ }
+
+ /**
+ * Set the headers
+ * @param headers the headers to set
+ */
+ public void setHeaders(HttpHeaders headers) {
+ this.theHeaders = headers;
+ }
+
+ /**
+ * Return the requestbuilder for the server
+ * @param requestType the type of the request
+ * @param restOperation the rest operation type
+ * @return the requestbuilder
+ */
+ public Builder getRequest(RequestTypeEnum requestType, RestOperationTypeEnum restOperation) {
+ return new JaxRsRequest.Builder(this, requestType, restOperation, theUriInfo.getRequestUri().toString());
+ }
+
+ /**
+ * DEFAULT = EncodingEnum.JSON
+ */
+ @Override
+ public EncodingEnum getDefaultResponseEncoding() {
+ return EncodingEnum.JSON;
+ }
+
+ /**
+ * DEFAULT = true
+ */
+ @Override
+ public boolean isDefaultPrettyPrint() {
+ return true;
+ }
+
+ /**
+ * DEFAULT = ETagSupportEnum.DISABLED
+ */
+ @Override
+ public ETagSupportEnum getETagSupport() {
+ return ETagSupportEnum.DISABLED;
+ }
+
+ /**
+ * DEFAULT = AddProfileTagEnum.NEVER
+ */
+ @Override
+ public AddProfileTagEnum getAddProfileTag() {
+ return AddProfileTagEnum.NEVER;
+ }
+
+ /**
+ * DEFAULT = false
+ */
+ @Override
+ public boolean isUseBrowserFriendlyContentTypes() {
+ return true;
+ }
+
+}
diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProvider.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProvider.java
new file mode 100644
index 00000000000..0a13cfb697a
--- /dev/null
+++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProvider.java
@@ -0,0 +1,295 @@
+package ca.uhn.fhir.jaxrs.server;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Collections;
+import java.util.List;
+
+import javax.interceptor.Interceptors;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import ca.uhn.fhir.jaxrs.server.interceptor.JaxRsExceptionInterceptor;
+import ca.uhn.fhir.jaxrs.server.interceptor.JaxRsResponseException;
+import ca.uhn.fhir.jaxrs.server.util.JaxRsMethodBindings;
+import ca.uhn.fhir.jaxrs.server.util.JaxRsRequest;
+import ca.uhn.fhir.jaxrs.server.util.JaxRsRequest.Builder;
+import ca.uhn.fhir.model.api.IResource;
+import ca.uhn.fhir.rest.api.RequestTypeEnum;
+import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
+import ca.uhn.fhir.rest.method.BaseMethodBinding;
+import ca.uhn.fhir.rest.server.BundleInclusionRule;
+import ca.uhn.fhir.rest.server.Constants;
+import ca.uhn.fhir.rest.server.IPagingProvider;
+import ca.uhn.fhir.rest.server.IResourceProvider;
+import ca.uhn.fhir.rest.server.IRestfulServer;
+import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
+
+/**
+ * This server is the abstract superclass for all resource providers. It exposes
+ * a large amount of the fhir api functionality using JAXRS
+ * @author Peter Van Houte | peter.vanhoute@agfa.com | Agfa Healthcare
+ */
+@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN })
+@Consumes({ MediaType.APPLICATION_FORM_URLENCODED, MediaType.APPLICATION_JSON, Constants.CT_FHIR_JSON,
+ Constants.CT_FHIR_XML })
+@Interceptors(JaxRsExceptionInterceptor.class)
+public abstract class AbstractJaxRsResourceProvider extends AbstractJaxRsProvider
+ implements IRestfulServer, IResourceProvider {
+
+ /** the method bindings for this class */
+ private final JaxRsMethodBindings theBindings;
+
+ /**
+ * The default constructor. The method bindings are retrieved from the class
+ * being constructed.
+ */
+ protected AbstractJaxRsResourceProvider() {
+ theBindings = JaxRsMethodBindings.getMethodBindings(this, getClass());
+ }
+
+ /**
+ * This constructor takes in an explicit interface class. This subclass
+ * should be identical to the class being constructed but is given
+ * explicitly in order to avoid issues with proxy classes in a jee
+ * environment.
+ *
+ * @param theProviderClass the interface of the class
+ */
+ protected AbstractJaxRsResourceProvider(Class extends AbstractJaxRsProvider> theProviderClass) {
+ theBindings = JaxRsMethodBindings.getMethodBindings(this, theProviderClass);
+ }
+
+ /**
+ * The base for request for a resource provider has the following form:
+ * {@link AbstractJaxRsResourceProvider#getBaseForServer()
+ * getBaseForServer()} + "/" +
+ * {@link AbstractJaxRsResourceProvider#getResourceType() getResourceType()}
+ * .{@link java.lang.Class#getSimpleName() getSimpleName()}
+ */
+ @Override
+ public String getBaseForRequest() {
+ try {
+ return new URL(getUriInfo().getBaseUri().toURL(), getResourceType().getSimpleName()).toExternalForm();
+ } catch (Exception e) {
+ // cannot happen
+ return null;
+ }
+ }
+
+ /**
+ * Create a new resource with a server assigned id
+ *
+ * @param resource the body of the post method containing resource being created in a xml/json form
+ * @return the response
+ * @see https://www.hl7. org/fhir/http.html#create
+ */
+ @POST
+ public Response create(final String resource) throws IOException {
+ return execute(getRequest(RequestTypeEnum.POST, RestOperationTypeEnum.CREATE).resource(resource));
+ }
+
+ /**
+ * Search the resource type based on some filter criteria
+ *
+ * @return the response
+ * @see https://www.hl7.org/fhir/http.html#search
+ */
+ @POST
+ @Path("/_search")
+ public Response searchWithPost() throws IOException {
+ return execute(getRequest(RequestTypeEnum.POST, RestOperationTypeEnum.SEARCH_TYPE));
+ }
+
+ /**
+ * Search the resource type based on some filter criteria
+ *
+ * @return the response
+ * @see https://www.hl7.org/fhir/http.html#search
+ */
+ @GET
+ public Response search() throws IOException {
+ return execute(getRequest(RequestTypeEnum.GET, RestOperationTypeEnum.SEARCH_TYPE));
+ }
+
+ /**
+ * Update an existing resource by its id (or create it if it is new)
+ *
+ * @param id the id of the resource
+ * @param resource the body contents for the put method
+ * @return the response
+ * @see https://www.hl7.org/fhir/http.html#update
+ */
+ @PUT
+ @Path("/{id}")
+ public Response update(@PathParam("id") final String id, final String resource) throws IOException {
+ return execute(getRequest(RequestTypeEnum.PUT, RestOperationTypeEnum.UPDATE).id(id).resource(resource));
+ }
+
+ /**
+ * Delete a resource
+ *
+ * @param id the id of the resource to delete
+ * @return the response
+ * @see https://www.hl7.org/fhir/http.html#delete
+ */
+ @DELETE
+ @Path("/{id}")
+ public Response delete(@PathParam("id") final String id) throws IOException {
+ return execute(getRequest(RequestTypeEnum.DELETE, RestOperationTypeEnum.DELETE).id(id));
+ }
+
+ /**
+ * Read the current state of the resource
+ *
+ * @param id the id of the resource to read
+ * @return the response
+ * @see https://www.hl7.org/fhir/http.html#read
+ */
+ @GET
+ @Path("/{id}")
+ public Response find(@PathParam("id") final String id) throws IOException {
+ return execute(getRequest(RequestTypeEnum.GET, RestOperationTypeEnum.READ).id(id));
+ }
+
+ /**
+ * Execute a custom operation
+ *
+ * @param resource the resource to create
+ * @param requestType the type of request
+ * @param id the id of the resource on which to perform the operation
+ * @param operationName the name of the operation to execute
+ * @param operationType the rest operation type
+ * @return the response
+ * @see https://www.hl7.org/fhir/operations.html
+ */
+ protected Response customOperation(final String resource, RequestTypeEnum requestType, String id,
+ String operationName, RestOperationTypeEnum operationType) throws IOException {
+ Builder request = getRequest(requestType, operationType).resource(resource).id(id);
+ return execute(request, operationName);
+ }
+
+ /**
+ * Retrieve the update history for a particular resource
+ *
+ * @param id the id of the resource
+ * @param version the version of the resource
+ * @return the response
+ * @see https://www.hl7.org/fhir/http.html#history
+ */
+ @GET
+ @Path("/{id}/_history/{version}")
+ public Response findHistory(@PathParam("id") final String id, @PathParam("version") final String version)
+ throws IOException {
+ Builder theRequest = getRequest(RequestTypeEnum.GET, RestOperationTypeEnum.VREAD).id(id)
+ .version(version);
+ return execute(theRequest);
+ }
+
+ /**
+ * Compartment Based Access
+ *
+ * @param id the resource to which the compartment belongs
+ * @param compartment the compartment
+ * @return the repsonse
+ * @see https://www.hl7.org/fhir/http.html#search
+ * @see https://www.hl7.org/fhir/compartments.html#compartment
+ */
+ @GET
+ @Path("/{id}/{compartment}")
+ public Response findCompartment(@PathParam("id") final String id,
+ @PathParam("compartment") final String compartment) throws IOException {
+ Builder theRequest = getRequest(RequestTypeEnum.GET, RestOperationTypeEnum.SEARCH_TYPE).id(id)
+ .compartment(compartment);
+ return execute(theRequest, compartment);
+ }
+
+ /**
+ * Execute the method described by the requestBuilder and methodKey
+ *
+ * @param theRequestBuilder the requestBuilder that contains the information about the request
+ * @param methodKey the key determining the method to be executed
+ * @return the response
+ */
+ private Response execute(Builder theRequestBuilder, String methodKey) throws IOException {
+ JaxRsRequest theRequest = theRequestBuilder.build();
+ BaseMethodBinding> method = getBinding(theRequest.getRestOperationType(), methodKey);
+ try {
+ return (Response) method.invokeServer(this, theRequest);
+ } catch (JaxRsResponseException theException) {
+ return new JaxRsExceptionInterceptor().convertExceptionIntoResponse(theRequest, theException);
+ }
+ }
+
+ /**
+ * Execute the method described by the requestBuilder
+ *
+ * @param theRequestBuilder the requestBuilder that contains the information about the request
+ * @return the response
+ */
+ private Response execute(Builder theRequestBuilder) throws IOException {
+ return execute(theRequestBuilder, JaxRsMethodBindings.DEFAULT_METHOD_KEY);
+ }
+
+ /**
+ * Return the method binding for the given rest operation
+ *
+ * @param restOperation the rest operation to retrieve
+ * @param theBindingKey the key determining the method to be executed (needed for e.g. custom operation)
+ * @return
+ */
+ protected BaseMethodBinding> getBinding(RestOperationTypeEnum restOperation, String theBindingKey) {
+ return getBindings().getBinding(restOperation, theBindingKey);
+ }
+
+ /**
+ * Default: an empty list of interceptors
+ *
+ * @see ca.uhn.fhir.rest.server.IRestfulServer#getInterceptors()
+ */
+ @Override
+ public List getInterceptors() {
+ return Collections.emptyList();
+ }
+
+ /**
+ * Default: no paging provider
+ */
+ @Override
+ public IPagingProvider getPagingProvider() {
+ return null;
+ }
+
+ /**
+ * Default: BundleInclusionRule.BASED_ON_INCLUDES
+ */
+ @Override
+ public BundleInclusionRule getBundleInclusionRule() {
+ return BundleInclusionRule.BASED_ON_INCLUDES;
+ }
+
+ /**
+ * The resource type should return conform to the generic resource included
+ * in the topic
+ */
+ @Override
+ public abstract Class getResourceType();
+
+ /**
+ * Return the bindings defined in this resource provider
+ *
+ * @return the jax-rs method bindings
+ */
+ public JaxRsMethodBindings getBindings() {
+ return theBindings;
+ }
+
+}
diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/interceptor/JaxRsExceptionInterceptor.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/interceptor/JaxRsExceptionInterceptor.java
new file mode 100644
index 00000000000..f4665f8d0b1
--- /dev/null
+++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/interceptor/JaxRsExceptionInterceptor.java
@@ -0,0 +1,91 @@
+package ca.uhn.fhir.jaxrs.server.interceptor;
+
+import java.io.IOException;
+
+import javax.interceptor.AroundInvoke;
+import javax.interceptor.InvocationContext;
+import javax.servlet.ServletException;
+import javax.ws.rs.core.Response;
+
+import ca.uhn.fhir.jaxrs.server.AbstractJaxRsProvider;
+import ca.uhn.fhir.jaxrs.server.util.JaxRsRequest;
+import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
+import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
+import ca.uhn.fhir.rest.server.interceptor.ExceptionHandlingInterceptor;
+
+/**
+ * An interceptor that catches the jax-rs exceptions
+ *
+ * @author Peter Van Houte | peter.vanhoute@agfa.com | Agfa Healthcare
+ */
+public class JaxRsExceptionInterceptor {
+
+ /** the existing exception handler which is able to convert exception into responses*/
+ private ExceptionHandlingInterceptor exceptionHandler;
+
+ /**
+ * The default constructor
+ */
+ public JaxRsExceptionInterceptor() {
+ this.exceptionHandler = new ExceptionHandlingInterceptor();
+ }
+
+ /**
+ * A utility constructor for unit testing
+ * @param exceptionHandler the handler for the exception conversion
+ */
+ JaxRsExceptionInterceptor(ExceptionHandlingInterceptor exceptionHandler) {
+ this.exceptionHandler = exceptionHandler;
+ }
+
+ /**
+ * This interceptor will catch all exception and convert them using the exceptionhandler
+ * @param ctx the invocation context
+ * @return the result
+ * @throws JaxRsResponseException an exception that can be handled by a jee container
+ */
+ @AroundInvoke
+ public Object intercept(final InvocationContext ctx) throws JaxRsResponseException {
+ try {
+ return ctx.proceed();
+ } catch(final Exception theException) {
+ AbstractJaxRsProvider theServer = (AbstractJaxRsProvider) ctx.getTarget();
+ throw convertException(theServer, theException);
+ }
+ }
+
+ private JaxRsResponseException convertException(final AbstractJaxRsProvider theServer, final Exception theException) {
+ JaxRsRequest requestDetails = theServer.getRequest(null, null).build();
+ BaseServerResponseException convertedException = preprocessException(theException, requestDetails);
+ return new JaxRsResponseException(convertedException);
+ }
+
+ /**
+ * This method converts an exception into a response
+ * @param theRequest the request
+ * @param theException the thrown exception
+ * @return the response describing the error
+ * @throws IOException
+ */
+ public Response convertExceptionIntoResponse(JaxRsRequest theRequest, JaxRsResponseException theException)
+ throws IOException {
+ return handleExceptionWithoutServletError(theRequest, theException);
+ }
+
+ private BaseServerResponseException preprocessException(final Exception theException, JaxRsRequest requestDetails) {
+ try {
+ return exceptionHandler.preProcessOutgoingException(requestDetails, theException, null);
+ } catch(ServletException e) {
+ return new InternalErrorException(e);
+ }
+ }
+
+ private Response handleExceptionWithoutServletError(JaxRsRequest theRequest, BaseServerResponseException theException) throws IOException {
+ try {
+ return (Response) exceptionHandler.handleException(theRequest, theException);
+ } catch (ServletException e) {
+ BaseServerResponseException newException = preprocessException(new InternalErrorException(e), theRequest);
+ return handleExceptionWithoutServletError(theRequest, newException);
+ }
+ }
+}
diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/interceptor/JaxRsResponseException.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/interceptor/JaxRsResponseException.java
new file mode 100644
index 00000000000..893fd23c1f1
--- /dev/null
+++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/interceptor/JaxRsResponseException.java
@@ -0,0 +1,26 @@
+package ca.uhn.fhir.jaxrs.server.interceptor;
+
+import javax.ejb.ApplicationException;
+
+import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
+
+/**
+ * A JEE wrapper exception that will not force a rollback.
+ *
+ * @author Peter Van Houte | peter.vanhoute@agfa.com | Agfa Healthcare
+ */
+@ApplicationException(rollback=false)
+public class JaxRsResponseException extends BaseServerResponseException {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Utility constructor
+ *
+ * @param base the base exception
+ */
+ public JaxRsResponseException(BaseServerResponseException base) {
+ super(base.getStatusCode(), base.getMessage(), base.getCause(), base.getOperationOutcome());
+ }
+
+}
diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsMethodBindings.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsMethodBindings.java
new file mode 100644
index 00000000000..1113ddb5fa6
--- /dev/null
+++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsMethodBindings.java
@@ -0,0 +1,134 @@
+package ca.uhn.fhir.jaxrs.server.util;
+
+import java.lang.reflect.Method;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.commons.lang3.StringUtils;
+
+import ca.uhn.fhir.jaxrs.server.AbstractJaxRsProvider;
+import ca.uhn.fhir.rest.annotation.Search;
+import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
+import ca.uhn.fhir.rest.method.BaseMethodBinding;
+import ca.uhn.fhir.rest.method.OperationMethodBinding;
+import ca.uhn.fhir.rest.method.SearchMethodBinding;
+import ca.uhn.fhir.rest.server.exceptions.NotImplementedOperationException;
+import ca.uhn.fhir.util.ReflectionUtil;
+
+/**
+ * Class that contains the method bindings defined by a ResourceProvider
+ *
+ * @author Peter Van Houte | peter.vanhoute@agfa.com | Agfa Healthcare
+ */
+public class JaxRsMethodBindings {
+
+ /** DEFAULT_METHOD_KEY="" */
+ public static final String DEFAULT_METHOD_KEY = "";
+ /** Static collection of bindings mapped to a class*/
+ private static final ConcurrentHashMap, JaxRsMethodBindings> classBindings = new ConcurrentHashMap, JaxRsMethodBindings>();
+ /** Static collection of operationBindings mapped to a class */
+ private ConcurrentHashMap>> operationBindings = new ConcurrentHashMap>>();
+
+ /**
+ * The constructor
+ * @param theProvider the provider which is an implementation of the theProviderClass
+ * @param theProviderClass the class definition contaning the operations
+ */
+ public JaxRsMethodBindings(AbstractJaxRsProvider theProvider, Class extends AbstractJaxRsProvider> theProviderClass) {
+ for (final Method m : ReflectionUtil.getDeclaredMethods(theProviderClass)) {
+ final BaseMethodBinding> foundMethodBinding = BaseMethodBinding.bindMethod(m, theProvider.getFhirContext(), theProvider);
+ if (foundMethodBinding == null) {
+ continue;
+ }
+ String bindingKey = getBindingKey(foundMethodBinding);
+ addMethodBinding(bindingKey, foundMethodBinding);
+ }
+ }
+
+ /**
+ * Get the key for the baseMethodBinding. This is:
+ *
+ * - the compartName for SearchMethodBindings
+ *
- the methodName for OperationMethodBindings
+ *
- {@link #DEFAULT_METHOD_KEY} for all other MethodBindings
+ *
+ * @param theBinding the methodbinding
+ * @return the key for the methodbinding.
+ */
+ private String getBindingKey(final BaseMethodBinding> theBinding) {
+ if (theBinding instanceof OperationMethodBinding) {
+ return ((OperationMethodBinding) theBinding).getName();
+ } else if (theBinding instanceof SearchMethodBinding) {
+ Search search = theBinding.getMethod().getAnnotation(Search.class);
+ return search.compartmentName();
+ } else {
+ return DEFAULT_METHOD_KEY;
+ }
+ }
+
+ private void addMethodBinding(String key, BaseMethodBinding> binding) {
+ ConcurrentHashMap> mapByOperation = getMapForOperation(binding.getRestOperationType());
+ if (mapByOperation.containsKey(key)) {
+ throw new IllegalArgumentException("Multiple Search Method Bindings Found : " + mapByOperation.get(key) + " -- " + binding.getMethod());
+ }
+ mapByOperation.put(key, binding);
+ }
+
+ /**
+ * Get the map for the given operation type. If no map exists for this operation type, create a new hashmap for this
+ * operation type and add it to the operation bindings.
+ *
+ * @param operationType the operation type.
+ * @return the map defined in the operation bindings
+ */
+ private ConcurrentHashMap> getMapForOperation(RestOperationTypeEnum operationType) {
+ ConcurrentHashMap> result = operationBindings.get(operationType);
+ if(result == null) {
+ operationBindings.putIfAbsent(operationType, new ConcurrentHashMap>());
+ return getMapForOperation(operationType);
+ } else {
+ return result;
+ }
+ }
+
+ /**
+ * Get the binding
+ *
+ * @param operationType the type of operation
+ * @param theBindingKey the binding key
+ * @return the binding defined
+ * @throws NotImplementedOperationException cannot be found
+ */
+ public BaseMethodBinding> getBinding(RestOperationTypeEnum operationType, String theBindingKey) {
+ String bindingKey = StringUtils.defaultIfBlank(theBindingKey, DEFAULT_METHOD_KEY);
+ ConcurrentHashMap> map = operationBindings.get(operationType);
+ if(map == null || !map.containsKey(bindingKey)) {
+ throw new NotImplementedOperationException("Operation not implemented");
+ } else {
+ return map.get(bindingKey);
+ }
+ }
+
+ /**
+ * Get the method bindings for the given class. If this class is not yet contained in the classBindings, they will be added for this class
+ *
+ * @param theProvider the implementation class
+ * @param theProviderClass the provider class
+ * @return the methodBindings for this class
+ */
+ public static JaxRsMethodBindings getMethodBindings(AbstractJaxRsProvider theProvider, Class extends AbstractJaxRsProvider> theProviderClass) {
+ if(!getClassBindings().containsKey(theProviderClass)) {
+ JaxRsMethodBindings foundBindings = new JaxRsMethodBindings(theProvider, theProviderClass);
+ getClassBindings().putIfAbsent(theProviderClass, foundBindings);
+ }
+ return getClassBindings().get(theProviderClass);
+ }
+
+ /**
+ * @return the classBindings
+ */
+ static ConcurrentHashMap, JaxRsMethodBindings> getClassBindings() {
+ return classBindings;
+ }
+
+
+}
diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsRequest.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsRequest.java
new file mode 100644
index 00000000000..296a5f2faa7
--- /dev/null
+++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsRequest.java
@@ -0,0 +1,209 @@
+package ca.uhn.fhir.jaxrs.server.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.util.Collections;
+import java.util.List;
+
+import javax.ws.rs.core.HttpHeaders;
+
+import org.apache.commons.lang3.StringUtils;
+
+import ca.uhn.fhir.jaxrs.server.AbstractJaxRsProvider;
+import ca.uhn.fhir.model.primitive.IdDt;
+import ca.uhn.fhir.rest.api.RequestTypeEnum;
+import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
+import ca.uhn.fhir.rest.method.RequestDetails;
+import ca.uhn.fhir.rest.param.ResourceParameter;
+import ca.uhn.fhir.rest.server.Constants;
+import ca.uhn.fhir.rest.server.IRestfulResponse;
+import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
+import ca.uhn.fhir.util.UrlUtil;
+
+/**
+ * The JaxRsRequest is a jax-rs specific implementation of the RequestDetails.
+ *
+ * @author Peter Van Houte | peter.vanhoute@agfa.com | Agfa Healthcare
+ */
+public class JaxRsRequest extends RequestDetails {
+
+ /**
+ * An implementation of the builder pattern for the JaxRsRequest
+ */
+ public static class Builder {
+ private String myResource;
+ private AbstractJaxRsProvider myServer;
+ private RequestTypeEnum myRequestType;
+ private RestOperationTypeEnum myRestOperation;
+ private String myId;
+ private String myVersion;
+ private String myCompartment;
+ private String myRequestUrl;
+
+ /**
+ * Utility Constructor
+ * @param theServer the server
+ * @param theRequestType the request type
+ * @param theRestOperation the rest operation
+ * @param theRequestUrl
+ */
+ public Builder(AbstractJaxRsProvider theServer, RequestTypeEnum theRequestType,
+ RestOperationTypeEnum theRestOperation, String theRequestUrl) {
+ this.myServer = theServer;
+ this.myRequestType = theRequestType;
+ this.myRestOperation = theRestOperation;
+ this.myRequestUrl = theRequestUrl;
+ }
+
+ /**
+ * Set the resource
+ * @param resource the body contents of an http method
+ * @return the builder
+ */
+ public Builder resource(String resource) {
+ this.myResource = resource;
+ return this;
+ }
+
+ /**
+ * Set the id
+ * @param id the resource id
+ * @return the builder
+ */
+ public Builder id(String id) {
+ this.myId = id;
+ return this;
+ }
+
+ /**
+ * Set the id version
+ * @param version the version of the resource
+ * @return the builder
+ */
+ public Builder version(String version) {
+ this.myVersion = version;
+ return this;
+ }
+
+ /**
+ * Set the compartment
+ * @param compartment the compartment
+ * @return the builder
+ */
+ public Builder compartment(String compartment) {
+ this.myCompartment = compartment;
+ return this;
+ }
+
+ /**
+ * Create the jax-rs request
+ * @return the jax-rs request
+ */
+ public JaxRsRequest build() {
+ JaxRsRequest result = new JaxRsRequest(myServer, myResource, myRequestType, myRestOperation);
+ if ((StringUtils.isNotBlank(myVersion) || StringUtils.isNotBlank(myCompartment))
+ && StringUtils.isBlank(myId)) {
+ throw new InvalidRequestException("Don't know how to handle request path: "
+ + myServer.getUriInfo().getRequestUri().toASCIIString());
+ }
+
+ if (StringUtils.isNotBlank(myVersion)) {
+ result.setId(
+ new IdDt(myServer.getBaseForRequest(), UrlUtil.unescape(myId), UrlUtil.unescape(myVersion)));
+ } else if (StringUtils.isNotBlank(myId)) {
+ result.setId(new IdDt(myServer.getBaseForRequest(), UrlUtil.unescape(myId)));
+ }
+
+ if (myRestOperation == RestOperationTypeEnum.UPDATE) {
+ String contentLocation = result.getHeader(Constants.HEADER_CONTENT_LOCATION);
+ if (contentLocation != null) {
+ result.setId(new IdDt(contentLocation));
+ }
+ }
+
+ result.setCompartmentName(myCompartment);
+ result.setCompleteUrl(myRequestUrl);
+
+ return result;
+ }
+ }
+
+ private String theResourceString;
+ private HttpHeaders headers;
+ private AbstractJaxRsProvider myServer;
+
+ /**
+ * Utility Constructor
+ * @param server the server
+ * @param resourceString the resource body
+ * @param requestType the request type
+ * @param restOperation the operation type
+ */
+ public JaxRsRequest(AbstractJaxRsProvider server, String resourceString, RequestTypeEnum requestType,
+ RestOperationTypeEnum restOperation) {
+ this.headers = server.getHeaders();
+ this.theResourceString = resourceString;
+ this.setRestOperationType(restOperation);
+ setServer(server);
+ setFhirServerBase(server.getBaseForServer());
+ setParameters(server.getParameters());
+ setRequestType(requestType);
+ }
+
+ @Override
+ public AbstractJaxRsProvider getServer() {
+ return myServer;
+ }
+
+ /**
+ * Set the server
+ * @param theServer the server to set
+ */
+ public void setServer(AbstractJaxRsProvider theServer) {
+ this.myServer = theServer;
+ }
+
+ @Override
+ public String getHeader(String headerKey) {
+ List requestHeader = getHeaders(headerKey);
+ return requestHeader.isEmpty() ? null : requestHeader.get(0);
+ }
+
+ @Override
+ public List getHeaders(String name) {
+ List requestHeader = headers.getRequestHeader(name);
+ return requestHeader == null ? Collections. emptyList() : requestHeader;
+ }
+
+ @Override
+ public String getServerBaseForRequest() {
+ return getServer().getServerAddressStrategy().determineServerBase(null, null);
+ }
+
+ @Override
+ protected byte[] getByteStreamRequestContents() {
+ return StringUtils.defaultIfEmpty(theResourceString, "")
+ .getBytes(ResourceParameter.determineRequestCharset(this));
+ }
+
+ @Override
+ public IRestfulResponse getResponse() {
+ if (super.getResponse() == null) {
+ setResponse(new JaxRsResponse(this));
+ }
+ return super.getResponse();
+ }
+
+ @Override
+ public Reader getReader() throws IOException {
+ // not yet implemented
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public InputStream getInputStream() {
+ // not yet implemented
+ throw new UnsupportedOperationException();
+ }
+}
\ No newline at end of file
diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsResponse.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsResponse.java
new file mode 100644
index 00000000000..7239427e5ba
--- /dev/null
+++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsResponse.java
@@ -0,0 +1,93 @@
+package ca.uhn.fhir.jaxrs.server.util;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+import java.util.Map.Entry;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.ResponseBuilder;
+
+import org.apache.commons.lang3.StringUtils;
+import org.hl7.fhir.instance.model.api.IBaseBinary;
+
+import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.parser.IParser;
+import ca.uhn.fhir.rest.api.MethodOutcome;
+import ca.uhn.fhir.rest.method.ParseAction;
+import ca.uhn.fhir.rest.server.Constants;
+import ca.uhn.fhir.rest.server.EncodingEnum;
+import ca.uhn.fhir.rest.server.RestfulResponse;
+import ca.uhn.fhir.rest.server.RestfulServerUtils;
+
+/**
+ * The JaxRsResponse is a jax-rs specific implementation of the RestfulResponse.
+ *
+ * @author Peter Van Houte | peter.vanhoute@agfa.com | Agfa Healthcare
+ */
+public class JaxRsResponse extends RestfulResponse {
+
+ /**
+ * The constructor
+ *
+ * @param request the JaxRs Request
+ */
+ public JaxRsResponse(JaxRsRequest request) {
+ super(request);
+ }
+
+ /**
+ * The response writer is a simple String Writer. All output is configured
+ * by the server.
+ */
+ @Override
+ public Writer getResponseWriter(int statusCode, String contentType, String charset, boolean respondGzip)
+ throws UnsupportedEncodingException, IOException {
+ return new StringWriter();
+ }
+
+ @Override
+ public Response sendWriterResponse(int status, String contentType, String charset, Writer writer) {
+ String charContentType = contentType + "; charset="
+ + StringUtils.defaultIfBlank(charset, Constants.CHARSET_NAME_UTF8);
+ return buildResponse(status).header(Constants.HEADER_CONTENT_TYPE, charContentType).entity(writer.toString())
+ .build();
+ }
+
+ @Override
+ public Response sendAttachmentResponse(IBaseBinary bin, int statusCode, String contentType) throws IOException {
+ ResponseBuilder response = buildResponse(statusCode);
+ if (bin.getContent() != null && bin.getContent().length > 0) {
+ response.header(Constants.HEADER_CONTENT_TYPE, contentType).entity(bin.getContent());
+ }
+ return response.build();
+ }
+
+ @Override
+ public Response returnResponse(ParseAction> outcome, int operationStatus, boolean allowPrefer,
+ MethodOutcome response, String resourceName) throws IOException {
+ StringWriter writer = new StringWriter();
+ if (outcome != null) {
+ FhirContext fhirContext = getRequestDetails().getServer().getFhirContext();
+ IParser parser = RestfulServerUtils.getNewParser(fhirContext, getRequestDetails());
+ outcome.execute(parser, writer);
+ }
+ return sendWriterResponse(operationStatus, getParserType(), null, writer);
+ }
+
+ protected String getParserType() {
+ EncodingEnum encodingEnum = RestfulServerUtils.determineResponseEncodingWithDefault(getRequestDetails());
+ return encodingEnum == EncodingEnum.JSON ? MediaType.APPLICATION_JSON : MediaType.APPLICATION_XML;
+ }
+
+ private ResponseBuilder buildResponse(int statusCode) {
+ ResponseBuilder response = Response.status(statusCode);
+ for (Entry header : getHeaders().entrySet()) {
+ response.header(header.getKey(), header.getValue());
+ }
+ return response;
+ }
+
+}
diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProviderTest.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProviderTest.java
new file mode 100644
index 00000000000..95fe581f049
--- /dev/null
+++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProviderTest.java
@@ -0,0 +1,109 @@
+package ca.uhn.fhir.jaxrs.server;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.net.URI;
+import java.util.Arrays;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.ws.rs.HttpMethod;
+import javax.ws.rs.core.MultivaluedHashMap;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+
+import org.glassfish.jersey.internal.MapPropertiesDelegate;
+import org.glassfish.jersey.server.ContainerRequest;
+import org.junit.Before;
+import org.junit.Test;
+
+import ca.uhn.fhir.jaxrs.server.test.TestJaxRsDummyPatientProvider;
+import ca.uhn.fhir.jaxrs.server.test.TestJaxRsMockPatientRestProvider;
+import ca.uhn.fhir.rest.server.Constants;
+import ca.uhn.fhir.rest.server.IResourceProvider;
+
+public class AbstractJaxRsConformanceProviderTest {
+
+ private static final String BASEURI = "http://basiuri";
+ private static final String REQUESTURI = BASEURI + "/metadata";
+ AbstractJaxRsConformanceProvider provider;
+ private ConcurrentHashMap, IResourceProvider> providers;
+ private ContainerRequest headers;
+ private MultivaluedHashMap queryParameters;
+
+ @Before
+ public void setUp() throws Exception {
+ // headers
+ headers = new ContainerRequest(new URI(BASEURI), new URI(REQUESTURI), HttpMethod.GET, null,
+ new MapPropertiesDelegate());
+ // uri info
+ queryParameters = new MultivaluedHashMap();
+
+
+ providers = new ConcurrentHashMap, IResourceProvider>();
+ provider = createConformanceProvider(providers);
+ }
+
+ @Test
+ public void testConformance() throws Exception {
+ providers.put(AbstractJaxRsConformanceProvider.class, provider);
+ providers.put(TestJaxRsDummyPatientProvider.class, new TestJaxRsDummyPatientProvider());
+ Response response = createConformanceProvider(providers).conformance();
+ System.out.println(response);
+ }
+
+ @Test
+ public void testConformanceUsingOptions() throws Exception {
+ providers.put(AbstractJaxRsConformanceProvider.class, provider);
+ providers.put(TestJaxRsDummyPatientProvider.class, new TestJaxRsDummyPatientProvider());
+ Response response = createConformanceProvider(providers).conformanceUsingOptions();
+ System.out.println(response);
+ }
+
+ @Test
+ public void testConformanceWithMethods() throws Exception {
+ providers.put(AbstractJaxRsConformanceProvider.class, provider);
+ providers.put(TestJaxRsMockPatientRestProvider.class, new TestJaxRsMockPatientRestProvider());
+ Response response = createConformanceProvider(providers).conformance();
+ assertEquals(Constants.STATUS_HTTP_200_OK, response.getStatus());
+ assertTrue(response.getEntity().toString().contains("\"type\":\"Patient\""));
+ assertTrue(response.getEntity().toString().contains("\"$someCustomOperation"));
+ System.out.println(response);
+ System.out.println(response.getEntity());
+ }
+
+ @Test
+ public void testConformanceInXml() throws Exception {
+ queryParameters.put(Constants.PARAM_FORMAT, Arrays.asList(Constants.CT_XML));
+ providers.put(AbstractJaxRsConformanceProvider.class, provider);
+ providers.put(TestJaxRsMockPatientRestProvider.class, new TestJaxRsMockPatientRestProvider());
+ Response response = createConformanceProvider(providers).conformance();
+ assertEquals(Constants.STATUS_HTTP_200_OK, response.getStatus());
+ System.out.println(response.getEntity());
+ assertTrue(response.getEntity().toString().contains(" "));
+ assertTrue(response.getEntity().toString().contains("\"$someCustomOperation"));
+ System.out.println(response.getEntity());
+ }
+
+ private AbstractJaxRsConformanceProvider createConformanceProvider(final ConcurrentHashMap, IResourceProvider> providers)
+ throws Exception {
+ AbstractJaxRsConformanceProvider result = new AbstractJaxRsConformanceProvider(null, null, null) {
+ @Override
+ protected ConcurrentHashMap, IResourceProvider> getProviders() {
+ return providers;
+ }
+ };
+ // mocks
+ UriInfo uriInfo = mock(UriInfo.class);
+ when(uriInfo.getQueryParameters()).thenReturn(queryParameters);
+ when(uriInfo.getBaseUri()).thenReturn(new URI(BASEURI));
+ when(uriInfo.getRequestUri()).thenReturn(new URI(BASEURI + "/foo"));
+ result.setUriInfo(uriInfo);
+ result.setHeaders(headers);
+ result.setUpPostConstruct();
+ return result;
+ }
+
+}
diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProviderTest.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProviderTest.java
new file mode 100644
index 00000000000..e39e8f60ec1
--- /dev/null
+++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProviderTest.java
@@ -0,0 +1,437 @@
+package ca.uhn.fhir.jaxrs.server;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isNull;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.commons.lang3.StringUtils;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.FixMethodOrder;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Matchers;
+
+import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.jaxrs.server.interceptor.JaxRsResponseException;
+import ca.uhn.fhir.jaxrs.server.test.RandomServerPortProvider;
+import ca.uhn.fhir.jaxrs.server.test.TestJaxRsConformanceRestProvider;
+import ca.uhn.fhir.jaxrs.server.test.TestJaxRsMockPageProvider;
+import ca.uhn.fhir.jaxrs.server.test.TestJaxRsMockPatientRestProvider;
+import ca.uhn.fhir.jaxrs.server.interceptor.JaxRsExceptionInterceptor;
+import ca.uhn.fhir.model.api.BundleEntry;
+import ca.uhn.fhir.model.api.IResource;
+import ca.uhn.fhir.model.base.resource.BaseOperationOutcome;
+import ca.uhn.fhir.model.dstu2.resource.Bundle;
+import ca.uhn.fhir.model.dstu2.resource.Bundle.Entry;
+import ca.uhn.fhir.model.dstu2.resource.Conformance;
+import ca.uhn.fhir.model.dstu2.resource.Parameters;
+import ca.uhn.fhir.model.dstu2.resource.Patient;
+import ca.uhn.fhir.model.primitive.BoundCodeDt;
+import ca.uhn.fhir.model.primitive.DateDt;
+import ca.uhn.fhir.model.primitive.IdDt;
+import ca.uhn.fhir.model.primitive.StringDt;
+import ca.uhn.fhir.model.primitive.UriDt;
+import ca.uhn.fhir.model.valueset.BundleEntryTransactionMethodEnum;
+import ca.uhn.fhir.rest.api.MethodOutcome;
+import ca.uhn.fhir.rest.api.PreferReturnEnum;
+import ca.uhn.fhir.rest.client.IGenericClient;
+import ca.uhn.fhir.rest.client.ServerValidationModeEnum;
+import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
+import ca.uhn.fhir.rest.method.SearchStyleEnum;
+import ca.uhn.fhir.rest.param.StringAndListParam;
+import ca.uhn.fhir.rest.param.StringParam;
+import ca.uhn.fhir.rest.server.EncodingEnum;
+import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
+
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class AbstractJaxRsResourceProviderTest {
+
+ private TestJaxRsMockPatientRestProvider mock;
+ private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(AbstractJaxRsResourceProviderTest.class);
+
+
+ private ArgumentCaptor idCaptor;
+ private ArgumentCaptor patientCaptor;
+
+ private static IGenericClient client;
+ private static final FhirContext ourCtx = FhirContext.forDstu2();
+ private static final String PATIENT_NAME = "Van Houte";
+ private static int ourPort;
+ private static String serverBase;
+ private static Server jettyServer;
+
+ @BeforeClass
+ public static void setUpClass() throws Exception {
+ ourPort = RandomServerPortProvider.findFreePort();
+ ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
+ context.setContextPath("/");
+ System.out.println(ourPort);
+ jettyServer = new Server(ourPort);
+ jettyServer.setHandler(context);
+ ServletHolder jerseyServlet = context.addServlet(org.glassfish.jersey.servlet.ServletContainer.class, "/*");
+ jerseyServlet.setInitOrder(0);
+
+ //@formatter:off
+ jerseyServlet.setInitParameter("jersey.config.server.provider.classnames",
+ StringUtils.join(Arrays.asList(
+ TestJaxRsMockPatientRestProvider.class.getCanonicalName(),
+ JaxRsExceptionInterceptor.class.getCanonicalName(),
+ TestJaxRsConformanceRestProvider.class.getCanonicalName(),
+ TestJaxRsMockPageProvider.class.getCanonicalName()
+ ), ";"));
+ //@formatter:on
+
+ jettyServer.start();
+
+ ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER);
+ ourCtx.getRestfulClientFactory().setSocketTimeout(1200 * 1000);
+ serverBase = "http://localhost:" + ourPort + "/";
+ client = ourCtx.newRestfulGenericClient(serverBase);
+ client.setEncoding(EncodingEnum.JSON);
+ client.registerInterceptor(new LoggingInterceptor(true));
+ }
+
+ @AfterClass
+ public static void tearDownClass() throws Exception {
+ try {
+ jettyServer.destroy();
+ } catch (Exception e) {
+
+ }
+ }
+
+ @Before
+ public void setUp() {
+ this.mock = TestJaxRsMockPatientRestProvider.mock;
+ idCaptor = ArgumentCaptor.forClass(IdDt.class);
+ patientCaptor = ArgumentCaptor.forClass(Patient.class);
+ reset(mock);
+ }
+
+ /** Search/Query - Type */
+ @Test
+ public void testSearchUsingGenericClientBySearch() {
+ // Perform a search
+ when(mock.search(any(StringParam.class), Matchers.isNull(StringAndListParam.class)))
+ .thenReturn(Arrays.asList(createPatient(1)));
+ final ca.uhn.fhir.model.api.Bundle results = client.search().forResource(Patient.class)
+ .where(Patient.NAME.matchesExactly().value(PATIENT_NAME)).execute();
+ verify(mock).search(any(StringParam.class), Matchers.isNull(StringAndListParam.class));
+ IResource resource = results.getEntries().get(0).getResource();
+
+ compareResultId(1, resource);
+ compareResultUrl("/Patient/1", resource);
+ }
+
+ /** Search - Multi-valued Parameters (ANY/OR) */
+ @Test
+ public void testSearchUsingGenericClientBySearchWithMultiValues() {
+ when(mock.search(any(StringParam.class), Matchers.isNotNull(StringAndListParam.class)))
+ .thenReturn(Arrays.asList(createPatient(1)));
+ final ca.uhn.fhir.model.api.Bundle results = client.search().forResource(Patient.class)
+ .where(Patient.ADDRESS.matches().values("Toronto")).and(Patient.ADDRESS.matches().values("Ontario"))
+ .and(Patient.ADDRESS.matches().values("Canada"))
+ .where(Patient.IDENTIFIER.exactly().systemAndIdentifier("SHORTNAME", "TOYS")).execute();
+ IResource resource = results.getEntries().get(0).getResource();
+
+ compareResultId(1, resource);
+ compareResultUrl("/Patient/1", resource);
+ }
+
+ /** Search - Paging */
+ @Test
+ public void testSearchWithPaging() {
+ // Perform a search
+ when(mock.search(any(StringParam.class), Matchers.isNull(StringAndListParam.class)))
+ .thenReturn(createPatients(1, 13));
+ final Bundle results = client.search().forResource(Patient.class).limitTo(8).returnBundle(Bundle.class)
+ .execute();
+
+ assertEquals(results.getEntry().size(), 8);
+ IResource resource = results.getEntry().get(0).getResource();
+ compareResultId(1, resource);
+ compareResultUrl("/Patient/1", resource);
+ compareResultId(8, results.getEntry().get(7).getResource());
+
+// ourLog.info("Next: " + results.getLink("next").getUrl());
+// String url = results.getLink("next").getUrl().replace("?", "Patient?");
+// results.getLink("next").setUrl(url);
+// ourLog.info("New Next: " + results.getLink("next").getUrl());
+
+ // load next page
+ final Bundle nextPage = client.loadPage().next(results).execute();
+ resource = nextPage.getEntry().get(0).getResource();
+ compareResultId(9, resource);
+ compareResultUrl("/Patient/9", resource);
+ assertNull(nextPage.getLink(Bundle.LINK_NEXT));
+ }
+
+ /** Search using other query options */
+ public void testOther() {
+ // missing
+ }
+
+ /** */
+ @Test
+ public void testSearchPost() {
+ when(mock.search(any(StringParam.class), Matchers.isNull(StringAndListParam.class)))
+ .thenReturn(createPatients(1, 13));
+ Bundle result = client.search().forResource("Patient").usingStyle(SearchStyleEnum.POST)
+ .returnBundle(Bundle.class).execute();
+ IResource resource = result.getEntry().get(0).getResource();
+ compareResultId(1, resource);
+ compareResultUrl("/Patient/1", resource);
+ }
+
+ /** Search - Compartments */
+ @Test
+ public void testSearchCompartements() {
+ when(mock.searchCompartment(any(IdDt.class))).thenReturn(Arrays.asList((IResource) createPatient(1)));
+ Bundle response = client.search().forResource(Patient.class).withIdAndCompartment("1", "Condition")
+ .returnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class).execute();
+ IResource resource = response.getEntry().get(0).getResource();
+ compareResultId(1, resource);
+ compareResultUrl("/Patient/1", resource);
+ }
+
+ /** Search - Subsetting (_summary and _elements) */
+ @Test
+ @Ignore
+ public void testSummary() {
+ Object response = client.search().forResource(Patient.class)
+ .returnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class).execute();
+ }
+
+ @Test
+ public void testCreatePatient() throws Exception {
+ Patient toCreate = createPatient(1);
+ MethodOutcome outcome = new MethodOutcome();
+ toCreate.getIdentifierFirstRep().setValue("myIdentifier");
+ outcome.setResource(toCreate);
+
+ when(mock.create(patientCaptor.capture(), isNull(String.class))).thenReturn(outcome);
+ client.setEncoding(EncodingEnum.JSON);
+ final MethodOutcome response = client.create().resource(toCreate).prefer(PreferReturnEnum.REPRESENTATION)
+ .execute();
+ IResource resource = (IResource) response.getResource();
+ compareResultId(1, resource);
+ assertEquals("myIdentifier", patientCaptor.getValue().getIdentifierFirstRep().getValue());
+ }
+
+ /** Conditional Creates */
+ @Test
+ public void testConditionalCreate() throws Exception {
+ Patient toCreate = createPatient(1);
+ MethodOutcome outcome = new MethodOutcome();
+ toCreate.getIdentifierFirstRep().setValue("myIdentifier");
+ outcome.setResource(toCreate);
+
+ when(mock.create(patientCaptor.capture(), eq("Patient?_format=json&identifier=2"))).thenReturn(outcome);
+ client.setEncoding(EncodingEnum.JSON);
+
+ MethodOutcome response = client.create().resource(toCreate).conditional()
+ .where(Patient.IDENTIFIER.exactly().identifier("2")).prefer(PreferReturnEnum.REPRESENTATION).execute();
+
+ assertEquals("myIdentifier", patientCaptor.getValue().getIdentifierFirstRep().getValue());
+ IResource resource = (IResource) response.getResource();
+ compareResultId(1, resource);
+ }
+
+ /** Find By Id */
+ @Test
+ public void findUsingGenericClientById() {
+ when(mock.find(any(IdDt.class))).thenReturn(createPatient(1));
+ Patient result = client.read(Patient.class, "1");
+ compareResultId(1, result);
+ compareResultUrl("/Patient/1", result);
+ reset(mock);
+ when(mock.find(withId(result.getId()))).thenReturn(createPatient(1));
+ result = (Patient) client.read(new UriDt(result.getId().getValue()));
+ compareResultId(1, result);
+ compareResultUrl("/Patient/1", result);
+ }
+
+ @Test
+ public void testUpdateById() throws Exception {
+ when(mock.update(idCaptor.capture(), patientCaptor.capture())).thenReturn(new MethodOutcome());
+ client.update("1", createPatient(2));
+ assertEquals("1", idCaptor.getValue().getIdPart());
+ compareResultId(1, patientCaptor.getValue());
+ }
+
+ @Test
+ public void testDeletePatient() {
+ when(mock.delete(idCaptor.capture())).thenReturn(new MethodOutcome());
+ final BaseOperationOutcome results = client.delete().resourceById("Patient", "1").execute();
+ assertEquals("1", idCaptor.getValue().getIdPart());
+ }
+
+ /** Transaction - Server */
+ @Ignore
+ @Test
+ public void testTransaction() {
+ ca.uhn.fhir.model.api.Bundle bundle = new ca.uhn.fhir.model.api.Bundle();
+ BundleEntry entry = bundle.addEntry();
+ final Patient existing = new Patient();
+ existing.getNameFirstRep().addFamily("Created with bundle");
+ entry.setResource(existing);
+
+ BoundCodeDt theTransactionOperation = new BoundCodeDt(
+ BundleEntryTransactionMethodEnum.VALUESET_BINDER, BundleEntryTransactionMethodEnum.POST);
+ entry.setTransactionMethod(theTransactionOperation);
+ ca.uhn.fhir.model.api.Bundle response = client.transaction().withBundle(bundle).execute();
+ }
+
+ /** Conformance - Server */
+ @Test
+ public void testConformance() {
+ final Conformance conf = client.fetchConformance().ofType(Conformance.class).execute();
+ assertEquals(conf.getRest().get(0).getResource().get(0).getType().toString(), "Patient");
+ }
+
+ /** Extended Operations */
+ @Test
+ public void testExtendedOperations() {
+ // prepare mock
+ Parameters resultParameters = new Parameters();
+ resultParameters.addParameter().setName("return").setResource(createPatient(1)).setValue(new StringDt("outputValue"));
+ when(mock.someCustomOperation(any(IdDt.class), eq(new StringDt("myAwesomeDummyValue")))).thenReturn(resultParameters);
+ // Create the input parameters to pass to the server
+ Parameters inParams = new Parameters();
+ inParams.addParameter().setName("start").setValue(new DateDt("2001-01-01"));
+ inParams.addParameter().setName("end").setValue(new DateDt("2015-03-01"));
+ inParams.addParameter().setName("dummy").setValue(new StringDt("myAwesomeDummyValue"));
+ //invoke
+ Parameters outParams = client.operation().onInstance(new IdDt("Patient", "1")).named("$someCustomOperation")
+ .withParameters(inParams).execute();
+ //verify
+ assertEquals("outputValue", ((StringDt)outParams.getParameter().get(0).getValue()).getValueAsString());
+ }
+
+ @Test
+ public void testExtendedOperationsUsingGet() {
+ // prepare mock
+ Parameters resultParameters = new Parameters();
+ resultParameters.addParameter().setName("return").setResource(createPatient(1)).setValue(new StringDt("outputValue"));
+ when(mock.someCustomOperation(any(IdDt.class), eq(new StringDt("myAwesomeDummyValue")))).thenReturn(resultParameters);
+ // Create the input parameters to pass to the server
+ Parameters inParams = new Parameters();
+ inParams.addParameter().setName("start").setValue(new DateDt("2001-01-01"));
+ inParams.addParameter().setName("end").setValue(new DateDt("2015-03-01"));
+ inParams.addParameter().setName("dummy").setValue(new StringDt("myAwesomeDummyValue"));
+
+ // invoke
+ Parameters outParams = client.operation().onInstance(new IdDt("Patient", "1")).named("$someCustomOperation")
+ .withParameters(inParams).useHttpGet().execute();
+ // verify
+ assertEquals("outputValue", ((StringDt)outParams.getParameter().get(0).getValue()).getValueAsString());
+ }
+
+ @Test
+ public void testVRead() {
+ when(mock.findHistory(idCaptor.capture())).thenReturn(createPatient(1));
+ final Patient patient = client.vread(Patient.class, "1", "2");
+ compareResultId(1, patient);
+ compareResultUrl("/Patient/1", patient);
+ assertEquals("1", idCaptor.getValue().getIdPart());
+ assertEquals("2", idCaptor.getValue().getVersionIdPart());
+ }
+
+ @Test
+ public void testRead() {
+ when(mock.find(idCaptor.capture())).thenReturn(createPatient(1));
+ final Patient patient = client.read(Patient.class, "1");
+ compareResultId(1, patient);
+ compareResultUrl("/Patient/1", patient);
+ assertEquals("1", idCaptor.getValue().getIdPart());
+ }
+
+ @Test
+ public void testXFindUnknownPatient() {
+ try {
+ JaxRsResponseException notFoundException = new JaxRsResponseException(new ResourceNotFoundException(new IdDt("999955541264")));
+ when(mock.find(idCaptor.capture())).thenThrow(notFoundException);
+ client.read(Patient.class, "999955541264");
+ fail();
+ } catch (final ResourceNotFoundException e) {
+ assertEquals(ResourceNotFoundException.STATUS_CODE, e.getStatusCode());
+ assertTrue(e.getMessage().contains("999955541264"));
+ }
+ }
+
+ private Bundle getPatientBundle(int size) {
+ Bundle result = new Bundle();
+ for (long i = 0; i < size; i++) {
+ Patient patient = createPatient(i);
+ Entry entry = new Entry().setResource(patient);
+ result.addEntry(entry);
+ }
+ return result;
+ }
+
+ private List createPatients(int firstId, int lastId) {
+ List result = new ArrayList(lastId - firstId);
+ for (long i = firstId; i <= lastId; i++) {
+ result.add(createPatient(i));
+ }
+ return result;
+ }
+
+ private Patient createPatient(long id) {
+ Patient theResource = new Patient();
+ theResource.setId(new IdDt(id));
+ return theResource;
+ }
+
+ private void compareResultId(int id, IResource resource) {
+ assertEquals(id, resource.getId().getIdPartAsLong().intValue());
+ }
+
+ private void compareResultUrl(String url, IResource resource) {
+ assertEquals(url, resource.getId().getValueAsString().substring(serverBase.length() - 1));
+ }
+
+ private T withId(final T id) {
+ return argThat(new BaseMatcher() {
+ @Override
+ public boolean matches(Object other) {
+ IdDt thisId;
+ IdDt otherId;
+ if (id instanceof IdDt) {
+ thisId = (IdDt) id;
+ otherId = (IdDt) other;
+ } else {
+ thisId = ((IResource) id).getId();
+ otherId = ((IResource) other).getId();
+ }
+ return thisId.getIdPartAsLong().equals(otherId.getIdPartAsLong());
+ }
+
+ @Override
+ public void describeTo(Description arg0) {
+ }
+ });
+ }
+
+}
diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/interceptor/JaxRsExceptionInterceptorTest.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/interceptor/JaxRsExceptionInterceptorTest.java
new file mode 100644
index 00000000000..2064e29e238
--- /dev/null
+++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/interceptor/JaxRsExceptionInterceptorTest.java
@@ -0,0 +1,110 @@
+package ca.uhn.fhir.jaxrs.server.interceptor;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.isNull;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.HashMap;
+
+import javax.interceptor.InvocationContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import ca.uhn.fhir.jaxrs.server.AbstractJaxRsProvider;
+import ca.uhn.fhir.jaxrs.server.test.TestJaxRsDummyPatientProvider;
+import ca.uhn.fhir.jaxrs.server.util.JaxRsRequest;
+import ca.uhn.fhir.rest.method.RequestDetails;
+import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
+import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
+import ca.uhn.fhir.rest.server.exceptions.NotImplementedOperationException;
+import ca.uhn.fhir.rest.server.interceptor.ExceptionHandlingInterceptor;
+
+public class JaxRsExceptionInterceptorTest {
+
+ JaxRsExceptionInterceptor interceptor = new JaxRsExceptionInterceptor();
+ private InvocationContext context;
+
+ @Before
+ public void setUp() throws Exception {
+ interceptor = new JaxRsExceptionInterceptor();
+ context = mock(InvocationContext.class);
+ TestJaxRsDummyPatientProvider provider = spy(TestJaxRsDummyPatientProvider.class);
+ when(context.getTarget()).thenReturn(provider);
+ doReturn("http://baseUri").when(provider).getBaseForServer();
+ doReturn(new HashMap()).when(provider).getParameters();
+ doReturn(mock(HttpHeaders.class)).when(provider).getHeaders();
+
+ UriInfo uriInfo = mock(UriInfo.class);
+ when(uriInfo.getRequestUri()).thenReturn(new URI("http://base/foo"));
+ provider.setUriInfo(uriInfo);
+
+ }
+
+ @Test
+ public void testInterceptWithBaseServerError() throws Throwable {
+ NotImplementedOperationException thrownException = new NotImplementedOperationException("not implemented");
+ when(context.proceed()).thenThrow(thrownException);
+ try {
+ interceptor.intercept(context);
+ fail();
+ } catch (BaseServerResponseException e) {
+ assertEquals(e.getMessage(), thrownException.getMessage());
+ }
+ }
+
+ @Test
+ public void testIntercepWithServletError() throws Throwable {
+ ExceptionHandlingInterceptor exceptionHandler = mock(ExceptionHandlingInterceptor.class);
+ when(exceptionHandler.preProcessOutgoingException(any(RequestDetails.class), any(Throwable.class),
+ isNull(HttpServletRequest.class))).thenThrow(new ServletException("someMessage"));
+ interceptor = new JaxRsExceptionInterceptor(exceptionHandler);
+ when(context.proceed()).thenThrow(new ServletException());
+ try {
+ interceptor.intercept(context);
+ fail();
+ } catch (BaseServerResponseException e) {
+ assertTrue(e.getMessage().contains("someMessage"));
+ }
+ }
+
+ @Test
+ public void testInterceptServletWithoutError() throws Throwable {
+ Object expected = new Object();
+ when(context.proceed()).thenReturn(expected);
+ Object result = interceptor.intercept(context);
+ assertSame(expected, result);
+ }
+
+ @Test
+ public void testHandleExceptionWithServletError() throws Throwable {
+ JaxRsRequest request = ((AbstractJaxRsProvider) context.getTarget()).getRequest(null, null).build();
+
+ ExceptionHandlingInterceptor exceptionHandler = spy(ExceptionHandlingInterceptor.class);
+
+ interceptor = new JaxRsExceptionInterceptor(exceptionHandler);
+
+ when(context.proceed()).thenThrow(new ServletException());
+
+ JaxRsResponseException thrownException = new JaxRsResponseException(new NotImplementedOperationException("not implemented"));
+ doThrow(new javax.servlet.ServletException("someMessage")).when(exceptionHandler).handleException(request, thrownException);
+ Response result = interceptor.convertExceptionIntoResponse(request, thrownException);
+ assertEquals(InternalErrorException.STATUS_CODE, result.getStatus());
+ }
+
+}
diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/interceptor/JaxRsResponseExceptionTest.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/interceptor/JaxRsResponseExceptionTest.java
new file mode 100644
index 00000000000..52dacb73e74
--- /dev/null
+++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/interceptor/JaxRsResponseExceptionTest.java
@@ -0,0 +1,23 @@
+package ca.uhn.fhir.jaxrs.server.interceptor;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import javax.ejb.ApplicationException;
+
+import org.junit.Test;
+
+import ca.uhn.fhir.rest.server.exceptions.ForbiddenOperationException;
+
+public class JaxRsResponseExceptionTest {
+
+ @Test
+ public void testException() {
+ ForbiddenOperationException wrappedException = new ForbiddenOperationException("someMessage");
+ JaxRsResponseException response = new JaxRsResponseException(wrappedException);
+ assertEquals(response.getMessage(), wrappedException.getMessage());
+ assertEquals(response.getStatusCode(), wrappedException.getStatusCode());
+ assertNotNull(response.getClass().getAnnotation(ApplicationException.class));
+ }
+
+}
diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/RandomServerPortProvider.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/RandomServerPortProvider.java
new file mode 100644
index 00000000000..e9e137a6b3e
--- /dev/null
+++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/RandomServerPortProvider.java
@@ -0,0 +1,36 @@
+package ca.uhn.fhir.jaxrs.server.test;
+
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Provides server ports
+ */
+public class RandomServerPortProvider {
+
+ private static List ourPorts = new ArrayList();
+
+ public static int findFreePort() {
+ ServerSocket server;
+ try {
+ server = new ServerSocket(0);
+ int port = server.getLocalPort();
+ ourPorts.add(port);
+ server.close();
+ Thread.sleep(500);
+ return port;
+ } catch (IOException e) {
+ throw new Error(e);
+ } catch (InterruptedException e) {
+ throw new Error(e);
+ }
+ }
+
+ public static List list() {
+ return ourPorts;
+ }
+
+}
+
\ No newline at end of file
diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsConformanceRestProvider.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsConformanceRestProvider.java
new file mode 100644
index 00000000000..ccbf8182461
--- /dev/null
+++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsConformanceRestProvider.java
@@ -0,0 +1,33 @@
+package ca.uhn.fhir.jaxrs.server.test;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.ejb.Stateless;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+import ca.uhn.fhir.jaxrs.server.AbstractJaxRsConformanceProvider;
+import ca.uhn.fhir.rest.server.Constants;
+import ca.uhn.fhir.rest.server.IResourceProvider;
+
+/**
+ * A conformance provider exposes the mock patient and this provider
+ */
+@Path("")
+@Stateless
+@Produces({ MediaType.APPLICATION_JSON, Constants.CT_FHIR_JSON, Constants.CT_FHIR_XML })
+public class TestJaxRsConformanceRestProvider extends AbstractJaxRsConformanceProvider {
+
+ public TestJaxRsConformanceRestProvider() {
+ super("description", "name", "version");
+ }
+
+ @Override
+ protected ConcurrentHashMap, IResourceProvider> getProviders() {
+ ConcurrentHashMap, IResourceProvider> map = new ConcurrentHashMap, IResourceProvider>();
+ map.put(TestJaxRsMockPatientRestProvider.class, new TestJaxRsMockPatientRestProvider());
+ map.put(TestJaxRsConformanceRestProvider.class, new TestJaxRsConformanceRestProvider());
+ return map;
+ }
+}
diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsDummyPatientProvider.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsDummyPatientProvider.java
new file mode 100644
index 00000000000..70907adf73c
--- /dev/null
+++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsDummyPatientProvider.java
@@ -0,0 +1,15 @@
+package ca.uhn.fhir.jaxrs.server.test;
+
+import ca.uhn.fhir.jaxrs.server.AbstractJaxRsResourceProvider;
+import ca.uhn.fhir.model.dstu2.resource.Patient;
+
+/**
+ * A dummy patient provider exposing no methods
+ */
+public class TestJaxRsDummyPatientProvider extends AbstractJaxRsResourceProvider {
+
+ @Override
+ public Class getResourceType() {
+ return Patient.class;
+ }
+}
diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsMockPageProvider.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsMockPageProvider.java
new file mode 100644
index 00000000000..26c83ed59c0
--- /dev/null
+++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsMockPageProvider.java
@@ -0,0 +1,22 @@
+package ca.uhn.fhir.jaxrs.server.test;
+
+import javax.ejb.Stateless;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+import ca.uhn.fhir.jaxrs.server.AbstractJaxRsPageProvider;
+import ca.uhn.fhir.rest.server.Constants;
+import ca.uhn.fhir.rest.server.IPagingProvider;
+
+@Path("/")
+@Stateless
+@Produces({ MediaType.APPLICATION_JSON, Constants.CT_FHIR_JSON, Constants.CT_FHIR_XML })
+public class TestJaxRsMockPageProvider extends AbstractJaxRsPageProvider {
+
+ @Override
+ public IPagingProvider getPagingProvider() {
+ return TestJaxRsMockPatientRestProvider.PAGING_PROVIDER;
+ }
+
+}
diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsMockPatientRestProvider.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsMockPatientRestProvider.java
new file mode 100644
index 00000000000..764240f5e37
--- /dev/null
+++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsMockPatientRestProvider.java
@@ -0,0 +1,136 @@
+package ca.uhn.fhir.jaxrs.server.test;
+
+import java.util.List;
+
+import javax.ejb.Stateless;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.mockito.Mockito;
+
+import ca.uhn.fhir.jaxrs.server.AbstractJaxRsResourceProvider;
+import ca.uhn.fhir.model.api.IResource;
+import ca.uhn.fhir.model.dstu2.resource.Parameters;
+import ca.uhn.fhir.model.dstu2.resource.Patient;
+import ca.uhn.fhir.model.primitive.IdDt;
+import ca.uhn.fhir.model.primitive.StringDt;
+import ca.uhn.fhir.rest.annotation.ConditionalUrlParam;
+import ca.uhn.fhir.rest.annotation.Create;
+import ca.uhn.fhir.rest.annotation.Delete;
+import ca.uhn.fhir.rest.annotation.IdParam;
+import ca.uhn.fhir.rest.annotation.Operation;
+import ca.uhn.fhir.rest.annotation.OperationParam;
+import ca.uhn.fhir.rest.annotation.Read;
+import ca.uhn.fhir.rest.annotation.RequiredParam;
+import ca.uhn.fhir.rest.annotation.ResourceParam;
+import ca.uhn.fhir.rest.annotation.Search;
+import ca.uhn.fhir.rest.annotation.Update;
+import ca.uhn.fhir.rest.api.MethodOutcome;
+import ca.uhn.fhir.rest.api.RequestTypeEnum;
+import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
+import ca.uhn.fhir.rest.param.StringAndListParam;
+import ca.uhn.fhir.rest.param.StringParam;
+import ca.uhn.fhir.rest.server.Constants;
+import ca.uhn.fhir.rest.server.FifoMemoryPagingProvider;
+import ca.uhn.fhir.rest.server.IPagingProvider;
+
+/**
+ * A test server delegating each call to a mock
+ */
+@Path(TestJaxRsMockPatientRestProvider.PATH)
+@Stateless
+@Produces({ MediaType.APPLICATION_JSON, Constants.CT_FHIR_JSON, Constants.CT_FHIR_XML })
+public class TestJaxRsMockPatientRestProvider extends AbstractJaxRsResourceProvider {
+
+ static final String PATH = "/Patient";
+
+ public static final TestJaxRsMockPatientRestProvider mock = Mockito.mock(TestJaxRsMockPatientRestProvider.class);
+
+ public static final FifoMemoryPagingProvider PAGING_PROVIDER;
+
+ static
+ {
+ PAGING_PROVIDER = new FifoMemoryPagingProvider(10);
+ PAGING_PROVIDER.setDefaultPageSize(10);
+ PAGING_PROVIDER.setMaximumPageSize(100);
+ }
+
+ /**
+ * Constructor
+ */
+ public TestJaxRsMockPatientRestProvider() {
+ super();
+ }
+
+ @Search
+ public List search(@RequiredParam(name = Patient.SP_NAME) final StringParam name, @RequiredParam(name=Patient.SP_ADDRESS) StringAndListParam theAddressParts) {
+ return mock.search(name, theAddressParts);
+ }
+
+ @Update
+ public MethodOutcome update(@IdParam final IdDt theId, @ResourceParam final Patient patient) throws Exception {
+ return mock.update(theId, patient);
+ }
+
+ @Read
+ public Patient find(@IdParam final IdDt theId) {
+ return mock.find(theId);
+ }
+
+ @Read(version = true)
+ public Patient findHistory(@IdParam final IdDt theId) {
+ return mock.findHistory(theId);
+ }
+
+ @Create
+ public MethodOutcome create(@ResourceParam final Patient patient, @ConditionalUrlParam String theConditional)
+ throws Exception {
+ return mock.create(patient, theConditional);
+ }
+
+ @Delete
+ public MethodOutcome delete(@IdParam final IdDt theId) {
+ return mock.delete(theId);
+ }
+
+ @Search(compartmentName = "Condition")
+ public List searchCompartment(@IdParam IdDt thePatientId) {
+ return mock.searchCompartment(thePatientId);
+ }
+
+ @GET
+ @Path("/{id}/$someCustomOperation")
+ public Response someCustomOperationUsingGet(@PathParam("id") String id, String resource) throws Exception {
+ return customOperation(resource, RequestTypeEnum.GET, id, "$someCustomOperation",
+ RestOperationTypeEnum.EXTENDED_OPERATION_INSTANCE);
+ }
+
+ @POST
+ @Path("/{id}/$someCustomOperation")
+ public Response someCustomOperationUsingPost(@PathParam("id") String id, String resource) throws Exception {
+ return customOperation(resource, RequestTypeEnum.POST, id, "$someCustomOperation",
+ RestOperationTypeEnum.EXTENDED_OPERATION_INSTANCE);
+ }
+
+ @Operation(name = "someCustomOperation", idempotent = true, returnParameters = {
+ @OperationParam(name = "return", type = StringDt.class) })
+ public Parameters someCustomOperation(@IdParam IdDt myId, @OperationParam(name = "dummy") StringDt dummyInput) {
+ return mock.someCustomOperation(myId, dummyInput);
+ }
+
+ @Override
+ public Class getResourceType() {
+ return Patient.class;
+ }
+
+ @Override
+ public IPagingProvider getPagingProvider() {
+ return PAGING_PROVIDER;
+ }
+
+}
diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/util/JaxRsMethodBindingsTest.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/util/JaxRsMethodBindingsTest.java
new file mode 100644
index 00000000000..e70c77f1df2
--- /dev/null
+++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/util/JaxRsMethodBindingsTest.java
@@ -0,0 +1,128 @@
+package ca.uhn.fhir.jaxrs.server.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+
+import ca.uhn.fhir.jaxrs.server.test.TestJaxRsDummyPatientProvider;
+import ca.uhn.fhir.model.dstu2.resource.Parameters;
+import ca.uhn.fhir.model.dstu2.resource.Patient;
+import ca.uhn.fhir.model.primitive.IdDt;
+import ca.uhn.fhir.model.primitive.StringDt;
+import ca.uhn.fhir.rest.annotation.IdParam;
+import ca.uhn.fhir.rest.annotation.Operation;
+import ca.uhn.fhir.rest.annotation.OperationParam;
+import ca.uhn.fhir.rest.annotation.RequiredParam;
+import ca.uhn.fhir.rest.annotation.ResourceParam;
+import ca.uhn.fhir.rest.annotation.Search;
+import ca.uhn.fhir.rest.annotation.Update;
+import ca.uhn.fhir.rest.api.MethodOutcome;
+import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
+import ca.uhn.fhir.rest.param.StringParam;
+import ca.uhn.fhir.rest.server.exceptions.NotImplementedOperationException;
+
+@FixMethodOrder(MethodSorters.DEFAULT)
+public class JaxRsMethodBindingsTest {
+
+ @Before
+ public void setUp() {
+ JaxRsMethodBindings.getClassBindings().clear();
+ }
+
+ @Test(expected = NotImplementedOperationException.class)
+ public void testFindMethodsForProviderNotDefinedMappingMethods() {
+ new TestJaxRsDummyPatientProvider().getBindings().getBinding(RestOperationTypeEnum.UPDATE, "");
+ }
+
+ @Test
+ public void testFindMethodsForProviderWithMethods() {
+ class TestFindPatientProvider extends TestJaxRsDummyPatientProvider {
+ @Search
+ public List search(@RequiredParam(name = Patient.SP_NAME) final StringParam name) {
+ return null;
+ }
+ }
+ new TestFindPatientProvider();
+ assertEquals(TestFindPatientProvider.class, new TestFindPatientProvider().getBindings().getBinding(RestOperationTypeEnum.SEARCH_TYPE, "").getMethod().getDeclaringClass());
+ }
+
+ @Test
+ public void testFindMethodsFor2ProvidersWithMethods() {
+ class TestFindPatientProvider extends TestJaxRsDummyPatientProvider {
+ @Search
+ public List search(@RequiredParam(name = Patient.SP_NAME) final StringParam name) {
+ return null;
+ }
+ }
+ class TestUpdatePatientProvider extends TestJaxRsDummyPatientProvider {
+ @Update
+ public MethodOutcome update(@IdParam final IdDt theId, @ResourceParam final Patient patient) {
+ return null;
+ }
+ }
+ assertEquals(TestFindPatientProvider.class, new TestFindPatientProvider().getBindings().getBinding(RestOperationTypeEnum.SEARCH_TYPE, "").getMethod().getDeclaringClass());
+ assertEquals(TestUpdatePatientProvider.class, new TestUpdatePatientProvider().getBindings().getBinding(RestOperationTypeEnum.UPDATE, "").getMethod().getDeclaringClass());
+ }
+
+ @Test
+ public void testFindMethodsWithDoubleMethodsDeclaration() {
+ class TestDoubleSearchProvider extends TestJaxRsDummyPatientProvider {
+ @Search
+ public List search1(@RequiredParam(name = Patient.SP_NAME) final StringParam name) {
+ return null;
+ }
+
+ @Search
+ public List search2(@RequiredParam(name = Patient.SP_NAME) final StringParam name) {
+ return null;
+ }
+ }
+ try {
+ new TestDoubleSearchProvider();
+ fail();
+ } catch(IllegalArgumentException e) {
+ assertTrue(e.getMessage().contains("search1"));
+ assertTrue(e.getMessage().contains("search2"));
+ }
+ }
+
+ @Test
+ public void testFindMethodsWithMultipleMethods() {
+ class TestFindPatientProvider extends TestJaxRsDummyPatientProvider {
+ @Search
+ public List search(@RequiredParam(name = Patient.SP_NAME) final StringParam name) {
+ return null;
+ }
+ @Update
+ public MethodOutcome update(@IdParam final IdDt theId, @ResourceParam final Patient patient) {
+ return null;
+ }
+ @Operation(name = "firstMethod", idempotent = true, returnParameters = { @OperationParam(name = "return", type = StringDt.class) })
+ public Parameters firstMethod(@OperationParam(name = "dummy") StringDt dummyInput) {
+ return null;
+ }
+ @Operation(name = "secondMethod", returnParameters = { @OperationParam(name = "return", type = StringDt.class) })
+ public Parameters secondMethod(@OperationParam(name = "dummy") StringDt dummyInput) {
+ return null;
+ }
+ }
+ JaxRsMethodBindings bindings = new TestFindPatientProvider().getBindings();
+ assertEquals("search", bindings.getBinding(RestOperationTypeEnum.SEARCH_TYPE, "").getMethod().getName());
+ assertEquals("update", bindings.getBinding(RestOperationTypeEnum.UPDATE, "").getMethod().getName());
+ assertEquals("firstMethod", bindings.getBinding(RestOperationTypeEnum.EXTENDED_OPERATION_TYPE, "$firstMethod").getMethod().getName());
+ assertEquals("secondMethod", bindings.getBinding(RestOperationTypeEnum.EXTENDED_OPERATION_TYPE, "$secondMethod").getMethod().getName());
+ try {
+ bindings.getBinding(RestOperationTypeEnum.EXTENDED_OPERATION_TYPE, "$thirdMethod");
+ fail();
+ } catch(NotImplementedOperationException e){
+ }
+ }
+
+}
diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/util/JaxRsRequestTest.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/util/JaxRsRequestTest.java
new file mode 100644
index 00000000000..7be1a639290
--- /dev/null
+++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/util/JaxRsRequestTest.java
@@ -0,0 +1,116 @@
+package ca.uhn.fhir.jaxrs.server.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Arrays;
+
+import javax.ws.rs.HttpMethod;
+import javax.ws.rs.core.MultivaluedHashMap;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.commons.lang3.StringUtils;
+import org.glassfish.jersey.internal.MapPropertiesDelegate;
+import org.glassfish.jersey.server.ContainerRequest;
+import org.junit.Before;
+import org.junit.Test;
+
+import ca.uhn.fhir.jaxrs.server.test.TestJaxRsDummyPatientProvider;
+import ca.uhn.fhir.rest.api.RequestTypeEnum;
+import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
+
+public class JaxRsRequestTest {
+
+ private static final String RESOURCE_STRING = "";
+ private static final String BASEURI = "http://baseuri";
+ private static final String REQUESTURI = "http://baseuri/test";
+
+ private JaxRsRequest details;
+ private MultivaluedMap queryParameters = new MultivaluedHashMap();
+ private ContainerRequest headers;
+ private TestJaxRsDummyPatientProvider provider;
+
+ @Before
+ public void setUp() throws URISyntaxException {
+ details = createRequestDetails();
+ }
+
+ @Test
+ public void testGetHeader() {
+ String headerKey = "key";
+ String headerValue = "location_value";
+ String headerValue2 = "location_value_2";
+ assertTrue(StringUtils.isBlank(details.getHeader(headerKey)));
+ headers.header(headerKey, headerValue);
+ assertEquals(headerValue, details.getHeader(headerKey));
+ assertEquals(Arrays.asList(headerValue), details.getHeaders(headerKey));
+
+ headers.header(headerKey, headerValue2);
+ assertEquals(headerValue, details.getHeader(headerKey));
+ assertEquals(Arrays.asList(headerValue, headerValue2), details.getHeaders(headerKey));
+ }
+
+ @Test
+ public void testGetByteStreamRequestContents() {
+ assertEquals(RESOURCE_STRING, new String(details.getByteStreamRequestContents()));
+ }
+
+ @Test
+ public void testServerBaseForRequest() {
+ assertEquals(BASEURI, new String(details.getServerBaseForRequest()));
+ }
+
+ @Test
+ public void testGetResponse() {
+ JaxRsResponse response = (JaxRsResponse) details.getResponse();
+ assertEquals(details, response.getRequestDetails());
+ assertTrue(response == details.getResponse());
+ }
+
+ @Test(expected = UnsupportedOperationException.class)
+ public void testGetReader() throws IOException {
+ details.getReader();
+ }
+
+ @Test(expected = UnsupportedOperationException.class)
+ public void testGetInputStream() {
+ details.getInputStream();
+ }
+
+ @Test
+ public void testGetServerBaseForRequest() {
+ assertEquals(JaxRsRequestTest.BASEURI, details.getFhirServerBase());
+ }
+
+ @Test
+ public void testGetServer() {
+ assertEquals(this.provider, details.getServer());
+ }
+
+ public JaxRsRequest createRequestDetails() throws URISyntaxException {
+ //headers
+ headers = new ContainerRequest(new URI(BASEURI), new URI(REQUESTURI), HttpMethod.GET, null, new MapPropertiesDelegate());
+
+ //uri info
+ UriInfo uriInfo = mock(UriInfo.class);
+ when(uriInfo.getQueryParameters()).thenReturn(queryParameters);
+
+ //mocks
+ provider = spy(TestJaxRsDummyPatientProvider.class);
+ doReturn(uriInfo).when(provider).getUriInfo();
+ doReturn(BASEURI).when(provider).getBaseForRequest();
+ doReturn(BASEURI).when(provider).getBaseForServer();
+ doReturn(headers).when(provider).getHeaders();
+
+ return new JaxRsRequest(provider, RESOURCE_STRING, RequestTypeEnum.GET, RestOperationTypeEnum.HISTORY_TYPE);
+ }
+
+}
diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/util/JaxRsResponseTest.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/util/JaxRsResponseTest.java
new file mode 100644
index 00000000000..8e26760539a
--- /dev/null
+++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/util/JaxRsResponseTest.java
@@ -0,0 +1,166 @@
+package ca.uhn.fhir.jaxrs.server.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.util.Collections;
+import java.util.Set;
+
+import javax.ws.rs.core.Response;
+
+import org.hl7.fhir.instance.model.api.IBaseBinary;
+import org.hl7.fhir.instance.model.api.IBaseResource;
+import org.junit.Before;
+import org.junit.Test;
+
+import ca.uhn.fhir.model.api.Bundle;
+import ca.uhn.fhir.model.dstu2.resource.Binary;
+import ca.uhn.fhir.model.dstu2.resource.Patient;
+import ca.uhn.fhir.model.primitive.IdDt;
+import ca.uhn.fhir.rest.api.MethodOutcome;
+import ca.uhn.fhir.rest.api.SummaryEnum;
+import ca.uhn.fhir.rest.method.ParseAction;
+import ca.uhn.fhir.rest.server.Constants;
+import ca.uhn.fhir.rest.server.RestfulServerUtils;
+
+public class JaxRsResponseTest {
+
+ private JaxRsResponse response;
+ private JaxRsRequest request;
+ private Bundle bundle;
+ private Set theSummaryMode;
+
+ @Before
+ public void setUp() throws URISyntaxException {
+ request = new JaxRsRequestTest().createRequestDetails();
+ this.response = (JaxRsResponse) request.getResponse();
+ bundle = getSinglePatientResource();
+ theSummaryMode = Collections.emptySet();
+ }
+
+ @Test
+ public void testGetResponseWriterNoZipNoBrowser() throws IOException {
+ boolean theRequestIsBrowser = false;
+ boolean respondGzip = false;
+ Set theSummaryMode = Collections.emptySet();
+ Response result = (Response) RestfulServerUtils.streamResponseAsBundle(request.getServer(), bundle, theSummaryMode, theRequestIsBrowser, respondGzip, request);
+ assertEquals(200, result.getStatus());
+ assertEquals(Constants.CT_FHIR_JSON+Constants.CHARSET_UTF8_CTSUFFIX, result.getHeaderString(Constants.HEADER_CONTENT_TYPE));
+ assertTrue(result.getEntity().toString().contains("Patient"));
+ assertTrue(result.getEntity().toString().contains("15"));
+ }
+
+ @Test
+ public void testGetResponseWriterBrowserNoZip() throws IOException {
+ boolean theRequestIsBrowser = true;
+ boolean respondGzip = false;
+ Response result = (Response) RestfulServerUtils.streamResponseAsBundle(request.getServer(), bundle, theSummaryMode, theRequestIsBrowser, respondGzip, request);
+ assertEquals(200, result.getStatus());
+ assertEquals(Constants.CT_JSON+Constants.CHARSET_UTF8_CTSUFFIX, result.getHeaderString(Constants.HEADER_CONTENT_TYPE));
+ assertTrue(result.getEntity().toString().contains("Patient"));
+ assertTrue(result.getEntity().toString().contains("15"));
+ }
+
+ @Test
+ public void testSendAttachmentResponse() throws IOException {
+ boolean theRequestIsBrowser = true;
+ boolean respondGzip = true;
+ IBaseBinary binary = new Binary();
+ String contentType = "foo";
+ byte[] content = new byte[] { 1, 2, 3, 4 };
+ binary.setContentType(contentType);
+ binary.setContent(content);
+ boolean theAddContentLocationHeader = false;
+ Response result = (Response) RestfulServerUtils.streamResponseAsResource(request.getServer(), binary, theRequestIsBrowser, theSummaryMode, 200, respondGzip, theAddContentLocationHeader, respondGzip, this.request);
+ assertEquals(200, result.getStatus());
+ assertEquals(contentType, result.getHeaderString(Constants.HEADER_CONTENT_TYPE));
+ assertEquals(content, result.getEntity());
+ }
+
+ @Test
+ public void testSendAttachmentResponseNoContent() throws IOException {
+ boolean theRequestIsBrowser = true;
+ boolean respondGzip = true;
+ IBaseBinary binary = new Binary();
+ binary.setContent(new byte[]{});
+ boolean theAddContentLocationHeader = false;
+ Response result = (Response) RestfulServerUtils.streamResponseAsResource(request.getServer(), binary, theRequestIsBrowser, theSummaryMode, 200, respondGzip, theAddContentLocationHeader, respondGzip, this.request);
+ assertEquals(200, result.getStatus());
+ assertEquals(null, result.getHeaderString(Constants.HEADER_CONTENT_TYPE));
+ assertEquals(null, result.getEntity());
+ }
+
+ @Test
+ public void testSendAttachmentResponseEmptyContent() throws IOException {
+ boolean theRequestIsBrowser = true;
+ boolean respondGzip = true;
+ IBaseBinary binary = new Binary();
+ boolean theAddContentLocationHeader = false;
+ Response result = (Response) RestfulServerUtils.streamResponseAsResource(request.getServer(), binary, theRequestIsBrowser, theSummaryMode, 200, respondGzip, theAddContentLocationHeader, respondGzip, this.request);
+ assertEquals(200, result.getStatus());
+ assertEquals(null, result.getHeaderString(Constants.HEADER_CONTENT_TYPE));
+ assertEquals(null, result.getEntity());
+ }
+
+
+ @Test
+ public void testReturnResponse() throws IOException {
+ IdDt theId = new IdDt(15L);
+ ParseAction> outcome = ParseAction.create(createPatient());
+ int operationStatus = 200;
+ boolean allowPrefer = true;
+ String resourceName = "Patient";
+ MethodOutcome methodOutcome = new MethodOutcome(theId);
+ Response result = response.returnResponse(outcome, operationStatus, allowPrefer, methodOutcome, resourceName);
+ assertEquals(200, result.getStatus());
+ assertEquals(Constants.CT_JSON+Constants.CHARSET_UTF8_CTSUFFIX, result.getHeaderString(Constants.HEADER_CONTENT_TYPE));
+ System.out.println(result.getEntity().toString());
+ assertTrue(result.getEntity().toString().contains("resourceType\":\"Patient"));
+ assertTrue(result.getEntity().toString().contains("15"));
+
+ }
+
+ @Test
+ public void testReturnResponseAsXml() throws IOException {
+ IdDt theId = new IdDt(15L);
+ ParseAction> outcome = ParseAction.create(createPatient());
+ int operationStatus = 200;
+ boolean allowPrefer = true;
+ String resourceName = "Patient";
+ MethodOutcome methodOutcome = new MethodOutcome(theId);
+ response.getRequestDetails().getParameters().put(Constants.PARAM_FORMAT, new String[]{Constants.CT_XML});
+ Response result = response.returnResponse(outcome, operationStatus, allowPrefer, methodOutcome, resourceName);
+ assertEquals(200, result.getStatus());
+ assertEquals(Constants.CT_XML+Constants.CHARSET_UTF8_CTSUFFIX, result.getHeaderString(Constants.HEADER_CONTENT_TYPE));
+ assertTrue(result.getEntity().toString().contains(" outcome = ParseAction.create((IBaseResource) null);
+ int operationStatus = Constants.STATUS_HTTP_204_NO_CONTENT;
+ boolean allowPrefer = true;
+ String resourceName = "Patient";
+ MethodOutcome methodOutcome = new MethodOutcome(null);
+ response.getRequestDetails().getParameters().put(Constants.PARAM_FORMAT, new String[]{Constants.CT_XML});
+ Response result = response.returnResponse(outcome, operationStatus, allowPrefer, methodOutcome, resourceName);
+ assertEquals(204, result.getStatus());
+ assertEquals(Constants.CT_XML+Constants.CHARSET_UTF8_CTSUFFIX, result.getHeaderString(Constants.HEADER_CONTENT_TYPE));
+ }
+
+ private Bundle getSinglePatientResource() {
+ Patient theResource = createPatient();
+ Bundle bundle = Bundle.withSingleResource(theResource);
+ return bundle;
+ }
+
+ private Patient createPatient() {
+ Patient theResource = new Patient();
+ theResource.setId(new IdDt(15L));
+ return theResource;
+ }
+
+}
diff --git a/hapi-fhir-jaxrsserver-base/src/test/resources/logback-test.xml b/hapi-fhir-jaxrsserver-base/src/test/resources/logback-test.xml
new file mode 100644
index 00000000000..e5cbbb9c22e
--- /dev/null
+++ b/hapi-fhir-jaxrsserver-base/src/test/resources/logback-test.xml
@@ -0,0 +1,30 @@
+
+
+
+
+ %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} [%file:%line] - %msg%n
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/hapi-fhir-jaxrsserver-example/pom.xml b/hapi-fhir-jaxrsserver-example/pom.xml
new file mode 100644
index 00000000000..31d115567ff
--- /dev/null
+++ b/hapi-fhir-jaxrsserver-example/pom.xml
@@ -0,0 +1,106 @@
+
+ 4.0.0
+
+
+
+ ca.uhn.hapi.fhir
+ hapi-fhir
+ 1.4-SNAPSHOT
+ ../pom.xml
+
+
+ hapi-fhir-jaxrsserver-example
+ war
+
+ HAPI FHIR JAX-RS Server - Example
+
+
+
+ oss-snapshots
+
+ true
+
+ https://oss.sonatype.org/content/repositories/snapshots/
+
+
+
+
+
+
+
+ ca.uhn.hapi.fhir
+ hapi-fhir-jaxrsserver-base
+ 1.4-SNAPSHOT
+
+
+
+ javax.ws.rs
+ javax.ws.rs-api
+ 2.0
+ provided
+
+
+ javax.ejb
+ ejb-api
+ 3.0
+ provided
+
+
+
+ org.eclipse.jetty
+ jetty-server
+ ${jetty_version}
+
+
+ org.eclipse.jetty
+ jetty-servlet
+ ${jetty_version}
+
+
+ org.glassfish.jersey.core
+ jersey-server
+ ${jersey_version}
+
+
+ org.glassfish.jersey.containers
+ jersey-container-servlet-core
+ ${jersey_version}
+
+
+ org.glassfish.jersey.containers
+ jersey-container-jetty-http
+ ${jersey_version}
+
+
+ org.glassfish.jersey.media
+ jersey-media-moxy
+ ${jersey_version}
+
+
+
+
+
+ hapi-fhir-jaxrsserver-example
+
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+
+ true
+
+
+
+
+ integration-test
+ verify
+
+
+
+
+
+
+
+
+
diff --git a/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/FhirPatientDemoApplication.java b/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/FhirPatientDemoApplication.java
new file mode 100644
index 00000000000..3123ed6969f
--- /dev/null
+++ b/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/FhirPatientDemoApplication.java
@@ -0,0 +1,15 @@
+package ca.uhn.fhir.jaxrs.server.example;
+
+import javax.ws.rs.ApplicationPath;
+import javax.ws.rs.core.Application;
+
+/**
+ * Fhir Patient Demo Application
+ *
+ * @author Peter Van Houte | peter.vanhoute@agfa.com | Agfa Healthcare
+ */
+@ApplicationPath(value=FhirPatientDemoApplication.PATH)
+public class FhirPatientDemoApplication extends Application {
+ /** The demo application path */
+ public final static String PATH = "/jaxrs-demo";
+}
diff --git a/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/JaxRsConformanceProvider.java b/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/JaxRsConformanceProvider.java
new file mode 100644
index 00000000000..c38ef890a34
--- /dev/null
+++ b/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/JaxRsConformanceProvider.java
@@ -0,0 +1,45 @@
+package ca.uhn.fhir.jaxrs.server.example;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.ejb.Stateless;
+import javax.inject.Inject;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+import ca.uhn.fhir.jaxrs.server.AbstractJaxRsConformanceProvider;
+import ca.uhn.fhir.rest.server.Constants;
+import ca.uhn.fhir.rest.server.IResourceProvider;
+
+/**
+ * Conformance Rest Service
+ *
+ * @author Peter Van Houte | peter.vanhoute@agfa.com | Agfa Healthcare
+ */
+@Path("")
+@Stateless
+@Produces({ MediaType.APPLICATION_JSON, Constants.CT_FHIR_JSON, Constants.CT_FHIR_XML })
+public class JaxRsConformanceProvider extends AbstractJaxRsConformanceProvider {
+ private static final String SERVER_VERSION = "1.0.0";
+ private static final String SERVER_DESCRIPTION = "Jax-Rs Test Example Description";
+ private static final String SERVER_NAME = "Jax-Rs Test Example";
+
+ @Inject
+ private JaxRsPatientRestProvider patientProvider;
+
+ /**
+ * Standard Constructor
+ */
+ public JaxRsConformanceProvider() {
+ super(SERVER_VERSION, SERVER_DESCRIPTION, SERVER_NAME);
+ }
+
+ @Override
+ protected ConcurrentHashMap, IResourceProvider> getProviders() {
+ ConcurrentHashMap, IResourceProvider> map = new ConcurrentHashMap, IResourceProvider>();
+ map.put(JaxRsConformanceProvider.class, this);
+ map.put(JaxRsPatientRestProvider.class, patientProvider);
+ return map;
+ }
+}
diff --git a/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/JaxRsPageProvider.java b/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/JaxRsPageProvider.java
new file mode 100644
index 00000000000..899bf2430ad
--- /dev/null
+++ b/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/JaxRsPageProvider.java
@@ -0,0 +1,22 @@
+package ca.uhn.fhir.jaxrs.server.example;
+
+import javax.ejb.Stateless;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+import ca.uhn.fhir.jaxrs.server.AbstractJaxRsPageProvider;
+import ca.uhn.fhir.rest.server.Constants;
+import ca.uhn.fhir.rest.server.IPagingProvider;
+
+@Path("/")
+@Stateless
+@Produces({ MediaType.APPLICATION_JSON, Constants.CT_FHIR_JSON, Constants.CT_FHIR_XML })
+public class JaxRsPageProvider extends AbstractJaxRsPageProvider {
+
+ @Override
+ public IPagingProvider getPagingProvider() {
+ return JaxRsPatientRestProvider.PAGE_PROVIDER;
+ }
+
+}
diff --git a/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/JaxRsPatientRestProvider.java b/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/JaxRsPatientRestProvider.java
new file mode 100644
index 00000000000..66d9bcbb5eb
--- /dev/null
+++ b/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/JaxRsPatientRestProvider.java
@@ -0,0 +1,256 @@
+package ca.uhn.fhir.jaxrs.server.example;
+
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.ejb.Local;
+import javax.ejb.Stateless;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import ca.uhn.fhir.jaxrs.server.AbstractJaxRsResourceProvider;
+import ca.uhn.fhir.model.api.IResource;
+import ca.uhn.fhir.model.dstu2.resource.Condition;
+import ca.uhn.fhir.model.dstu2.resource.Parameters;
+import ca.uhn.fhir.model.dstu2.resource.Patient;
+import ca.uhn.fhir.model.primitive.IdDt;
+import ca.uhn.fhir.model.primitive.StringDt;
+import ca.uhn.fhir.rest.annotation.ConditionalUrlParam;
+import ca.uhn.fhir.rest.annotation.Create;
+import ca.uhn.fhir.rest.annotation.Delete;
+import ca.uhn.fhir.rest.annotation.IdParam;
+import ca.uhn.fhir.rest.annotation.Operation;
+import ca.uhn.fhir.rest.annotation.OperationParam;
+import ca.uhn.fhir.rest.annotation.Read;
+import ca.uhn.fhir.rest.annotation.RequiredParam;
+import ca.uhn.fhir.rest.annotation.ResourceParam;
+import ca.uhn.fhir.rest.annotation.Search;
+import ca.uhn.fhir.rest.annotation.Update;
+import ca.uhn.fhir.rest.api.MethodOutcome;
+import ca.uhn.fhir.rest.api.RequestTypeEnum;
+import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
+import ca.uhn.fhir.rest.param.StringParam;
+import ca.uhn.fhir.rest.server.AddProfileTagEnum;
+import ca.uhn.fhir.rest.server.BundleInclusionRule;
+import ca.uhn.fhir.rest.server.Constants;
+import ca.uhn.fhir.rest.server.ETagSupportEnum;
+import ca.uhn.fhir.rest.server.FifoMemoryPagingProvider;
+import ca.uhn.fhir.rest.server.IPagingProvider;
+import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
+import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
+
+/**
+ * A demo JaxRs Patient Rest Provider
+ *
+ * @author Peter Van Houte | peter.vanhoute@agfa.com | Agfa Healthcare
+ */
+@Local
+@Path(JaxRsPatientRestProvider.PATH)
+@Stateless
+@Produces({ MediaType.APPLICATION_JSON, Constants.CT_FHIR_JSON, Constants.CT_FHIR_XML })
+public class JaxRsPatientRestProvider extends AbstractJaxRsResourceProvider {
+
+ private static Long counter = 1L;
+
+ /**
+ * The HAPI paging provider for this server
+ */
+ public static final IPagingProvider PAGE_PROVIDER;
+
+ static final String PATH = "/Patient";
+ private static final ConcurrentHashMap> patients = new ConcurrentHashMap>();
+
+ static {
+ PAGE_PROVIDER = new FifoMemoryPagingProvider(10);
+ }
+
+ static {
+ patients.put(String.valueOf(counter), createPatient("Van Houte"));
+ patients.put(String.valueOf(counter), createPatient("Agnew"));
+ for (int i = 0; i < 20; i++) {
+ patients.put(String.valueOf(counter), createPatient("Random Patient " + counter));
+ }
+ }
+
+ public JaxRsPatientRestProvider() {
+ super(JaxRsPatientRestProvider.class);
+ }
+
+ @Create
+ public MethodOutcome create(@ResourceParam final Patient patient, @ConditionalUrlParam String theConditional) throws Exception {
+ patients.put("" + counter, createPatient(patient));
+ final MethodOutcome result = new MethodOutcome().setCreated(true);
+ result.setResource(patient);
+ result.setId(patient.getId());
+ return result;
+ }
+
+ @Delete
+ public MethodOutcome delete(@IdParam final IdDt theId) {
+ final Patient deletedPatient = find(theId);
+ patients.remove(deletedPatient.getId().getIdPart());
+ final MethodOutcome result = new MethodOutcome().setCreated(true);
+ result.setResource(deletedPatient);
+ return result;
+ }
+
+ @Read
+ public Patient find(@IdParam final IdDt theId) {
+ if (patients.containsKey(theId.getIdPart())) {
+ return getLast(patients.get(theId.getIdPart()));
+ } else {
+ throw new ResourceNotFoundException(theId);
+ }
+ }
+
+ @Read(version = true)
+ public Patient findHistory(@IdParam final IdDt theId) {
+ if (patients.containsKey(theId.getIdPart())) {
+ final List list = patients.get(theId.getIdPart());
+ for (final Patient patient : list) {
+ if (patient.getId().getVersionIdPartAsLong().equals(theId.getVersionIdPartAsLong())) {
+ return patient;
+ }
+ }
+ }
+ throw new ResourceNotFoundException(theId);
+ }
+
+ @Operation(name = "firstVersion", idempotent = true, returnParameters = { @OperationParam(name = "return", type = StringDt.class) })
+ public Parameters firstVersion(@IdParam final IdDt theId, @OperationParam(name = "dummy") StringDt dummyInput) {
+ Parameters parameters = new Parameters();
+ Patient patient = find(new IdDt(theId.getResourceType(), theId.getIdPart(), "0"));
+ parameters.addParameter().setName("return").setResource(patient).setValue(new StringDt((counter - 1) + "" + "inputVariable [ " + dummyInput.getValue() + "]"));
+ return parameters;
+ }
+
+ @Override
+ public AddProfileTagEnum getAddProfileTag() {
+ return AddProfileTagEnum.NEVER;
+ }
+
+ @Override
+ public BundleInclusionRule getBundleInclusionRule() {
+ return BundleInclusionRule.BASED_ON_INCLUDES;
+ }
+
+ @Override
+ public ETagSupportEnum getETagSupport() {
+ return ETagSupportEnum.DISABLED;
+ }
+
+ /** THE DEFAULTS */
+
+ @Override
+ public List getInterceptors() {
+ return Collections.emptyList();
+ }
+
+ private Patient getLast(final List list) {
+ return list.get(list.size() - 1);
+ }
+
+ @Override
+ public IPagingProvider getPagingProvider() {
+ return PAGE_PROVIDER;
+ }
+
+ @Override
+ public Class getResourceType() {
+ return Patient.class;
+ }
+
+ @Override
+ public boolean isDefaultPrettyPrint() {
+ return true;
+ }
+
+ @Override
+ public boolean isUseBrowserFriendlyContentTypes() {
+ return true;
+ }
+
+ @GET
+ @Path("/{id}/$firstVersion")
+ public Response operationFirstVersionUsingGet(@PathParam("id") String id) throws IOException {
+ return customOperation(null, RequestTypeEnum.GET, id, "$firstVersion", RestOperationTypeEnum.EXTENDED_OPERATION_INSTANCE);
+ }
+
+ @POST
+ @Path("/{id}/$firstVersion")
+ public Response operationFirstVersionUsingGet(@PathParam("id") String id, final String resource) throws Exception {
+ return customOperation(resource, RequestTypeEnum.POST, id, "$firstVersion", RestOperationTypeEnum.EXTENDED_OPERATION_INSTANCE);
+ }
+
+ @Search
+ public List search(@RequiredParam(name = Patient.SP_NAME) final StringParam name) {
+ final List result = new LinkedList();
+ for (final List patientIterator : patients.values()) {
+ Patient single = null;
+ for (Patient patient : patientIterator) {
+ if (name == null || patient.getNameFirstRep().getFamilyFirstRep().getValueNotNull().equals(name.getValueNotNull())) {
+ single = patient;
+ }
+ }
+ if (single != null) {
+ result.add(single);
+ }
+ }
+ return result;
+ }
+
+ @Search(compartmentName = "Condition")
+ public List searchCompartment(@IdParam IdDt thePatientId) {
+ List retVal = new ArrayList();
+ Condition condition = new Condition();
+ condition.setId(new IdDt("665577"));
+ retVal.add(condition);
+ return retVal;
+ }
+
+ @Update
+ public MethodOutcome update(@IdParam final IdDt theId, @ResourceParam final Patient patient) {
+ final String idPart = theId.getIdPart();
+ if (patients.containsKey(idPart)) {
+ final List patientList = patients.get(idPart);
+ final Patient lastPatient = getLast(patientList);
+ patient.setId(createId(theId.getIdPartAsLong(), lastPatient.getId().getVersionIdPartAsLong() + 1));
+ patientList.add(patient);
+ final MethodOutcome result = new MethodOutcome().setCreated(false);
+ result.setResource(patient);
+ result.setId(patient.getId());
+ return result;
+ } else {
+ throw new ResourceNotFoundException(theId);
+ }
+ }
+
+ private static IdDt createId(final Long id, final Long theVersionId) {
+ return new IdDt("Patient", "" + id, "" + theVersionId);
+ }
+
+ private static List createPatient(final Patient patient) {
+ patient.setId(createId(counter, 1L));
+ final LinkedList list = new LinkedList();
+ list.add(patient);
+ counter++;
+ return list;
+ }
+
+ private static List createPatient(final String name) {
+ final Patient patient = new Patient();
+ patient.getNameFirstRep().addFamily(name);
+ return createPatient(patient);
+ }
+
+}
diff --git a/hapi-fhir-jaxrsserver-example/src/main/webapp/WEB-INF/web.xml b/hapi-fhir-jaxrsserver-example/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 00000000000..0bd809a85b1
--- /dev/null
+++ b/hapi-fhir-jaxrsserver-example/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,52 @@
+
+
+
+
+
+
diff --git a/hapi-fhir-jaxrsserver-example/src/test/java/ca/uhn/fhir/jaxrs/server/example/JaxRsPatientProviderTest.java b/hapi-fhir-jaxrsserver-example/src/test/java/ca/uhn/fhir/jaxrs/server/example/JaxRsPatientProviderTest.java
new file mode 100644
index 00000000000..d22015b3d69
--- /dev/null
+++ b/hapi-fhir-jaxrsserver-example/src/test/java/ca/uhn/fhir/jaxrs/server/example/JaxRsPatientProviderTest.java
@@ -0,0 +1,311 @@
+package ca.uhn.fhir.jaxrs.server.example;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.commons.lang3.StringUtils;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.model.api.BundleEntry;
+import ca.uhn.fhir.model.dstu2.composite.HumanNameDt;
+import ca.uhn.fhir.model.dstu2.resource.Bundle;
+import ca.uhn.fhir.model.dstu2.resource.Conformance;
+import ca.uhn.fhir.model.dstu2.resource.Parameters;
+import ca.uhn.fhir.model.dstu2.resource.Patient;
+import ca.uhn.fhir.model.primitive.BoundCodeDt;
+import ca.uhn.fhir.model.primitive.DateDt;
+import ca.uhn.fhir.model.primitive.IdDt;
+import ca.uhn.fhir.model.primitive.StringDt;
+import ca.uhn.fhir.model.valueset.BundleEntryTransactionMethodEnum;
+import ca.uhn.fhir.rest.api.MethodOutcome;
+import ca.uhn.fhir.rest.api.PreferReturnEnum;
+import ca.uhn.fhir.rest.client.IGenericClient;
+import ca.uhn.fhir.rest.client.ServerValidationModeEnum;
+import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
+import ca.uhn.fhir.rest.method.SearchStyleEnum;
+import ca.uhn.fhir.rest.server.EncodingEnum;
+
+public class JaxRsPatientProviderTest {
+
+ private static IGenericClient client;
+ private static final FhirContext ourCtx = FhirContext.forDstu2();
+ private static final String PATIENT_NAME = "Van Houte";
+ private static int ourPort;
+ private static Server jettyServer;
+
+ @BeforeClass
+ public static void setUpClass()
+ throws Exception {
+ ourPort = RandomServerPortProvider.findFreePort();
+ ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
+ context.setContextPath("/");
+ System.out.println(ourPort);
+ jettyServer = new Server(ourPort);
+ jettyServer.setHandler(context);
+ ServletHolder jerseyServlet = context.addServlet(org.glassfish.jersey.servlet.ServletContainer.class, "/*");
+ jerseyServlet.setInitOrder(0);
+ //@formatter:off
+ jerseyServlet.setInitParameter("jersey.config.server.provider.classnames",
+ StringUtils.join(Arrays.asList(
+ JaxRsConformanceProvider.class.getCanonicalName(),
+ JaxRsPatientRestProvider.class.getCanonicalName(),
+ JaxRsPageProvider.class.getCanonicalName()
+ ), ";"));
+ //@formatter:on
+ jettyServer.start();
+
+ ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER);
+ ourCtx.getRestfulClientFactory().setSocketTimeout(1200 * 1000);
+ client = ourCtx.newRestfulGenericClient("http://localhost:" + ourPort + "/");
+ client.setEncoding(EncodingEnum.JSON);
+ client.registerInterceptor(new LoggingInterceptor(true));
+ }
+
+ @AfterClass
+ public static void tearDownClass()
+ throws Exception {
+ try {
+ jettyServer.destroy();
+ }
+ catch (Exception e) {
+ }
+ }
+
+ /** Search/Query - Type */
+ @Test
+ public void findUsingGenericClientBySearch() {
+ // Perform a search
+ final ca.uhn.fhir.model.api.Bundle results = client.search().forResource(Patient.class)
+ .where(Patient.NAME.matchesExactly().value(PATIENT_NAME)).execute();
+ System.out.println(results.getEntries().get(0));
+ assertEquals(results.getEntries().size(), 1);
+ }
+
+ /** Search - Multi-valued Parameters (ANY/OR) */
+ @Test
+ public void findUsingGenericClientBySearchWithMultiValues() {
+ final ca.uhn.fhir.model.api.Bundle response = client.search().forResource(Patient.class)
+ .where(Patient.ADDRESS.matches().values("Toronto")).and(Patient.ADDRESS.matches().values("Ontario"))
+ .and(Patient.ADDRESS.matches().values("Canada"))
+ .where(Patient.IDENTIFIER.exactly().systemAndIdentifier("SHORTNAME", "TOYS")).execute();
+ System.out.println(response.getEntries().get(0));
+ }
+
+ /** Search - Paging */
+ @Test
+ public void findWithPaging() {
+ // Perform a search
+ final Bundle results = client.search().forResource(Patient.class).limitTo(8).returnBundle(Bundle.class).execute();
+ System.out.println(results.getEntry().size());
+
+ if (results.getLink(Bundle.LINK_NEXT) != null) {
+
+ // load next page
+ final Bundle nextPage = client.loadPage().next(results).execute();
+ System.out.println(nextPage.getEntry().size());
+ }
+ }
+
+ /** Search using other query options */
+ public void testOther() {
+ //missing
+ }
+
+ /** */
+ @Test
+ public void testSearchPost() {
+ Bundle response = client.search()
+ .forResource("Patient")
+ .usingStyle(SearchStyleEnum.POST)
+ .returnBundle(Bundle.class)
+ .execute();
+ assertTrue(response.getEntry().size() > 0);
+ }
+
+ /** Search - Compartments */
+ @Test
+ public void testSearchCompartements() {
+ Bundle response = client.search()
+ .forResource(Patient.class)
+ .withIdAndCompartment("1", "Condition")
+ .returnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class)
+ .execute();
+ assertTrue(response.getEntry().size() > 0);
+ }
+
+ /** Search - Subsetting (_summary and _elements) */
+ @Test
+ @Ignore
+ public void testSummary() {
+ client.search()
+ .forResource(Patient.class)
+ .returnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class)
+ .execute();
+ }
+
+ @Test
+ public void testCreatePatient() {
+ final Patient existing = new Patient();
+ existing.setId((IdDt) null);
+ existing.getNameFirstRep().addFamily("Created Patient 54");
+ client.setEncoding(EncodingEnum.JSON);
+ final MethodOutcome results = client.create().resource(existing).prefer(PreferReturnEnum.REPRESENTATION).execute();
+ System.out.println(results.getId());
+ final Patient patient = (Patient) results.getResource();
+ System.out.println(patient);
+ assertNotNull(client.read(patient.getId()));
+ client.setEncoding(EncodingEnum.JSON);
+ }
+
+
+ /** Conditional Creates */
+ @Test
+ public void testConditionalCreate() {
+ final Patient existing = new Patient();
+ existing.setId((IdDt) null);
+ existing.getNameFirstRep().addFamily("Created Patient 54");
+ client.setEncoding(EncodingEnum.XML);
+ final MethodOutcome results = client.create().resource(existing).prefer(PreferReturnEnum.REPRESENTATION).execute();
+ System.out.println(results.getId());
+ final Patient patient = (Patient) results.getResource();
+
+ client.create()
+ .resource(patient)
+ .conditional()
+ .where(Patient.IDENTIFIER.exactly().identifier(patient.getIdentifierFirstRep()))
+ .execute();
+ }
+
+
+ /** Find By Id */
+ @Test
+ public void findUsingGenericClientById() {
+ final Patient results = client.read(Patient.class, "1");
+ assertEquals(results.getId().getIdPartAsLong().longValue(), 1L);
+ }
+
+ @Test
+ public void testUpdateById() {
+ final Patient existing = client.read(Patient.class, "1");
+ final List name = existing.getName();
+ name.get(0).addSuffix("The Second");
+ existing.setName(name);
+ client.setEncoding(EncodingEnum.XML);
+ final MethodOutcome results = client.update("1", existing);
+ }
+
+ @Test
+ public void testDeletePatient() {
+ final Patient existing = new Patient();
+ existing.getNameFirstRep().addFamily("Created Patient XYZ");
+ final MethodOutcome results = client.create().resource(existing).prefer(PreferReturnEnum.REPRESENTATION).execute();
+ System.out.println(results.getId());
+ final Patient patient = (Patient) results.getResource();
+ client.delete(Patient.class, patient.getId());
+ try {
+ client.read(patient.getId());
+ fail();
+ }
+ catch (final Exception e) {
+ //assertEquals(e.getStatusCode(), Constants.STATUS_HTTP_404_NOT_FOUND);
+ }
+ }
+
+ /** Transaction - Server */
+ @Ignore
+ @Test
+ public void testTransaction() {
+ ca.uhn.fhir.model.api.Bundle bundle = new ca.uhn.fhir.model.api.Bundle();
+ BundleEntry entry = bundle.addEntry();
+ final Patient existing = new Patient();
+ existing.getNameFirstRep().addFamily("Created with bundle");
+ entry.setResource(existing);
+
+ BoundCodeDt theTransactionOperation =
+ new BoundCodeDt(
+ BundleEntryTransactionMethodEnum.VALUESET_BINDER,
+ BundleEntryTransactionMethodEnum.POST);
+ entry.setTransactionMethod(theTransactionOperation);
+ ca.uhn.fhir.model.api.Bundle response = client.transaction().withBundle(bundle).execute();
+ }
+
+ /** Conformance - Server */
+ @Test
+ @Ignore
+ public void testConformance() {
+ final Conformance conf = client.fetchConformance().ofType(Conformance.class).execute();
+ System.out.println(conf.getRest().get(0).getResource().get(0).getType());
+ assertEquals(conf.getRest().get(0).getResource().get(0).getType().toString(), "Patient");
+ }
+
+ /** Extended Operations */
+ // Create a client to talk to the HeathIntersections server
+ @Test
+ public void testExtendedOperations() {
+ client.registerInterceptor(new LoggingInterceptor(true));
+
+ // Create the input parameters to pass to the server
+ Parameters inParams = new Parameters();
+ inParams.addParameter().setName("start").setValue(new DateDt("2001-01-01"));
+ inParams.addParameter().setName("end").setValue(new DateDt("2015-03-01"));
+ inParams.addParameter().setName("dummy").setValue(new StringDt("myAwesomeDummyValue"));
+
+ // Invoke $everything on "Patient/1"
+ Parameters outParams = client
+ .operation()
+ .onInstance(new IdDt("Patient", "1"))
+ .named("$firstVersion")
+ .withParameters(inParams)
+ //.useHttpGet() // Use HTTP GET instead of POST
+ .execute();
+ String resultValue = outParams.getParameter().get(0).getValue().toString();
+ System.out.println(resultValue);
+ assertEquals("expected but found : "+ resultValue, resultValue.contains("myAwesomeDummyValue"), true);
+ }
+
+ @Test
+ public void testExtendedOperationsUsingGet() {
+ // Create the input parameters to pass to the server
+ Parameters inParams = new Parameters();
+ inParams.addParameter().setName("start").setValue(new DateDt("2001-01-01"));
+ inParams.addParameter().setName("end").setValue(new DateDt("2015-03-01"));
+ inParams.addParameter().setName("dummy").setValue(new StringDt("myAwesomeDummyValue"));
+
+ // Invoke $everything on "Patient/1"
+ Parameters outParams = client
+ .operation()
+ .onInstance(new IdDt("Patient", "1"))
+ .named("$firstVersion")
+ .withParameters(inParams)
+ .useHttpGet() // Use HTTP GET instead of POST
+ .execute();
+ String resultValue = outParams.getParameter().get(0).getValue().toString();
+ System.out.println(resultValue);
+ assertEquals("expected but found : "+ resultValue, resultValue.contains("myAwesomeDummyValue"), true);
+ }
+
+ @Test
+ public void testVRead() {
+ final Patient patient = client.vread(Patient.class, "1", "1");
+ System.out.println(patient);
+ }
+
+ @Test
+ public void testRead() {
+ final Patient patient = client.read(Patient.class, "1");
+ System.out.println(patient);
+ }
+
+}
diff --git a/hapi-fhir-jaxrsserver-example/src/test/java/ca/uhn/fhir/jaxrs/server/example/RandomServerPortProvider.java b/hapi-fhir-jaxrsserver-example/src/test/java/ca/uhn/fhir/jaxrs/server/example/RandomServerPortProvider.java
new file mode 100644
index 00000000000..4cfb0eb97d4
--- /dev/null
+++ b/hapi-fhir-jaxrsserver-example/src/test/java/ca/uhn/fhir/jaxrs/server/example/RandomServerPortProvider.java
@@ -0,0 +1,36 @@
+package ca.uhn.fhir.jaxrs.server.example;
+
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Provides server ports
+ */
+public class RandomServerPortProvider {
+
+ private static List ourPorts = new ArrayList();
+
+ public static int findFreePort() {
+ ServerSocket server;
+ try {
+ server = new ServerSocket(0);
+ int port = server.getLocalPort();
+ ourPorts.add(port);
+ server.close();
+ Thread.sleep(500);
+ return port;
+ } catch (IOException e) {
+ throw new Error(e);
+ } catch (InterruptedException e) {
+ throw new Error(e);
+ }
+ }
+
+ public static List list() {
+ return ourPorts;
+ }
+
+}
+
\ No newline at end of file
diff --git a/hapi-fhir-jaxrsserver-example/src/test/resources/logback-test.xml b/hapi-fhir-jaxrsserver-example/src/test/resources/logback-test.xml
new file mode 100644
index 00000000000..e5cbbb9c22e
--- /dev/null
+++ b/hapi-fhir-jaxrsserver-example/src/test/resources/logback-test.xml
@@ -0,0 +1,30 @@
+
+
+
+
+ %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} [%file:%line] - %msg%n
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2.java
index 1879e7fa52c..8de6a67ecbd 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2.java
@@ -55,6 +55,7 @@ import com.google.common.collect.ArrayListMultimap;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.jpa.entity.ResourceTable;
import ca.uhn.fhir.jpa.entity.TagDefinition;
+import ca.uhn.fhir.jpa.provider.ServletSubRequestDetails;
import ca.uhn.fhir.jpa.util.DeleteConflict;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
@@ -84,6 +85,7 @@ import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.NotModifiedException;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
+import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.util.FhirTerser;
import ca.uhn.fhir.util.UrlUtil;
import ca.uhn.fhir.util.UrlUtil.UrlParts;
@@ -119,7 +121,7 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao {
subRequestBundle.setType(BundleTypeEnum.TRANSACTION);
subRequestBundle.addEntry(nextRequestEntry);
- Bundle subResponseBundle = transaction(theRequestDetails, subRequestBundle, "Batch sub-request");
+ Bundle subResponseBundle = transaction((ServletRequestDetails) theRequestDetails, subRequestBundle, "Batch sub-request");
return subResponseBundle;
}
};
@@ -242,11 +244,11 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao {
notifyInterceptors(RestOperationTypeEnum.TRANSACTION, requestDetails);
String actionName = "Transaction";
- return transaction(theRequestDetails, theRequest, actionName);
+ return transaction((ServletRequestDetails) theRequestDetails, theRequest, actionName);
}
@SuppressWarnings("unchecked")
- private Bundle transaction(RequestDetails theRequestDetails, Bundle theRequest, String theActionName) {
+ private Bundle transaction(ServletRequestDetails theRequestDetails, Bundle theRequest, String theActionName) {
BundleTypeEnum transactionType = theRequest.getTypeElement().getValueAsEnum();
if (transactionType == BundleTypeEnum.BATCH) {
return batch(theRequestDetails, theRequest);
@@ -491,7 +493,7 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao {
Integer originalOrder = originalRequestOrder.get(nextReqEntry);
Entry nextRespEntry = response.getEntry().get(originalOrder);
- RequestDetails requestDetails = new RequestDetails();
+ ServletSubRequestDetails requestDetails = new ServletSubRequestDetails();
requestDetails.setServletRequest(theRequestDetails.getServletRequest());
requestDetails.setRequestType(RequestTypeEnum.GET);
requestDetails.setServer(theRequestDetails.getServer());
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaProvider.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaProvider.java
index 47d8f1f84be..4e517f0e0ca 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaProvider.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaProvider.java
@@ -31,6 +31,7 @@ import org.jboss.logging.MDC;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.method.RequestDetails;
+import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
public class BaseJpaProvider {
@@ -45,7 +46,7 @@ public class BaseJpaProvider {
MDC.remove(REMOTE_UA);
}
- public void endRequest(RequestDetails theRequest) {
+ public void endRequest(ServletRequestDetails theRequest) {
endRequest(theRequest.getServletRequest());
}
@@ -92,7 +93,7 @@ public class BaseJpaProvider {
}
- public void startRequest(RequestDetails theRequest) {
+ public void startRequest(ServletRequestDetails theRequest) {
startRequest(theRequest.getServletRequest());
}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaSystemProviderDstu1.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaSystemProviderDstu1.java
index e4f958d3757..2de82d0525b 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaSystemProviderDstu1.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaSystemProviderDstu1.java
@@ -26,16 +26,17 @@ import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.rest.annotation.Transaction;
import ca.uhn.fhir.rest.annotation.TransactionParam;
import ca.uhn.fhir.rest.method.RequestDetails;
+import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
public class JpaSystemProviderDstu1 extends BaseJpaSystemProvider> {
@Transaction
public List transaction(RequestDetails theRequestDetails, @TransactionParam List theResources) {
- startRequest(theRequestDetails);
+ startRequest(((ServletRequestDetails) theRequestDetails).getServletRequest());
try {
return getDao().transaction(theRequestDetails, theResources);
} finally {
- endRequest(theRequestDetails);
+ endRequest(((ServletRequestDetails) theRequestDetails).getServletRequest());
}
}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaSystemProviderDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaSystemProviderDstu2.java
index 8d5185eb48d..cb03a014b26 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaSystemProviderDstu2.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaSystemProviderDstu2.java
@@ -28,8 +28,6 @@ import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
-import javax.servlet.http.HttpServletRequest;
-
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
@@ -50,6 +48,7 @@ import ca.uhn.fhir.rest.annotation.Transaction;
import ca.uhn.fhir.rest.annotation.TransactionParam;
import ca.uhn.fhir.rest.method.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
+import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
public class JpaSystemProviderDstu2 extends BaseJpaSystemProvider {
@@ -227,11 +226,11 @@ public class JpaSystemProviderDstu2 extends BaseJpaSystemProvider {
@Transaction
public Bundle transaction(RequestDetails theRequestDetails, @TransactionParam Bundle theResources) {
- startRequest(theRequestDetails);
+ startRequest(((ServletRequestDetails) theRequestDetails).getServletRequest());
try {
return getDao().transaction(theRequestDetails, theResources);
} finally {
- endRequest(theRequestDetails);
+ endRequest(((ServletRequestDetails) theRequestDetails).getServletRequest());
}
}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/ServletSubRequestDetails.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/ServletSubRequestDetails.java
new file mode 100644
index 00000000000..4ef0cfa7407
--- /dev/null
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/ServletSubRequestDetails.java
@@ -0,0 +1,42 @@
+package ca.uhn.fhir.jpa.provider;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
+
+public class ServletSubRequestDetails extends ServletRequestDetails {
+
+ private Map> myHeaders = new HashMap>();
+
+ @Override
+ public String getHeader(String theName) {
+ ArrayList list = myHeaders.get(theName.toLowerCase());
+ if (list == null || list.isEmpty()) {
+ return null;
+ }
+ return list.get(0);
+ }
+
+ @Override
+ public List getHeaders(String theName) {
+ ArrayList list = myHeaders.get(theName.toLowerCase());
+ if (list == null || list.isEmpty()) {
+ return null;
+ }
+ return list;
+ }
+
+ public void addHeader(String theName, String theValue) {
+ String lowerCase = theName.toLowerCase();
+ ArrayList list = myHeaders.get(lowerCase);
+ if (list == null) {
+ list = new ArrayList();
+ myHeaders.put(lowerCase, list);
+ }
+ list.add(theValue);
+ }
+
+}
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/BaseJpaDstu2SystemTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/BaseJpaDstu2SystemTest.java
index 9f199c8be15..adcc78bfac5 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/BaseJpaDstu2SystemTest.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/BaseJpaDstu2SystemTest.java
@@ -12,17 +12,17 @@ import javax.servlet.http.HttpServletRequest;
import org.junit.Before;
import ca.uhn.fhir.jpa.rp.dstu2.PatientResourceProvider;
-import ca.uhn.fhir.rest.method.RequestDetails;
import ca.uhn.fhir.rest.server.RestfulServer;
+import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
public abstract class BaseJpaDstu2SystemTest extends BaseJpaDstu2Test {
- protected RequestDetails myRequestDetails;
+ protected ServletRequestDetails myRequestDetails;
private RestfulServer myServer;
@SuppressWarnings("unchecked")
@Before
public void before() throws ServletException {
- myRequestDetails = mock(RequestDetails.class);
+ myRequestDetails = mock(ServletRequestDetails.class);
if (myServer == null) {
myServer = new RestfulServer(myFhirCtx);
diff --git a/hapi-fhir-osgi-core/pom.xml b/hapi-fhir-osgi-core/pom.xml
index fefbb2e0bbe..3bf73986c44 100644
--- a/hapi-fhir-osgi-core/pom.xml
+++ b/hapi-fhir-osgi-core/pom.xml
@@ -55,6 +55,12 @@
phloc-commons
+
+ javax.servlet
+ javax.servlet-api
+ provided
+
+
org.slf4j
diff --git a/hapi-fhir-structures-dstu/pom.xml b/hapi-fhir-structures-dstu/pom.xml
index 1d9172d5de2..877f837b56b 100644
--- a/hapi-fhir-structures-dstu/pom.xml
+++ b/hapi-fhir-structures-dstu/pom.xml
@@ -30,7 +30,6 @@
xmlunit
xmlunit
- 1.5
test
diff --git a/hapi-fhir-structures-dstu/src/main/java/ca/uhn/fhir/rest/server/Dstu1BundleFactory.java b/hapi-fhir-structures-dstu/src/main/java/ca/uhn/fhir/rest/server/Dstu1BundleFactory.java
index c751882aa47..c168b412e1e 100644
--- a/hapi-fhir-structures-dstu/src/main/java/ca/uhn/fhir/rest/server/Dstu1BundleFactory.java
+++ b/hapi-fhir-structures-dstu/src/main/java/ca/uhn/fhir/rest/server/Dstu1BundleFactory.java
@@ -197,7 +197,7 @@ public class Dstu1BundleFactory implements IVersionSpecificBundleFactory {
}
@Override
- public void initializeBundleFromBundleProvider(RestfulServer theServer, IBundleProvider theResult, EncodingEnum theResponseEncoding, String theServerBase, String theCompleteUrl, boolean thePrettyPrint, int theOffset, Integer theLimit, String theSearchId, BundleTypeEnum theBundleType, Set theIncludes) {
+ public void initializeBundleFromBundleProvider(IRestfulServer theServer, IBundleProvider theResult, EncodingEnum theResponseEncoding, String theServerBase, String theCompleteUrl, boolean thePrettyPrint, int theOffset, Integer theLimit, String theSearchId, BundleTypeEnum theBundleType, Set theIncludes) {
int numToReturn;
String searchId = null;
List resourceList;
diff --git a/hapi-fhir-structures-dstu/src/main/java/ca/uhn/fhir/rest/server/provider/ServerConformanceProvider.java b/hapi-fhir-structures-dstu/src/main/java/ca/uhn/fhir/rest/server/provider/ServerConformanceProvider.java
index 84e059a7d9e..60d60eebf36 100644
--- a/hapi-fhir-structures-dstu/src/main/java/ca/uhn/fhir/rest/server/provider/ServerConformanceProvider.java
+++ b/hapi-fhir-structures-dstu/src/main/java/ca/uhn/fhir/rest/server/provider/ServerConformanceProvider.java
@@ -1,34 +1,16 @@
package ca.uhn.fhir.rest.server.provider;
-/*
- * #%L
- * HAPI FHIR Structures - DSTU1 (FHIR v0.80)
- * %%
- * 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.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
-import java.io.IOException;
-import java.io.InputStream;
-import java.text.DateFormat;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.*;
-import java.util.jar.Manifest;
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpServletRequest;
-import ca.uhn.fhir.parser.DataFormatException;
import org.apache.commons.lang3.StringUtils;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
@@ -49,6 +31,7 @@ import ca.uhn.fhir.model.primitive.CodeDt;
import ca.uhn.fhir.model.primitive.DateTimeDt;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.StringDt;
+import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.rest.annotation.Metadata;
import ca.uhn.fhir.rest.method.BaseMethodBinding;
import ca.uhn.fhir.rest.method.DynamicSearchMethodBinding;
@@ -59,10 +42,9 @@ import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.IServerConformanceProvider;
import ca.uhn.fhir.rest.server.ResourceBinding;
import ca.uhn.fhir.rest.server.RestfulServer;
+import ca.uhn.fhir.rest.server.RestulfulServerConfiguration;
import ca.uhn.fhir.util.ExtensionConstants;
-import javax.servlet.http.HttpServletRequest;
-
/**
* Server FHIR Provider which serves the conformance statement for a RESTful server implementation
*
@@ -78,12 +60,8 @@ public class ServerConformanceProvider implements IServerConformanceProvider systemOps = new HashSet();
- List bindings = new ArrayList(myRestfulServer.getResourceBindings());
+ List bindings = new ArrayList(myServerConfiguration.getResourceBindings());
Collections.sort(bindings, new Comparator() {
@Override
public int compare(ResourceBinding theArg0, ResourceBinding theArg1) {
@@ -151,9 +133,11 @@ public class ServerConformanceProvider implements IServerConformanceProvider includes = new TreeSet();
@@ -215,7 +199,7 @@ public class ServerConformanceProvider implements IServerConformanceProvider includes, DynamicSearchMethodBinding searchMethodBinding) {
includes.addAll(searchMethodBinding.getIncludes());
@@ -348,7 +317,7 @@ public class ServerConformanceProvider implements IServerConformanceProvider