diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseAddOrDeleteTagsMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseAddOrDeleteTagsMethodBinding.java index e79b2edb99f..679438e35db 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseAddOrDeleteTagsMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseAddOrDeleteTagsMethodBinding.java @@ -45,6 +45,7 @@ import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.client.BaseHttpClientInvocation; import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.IResourceProvider; +import ca.uhn.fhir.rest.server.IRestfulServer; import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; @@ -154,8 +155,8 @@ abstract class BaseAddOrDeleteTagsMethodBinding extends BaseMethodBinding } @Override - public void invokeServer(RestfulServer theServer, RequestDetails theRequest) throws BaseServerResponseException, IOException { - Object[] params = createParametersForServerRequest(theRequest, null); + public Object invokeServer(IRestfulServer theServer, RequestDetails theRequest) throws BaseServerResponseException, IOException { + Object[] params = createParametersForServerRequest(theRequest); params[myIdParamIndex] = theRequest.getId(); @@ -164,7 +165,7 @@ abstract class BaseAddOrDeleteTagsMethodBinding extends BaseMethodBinding } IParser parser = createAppropriateParserForParsingServerRequest(theRequest); - Reader reader = theRequest.getServletRequest().getReader(); + Reader reader = theRequest.getReader(); try { TagList tagList = parser.parseTagList(reader); params[myTagListParamIndex] = tagList; @@ -175,21 +176,13 @@ abstract class BaseAddOrDeleteTagsMethodBinding extends BaseMethodBinding for (int i = theServer.getInterceptors().size() - 1; i >= 0; i--) { IServerInterceptor next = theServer.getInterceptors().get(i); - boolean continueProcessing = next.outgoingResponse(theRequest, theRequest.getServletRequest(), theRequest.getServletResponse()); + boolean continueProcessing = next.outgoingResponse(theRequest); if (!continueProcessing) { - return; + return null; } } - - HttpServletResponse response = theRequest.getServletResponse(); - response.setContentType(Constants.CT_TEXT); - response.setStatus(Constants.STATUS_HTTP_200_OK); - response.setCharacterEncoding(Constants.CHARSET_NAME_UTF8); - - theServer.addHeadersToResponse(response); - - PrintWriter writer = response.getWriter(); - writer.close(); + + return theRequest.getResponse().returnResponse(null, Constants.STATUS_HTTP_200_OK, false, null, null); } @Override diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseMethodBinding.java index 1a81d325cc5..6d5fd53815a 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseMethodBinding.java @@ -69,7 +69,7 @@ import ca.uhn.fhir.rest.server.EncodingEnum; import ca.uhn.fhir.rest.server.IBundleProvider; import ca.uhn.fhir.rest.server.IDynamicSearchResourceProvider; import ca.uhn.fhir.rest.server.IResourceProvider; -import ca.uhn.fhir.rest.server.RestfulServer; +import ca.uhn.fhir.rest.server.IRestfulServer; import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; @@ -86,10 +86,6 @@ import ca.uhn.fhir.util.ReflectionUtil; public abstract class BaseMethodBinding implements IClientResponseHandler { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseMethodBinding.class); - /** - * @see BaseMethodBinding#loadRequestContents(RequestDetails) - */ - private static volatile IRequestReader ourRequestReader; private FhirContext myContext; private Method myMethod; private List myParameters; @@ -131,7 +127,7 @@ public abstract class BaseMethodBinding implements IClientResponseHandler } protected IParser createAppropriateParserForParsingServerRequest(RequestDetails theRequest) { - String contentTypeHeader = theRequest.getServletRequest().getHeader("content-type"); + String contentTypeHeader = theRequest.getHeader(Constants.HEADER_CONTENT_TYPE); EncodingEnum encoding; if (isBlank(contentTypeHeader)) { encoding = EncodingEnum.XML; @@ -151,14 +147,14 @@ public abstract class BaseMethodBinding implements IClientResponseHandler return parser; } - protected Object[] createParametersForServerRequest(RequestDetails theRequest, byte[] theRequestContents) { + protected Object[] createParametersForServerRequest(RequestDetails theRequest) { Object[] params = new Object[getParameters().size()]; for (int i = 0; i < getParameters().size(); i++) { IParameter param = getParameters().get(i); if (param == null) { continue; } - params[i] = param.translateQueryParametersIntoServerArgument(theRequest, theRequestContents, this); + params[i] = param.translateQueryParametersIntoServerArgument(theRequest, this); } return params; } @@ -251,9 +247,9 @@ public abstract class BaseMethodBinding implements IClientResponseHandler public abstract BaseHttpClientInvocation invokeClient(Object[] theArgs) throws InternalErrorException; - public abstract void invokeServer(RestfulServer theServer, RequestDetails theRequest) throws BaseServerResponseException, IOException; + public abstract Object invokeServer(IRestfulServer theServer, RequestDetails theRequest) throws BaseServerResponseException, IOException; - protected final Object invokeServerMethod(RestfulServer theServer, RequestDetails theRequest, Object[] theMethodParams) { + protected final Object invokeServerMethod(IRestfulServer theServer, RequestDetails theRequest, Object[] theMethodParams) { // Handle server action interceptors RestOperationTypeEnum operationType = getRestOperationType(theRequest); if (operationType != null) { @@ -293,41 +289,6 @@ public abstract class BaseMethodBinding implements IClientResponseHandler return mySupportsConditionalMultiple; } - protected byte[] loadRequestContents(RequestDetails theRequest) throws IOException { - /* - * 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; - } - - InputStream inputStream = reader.getInputStream(theRequest); - byte[] requestContents = IOUtils.toByteArray(inputStream); - - theRequest.setRawRequest(requestContents); - - return requestContents; - } - /** * Subclasses may override this method (but should also call super.{@link #populateActionRequestDetailsForInterceptor(RequestDetails, ActionRequestDetails, Object[])} to provide method specifics to the * interceptors. @@ -643,17 +604,17 @@ public abstract class BaseMethodBinding implements IClientResponseHandler /** * @see BaseMethodBinding#loadRequestContents(RequestDetails) */ - static class ActiveRequestReader implements IRequestReader { + public static class ActiveRequestReader implements IRequestReader { @Override public InputStream getInputStream(RequestDetails theRequestDetails) throws IOException { - return theRequestDetails.getServletRequest().getInputStream(); + return theRequestDetails.getInputStream(); } } /** * @see BaseMethodBinding#loadRequestContents(RequestDetails) */ - static class InactiveRequestReader implements IRequestReader { + public static class InactiveRequestReader implements IRequestReader { @Override public InputStream getInputStream(RequestDetails theRequestDetails) { throw new IllegalStateException("The servlet-api JAR is not found on the classpath. Please check that this library is available."); @@ -663,7 +624,7 @@ public abstract class BaseMethodBinding implements IClientResponseHandler /** * @see BaseMethodBinding#loadRequestContents(RequestDetails) */ - private static interface IRequestReader { + public static interface IRequestReader { InputStream getInputStream(RequestDetails theRequestDetails) throws IOException; } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseOutcomeReturningMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseOutcomeReturningMethodBinding.java index 7d9f021ed63..37316520fa4 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseOutcomeReturningMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseOutcomeReturningMethodBinding.java @@ -24,6 +24,7 @@ import java.io.IOException; import java.io.Reader; import java.io.Writer; import java.lang.reflect.Method; +import java.util.EnumSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -41,9 +42,11 @@ import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.PreferReturnEnum; import ca.uhn.fhir.rest.api.RequestTypeEnum; +import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.client.BaseHttpClientInvocation; import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.EncodingEnum; +import ca.uhn.fhir.rest.server.IRestfulServer; import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.server.RestfulServerUtils; import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; @@ -68,24 +71,6 @@ abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding theServer, RequestDetails theRequest) throws BaseServerResponseException, IOException { // if (requestContainsResource()) { // requestContents = parseIncomingServerResource(theRequest); // } else { // requestContents = null; // } - Object[] params = createParametersForServerRequest(theRequest, requestContents); + Object[] params = createParametersForServerRequest(theRequest); addParametersForServerRequest(theRequest, params); - HttpServletResponse servletResponse = theRequest.getServletResponse(); /* * No need to catch nd handle exceptions here, we already handle them one level up @@ -182,13 +164,16 @@ abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding= 0; i--) { IServerInterceptor next = theServer.getInterceptors().get(i); - boolean continueProcessing = next.outgoingResponse(theRequest, outcome, theRequest.getServletRequest(), theRequest.getServletResponse()); + boolean continueProcessing = next.outgoingResponse(theRequest, outcome); if (!continueProcessing) { - return; + return null; } } - boolean allowPrefer = false; + return returnResponse(theRequest, response, outcome, resource); + } + + private int getOperationStatus(MethodOutcome response) { switch (getRestOperationType()) { case CREATE: if (response == null) { @@ -196,23 +181,17 @@ abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding theServer, RequestDetails theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException; @Override - public void invokeServer(RestfulServer theServer, RequestDetails theRequest) throws BaseServerResponseException, IOException { + public Object invokeServer(IRestfulServer theServer, RequestDetails theRequest) throws BaseServerResponseException, IOException { // Pretty print boolean prettyPrint = RestfulServerUtils.prettyPrintResponse(theServer, theRequest); @@ -245,38 +243,36 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding summaryMode = RestfulServerUtils.determineSummaryMode(theRequest); // Determine response encoding - EncodingEnum responseEncoding = RestfulServerUtils.determineResponseEncodingNoDefault(theRequest.getServletRequest(), theServer.getDefaultResponseEncoding()); + EncodingEnum responseEncoding = RestfulServerUtils.determineResponseEncodingNoDefault(theRequest, theServer.getDefaultResponseEncoding()); // Is this request coming from a browser - String uaHeader = theRequest.getServletRequest().getHeader("user-agent"); + String uaHeader = theRequest.getHeader("user-agent"); boolean requestIsBrowser = false; if (uaHeader != null && uaHeader.contains("Mozilla")) { requestIsBrowser = true; } - byte[] requestContents = loadRequestContents(theRequest); - // Method params Object[] params = new Object[getParameters().size()]; for (int i = 0; i < getParameters().size(); i++) { IParameter param = getParameters().get(i); if (param != null) { - params[i] = param.translateQueryParametersIntoServerArgument(theRequest, requestContents, this); + params[i] = param.translateQueryParametersIntoServerArgument(theRequest, this); } } Object resultObj = invokeServer(theServer, theRequest, params); - Integer count = RestfulServerUtils.extractCountParameter(theRequest.getServletRequest()); + Integer count = RestfulServerUtils.extractCountParameter(theRequest); boolean respondGzip = theRequest.isRespondGzip(); - HttpServletResponse response = theRequest.getServletResponse(); + switch (getReturnType()) { case BUNDLE: { /* * Figure out the self-link for this request */ - String serverBase = theServer.getServerBaseForRequest(theRequest.getServletRequest()); + String serverBase = theRequest.getServerBaseForRequest(); String linkSelf; StringBuilder b = new StringBuilder(); b.append(serverBase); @@ -325,16 +321,15 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding= 0; i--) { IServerInterceptor next = theServer.getInterceptors().get(i); - boolean continueProcessing = next.outgoingResponse(theRequest, resource, theRequest.getServletRequest(), theRequest.getServletResponse()); + boolean continueProcessing = next.outgoingResponse(theRequest, resource); if (!continueProcessing) { ourLog.debug("Interceptor {} returned false, not continuing processing"); - return; + return null; } } - RestfulServerUtils.streamResponseAsResource(theServer, response, resource, prettyPrint, summaryMode, Constants.STATUS_HTTP_200_OK, respondGzip, - isAddContentLocationHeader(), theRequest); - break; + return theRequest.getResponse().streamResponseAsResource(resource, prettyPrint, summaryMode, Constants.STATUS_HTTP_200_OK, respondGzip, + isAddContentLocationHeader()); } else { Set includes = getRequestIncludesFromParams(params); @@ -350,28 +345,26 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding= 0; i--) { IServerInterceptor next = theServer.getInterceptors().get(i); - boolean continueProcessing = next.outgoingResponse(theRequest, bundle, theRequest.getServletRequest(), theRequest.getServletResponse()); + boolean continueProcessing = next.outgoingResponse(theRequest, bundle); if (!continueProcessing) { ourLog.debug("Interceptor {} returned false, not continuing processing"); - return; + return null; } } - RestfulServerUtils.streamResponseAsBundle(theServer, response, bundle, theRequest.getFhirServerBase(), summaryMode, respondGzip, requestIsBrowser, theRequest); + return theRequest.getResponse().streamResponseAsBundle(bundle, summaryMode, respondGzip, requestIsBrowser); } else { IBaseResource resBundle = bundleFactory.getResourceBundle(); for (int i = theServer.getInterceptors().size() - 1; i >= 0; i--) { IServerInterceptor next = theServer.getInterceptors().get(i); - boolean continueProcessing = next.outgoingResponse(theRequest, resBundle, theRequest.getServletRequest(), theRequest.getServletResponse()); + boolean continueProcessing = next.outgoingResponse(theRequest, resBundle); if (!continueProcessing) { ourLog.debug("Interceptor {} returned false, not continuing processing"); - return; + return null; } } - RestfulServerUtils.streamResponseAsResource(theServer, response, resBundle, prettyPrint, summaryMode, - Constants.STATUS_HTTP_200_OK, theRequest.isRespondGzip(), isAddContentLocationHeader(), theRequest); + return theRequest.getResponse().streamResponseAsResource(resBundle, prettyPrint, summaryMode, Constants.STATUS_HTTP_200_OK, + theRequest.isRespondGzip(), isAddContentLocationHeader()); } - - break; } } case RESOURCE: { @@ -386,17 +379,16 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding= 0; i--) { IServerInterceptor next = theServer.getInterceptors().get(i); - boolean continueProcessing = next.outgoingResponse(theRequest, resource, theRequest.getServletRequest(), theRequest.getServletResponse()); + boolean continueProcessing = next.outgoingResponse(theRequest, resource); if (!continueProcessing) { - return; + return null; } } - RestfulServerUtils.streamResponseAsResource(theServer, response, resource, prettyPrint, summaryMode, Constants.STATUS_HTTP_200_OK, respondGzip, - isAddContentLocationHeader(), theRequest); - break; + return theRequest.getResponse().streamResponseAsResource(resource, prettyPrint, summaryMode, Constants.STATUS_HTTP_200_OK, respondGzip, isAddContentLocationHeader()); } } + return null; } /** diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ConditionalParamBinder.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ConditionalParamBinder.java index b97b05436ac..bcb77d9f9ca 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ConditionalParamBinder.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ConditionalParamBinder.java @@ -61,10 +61,10 @@ class ConditionalParamBinder 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 (myOperationType == RestOperationTypeEnum.CREATE) { - String retVal = theRequest.getServletRequest().getHeader(Constants.HEADER_IF_NONE_EXIST); + String retVal = theRequest.getHeader(Constants.HEADER_IF_NONE_EXIST); if (isBlank(retVal)) { return null; } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ConformanceMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ConformanceMethodBinding.java index 3e751b41c6a..c6ecbcbcf1f 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ConformanceMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ConformanceMethodBinding.java @@ -31,6 +31,7 @@ import ca.uhn.fhir.model.valueset.BundleTypeEnum; import ca.uhn.fhir.rest.api.RequestTypeEnum; import ca.uhn.fhir.rest.api.RestOperationTypeEnum; 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.SimpleBundleProvider; import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; @@ -72,7 +73,7 @@ public class ConformanceMethodBinding extends BaseResourceReturningMethodBinding } @Override - public IBundleProvider invokeServer(RestfulServer theServer, RequestDetails theRequest, Object[] theMethodParams) throws BaseServerResponseException { + public IBundleProvider invokeServer(IRestfulServer theServer, RequestDetails theRequest, Object[] theMethodParams) throws BaseServerResponseException { IBaseResource conf = (IBaseResource) invokeServerMethod(theServer, theRequest, theMethodParams); return new SimpleBundleProvider(conf); } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/CountParameter.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/CountParameter.java index 87003347a4e..6ce2ea39276 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/CountParameter.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/CountParameter.java @@ -54,7 +54,7 @@ public class CountParameter 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_COUNT); if (sinceParams != null) { if (sinceParams.length > 0) { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/DynamicSearchMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/DynamicSearchMethodBinding.java index 00acc18a617..8b4296fbf41 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/DynamicSearchMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/DynamicSearchMethodBinding.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.IBundleProvider; import ca.uhn.fhir.rest.server.IDynamicSearchResourceProvider; -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; @@ -86,7 +86,7 @@ public class DynamicSearchMethodBinding extends BaseResourceReturningMethodBindi } @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/DynamicSearchParameter.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/DynamicSearchParameter.java index bafd18ff861..ad89e934285 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/DynamicSearchParameter.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/DynamicSearchParameter.java @@ -67,7 +67,7 @@ public class DynamicSearchParameter implements IParameter { @SuppressWarnings("unchecked") @Override - public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, byte[] theRequestContents, BaseMethodBinding theMethodBinding) throws InternalErrorException, InvalidRequestException { + public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, BaseMethodBinding theMethodBinding) throws InternalErrorException, InvalidRequestException { SearchParameterMap retVal = new SearchParameterMap(); for (String next : theRequest.getParameters().keySet()) { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ElementsParameter.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ElementsParameter.java index 62768163752..d08f12630cc 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ElementsParameter.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ElementsParameter.java @@ -71,7 +71,7 @@ public class ElementsParameter 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 = getElementsValueOrNull(theRequest); if (value == null || value.isEmpty()) { return null; diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/GetTagsMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/GetTagsMethodBinding.java index 30e52674fb4..47d44af4794 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/GetTagsMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/GetTagsMethodBinding.java @@ -21,15 +21,12 @@ package ca.uhn.fhir.rest.method; */ import java.io.IOException; -import java.io.PrintWriter; import java.io.Reader; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.List; import java.util.Map; -import javax.servlet.http.HttpServletResponse; - import org.hl7.fhir.instance.model.api.IBaseResource; import ca.uhn.fhir.context.ConfigurationException; @@ -44,10 +41,8 @@ import ca.uhn.fhir.rest.api.RequestTypeEnum; import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.client.BaseHttpClientInvocation; import ca.uhn.fhir.rest.server.Constants; -import ca.uhn.fhir.rest.server.EncodingEnum; import ca.uhn.fhir.rest.server.IResourceProvider; -import ca.uhn.fhir.rest.server.RestfulServer; -import ca.uhn.fhir.rest.server.RestfulServerUtils; +import ca.uhn.fhir.rest.server.IRestfulServer; import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; @@ -168,8 +163,8 @@ public class GetTagsMethodBinding extends BaseMethodBinding { } @Override - public void invokeServer(RestfulServer theServer, RequestDetails theRequest) throws BaseServerResponseException, IOException { - Object[] params = createParametersForServerRequest(theRequest, null); + public Object invokeServer(IRestfulServer theServer, RequestDetails theRequest) throws BaseServerResponseException, IOException { + Object[] params = createParametersForServerRequest(theRequest); if (myIdParamIndex != null) { params[myIdParamIndex] = theRequest.getId(); @@ -182,29 +177,13 @@ public class GetTagsMethodBinding extends BaseMethodBinding { for (int i = theServer.getInterceptors().size() - 1; i >= 0; i--) { IServerInterceptor next = theServer.getInterceptors().get(i); - boolean continueProcessing = next.outgoingResponse(theRequest, resp, theRequest.getServletRequest(), theRequest.getServletResponse()); + boolean continueProcessing = next.outgoingResponse(theRequest, resp); if (!continueProcessing) { - return; + return null; } } - EncodingEnum responseEncoding = RestfulServerUtils.determineResponseEncodingWithDefault(theServer, theRequest.getServletRequest()); - - HttpServletResponse response = theRequest.getServletResponse(); - response.setContentType(responseEncoding.getResourceContentType()); - response.setStatus(Constants.STATUS_HTTP_200_OK); - response.setCharacterEncoding(Constants.CHARSET_NAME_UTF8); - - theServer.addHeadersToResponse(response); - - IParser parser = responseEncoding.newParser(getContext()); - parser.setPrettyPrint(RestfulServerUtils.prettyPrintResponse(theServer, theRequest)); - PrintWriter writer = response.getWriter(); - try { - parser.encodeTagListToWriter(resp, writer); - } finally { - writer.close(); - } + return theRequest.getResponse().returnResponse(ParseAction.create(resp), Constants.STATUS_HTTP_200_OK, false, null, null); } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HistoryMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HistoryMethodBinding.java index f5a2a0330b9..fb83132e0d3 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HistoryMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HistoryMethodBinding.java @@ -26,7 +26,6 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.List; -import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.hl7.fhir.instance.model.api.IBaseResource; @@ -43,7 +42,7 @@ import ca.uhn.fhir.rest.client.BaseHttpClientInvocation; import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.IBundleProvider; import ca.uhn.fhir.rest.server.IResourceProvider; -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; @@ -155,7 +154,7 @@ public class HistoryMethodBinding 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/IParameter.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/IParameter.java index 51bd0febf5d..c98baad5171 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/IParameter.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/IParameter.java @@ -48,7 +48,7 @@ public interface IParameter { * @param theMethodBinding TODO * @return Returns the argument object as it will be passed to the {@link IResourceProvider} method. */ - Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, byte[] theRequestContents, BaseMethodBinding theMethodBinding) throws InternalErrorException, InvalidRequestException; + Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, BaseMethodBinding theMethodBinding) throws InternalErrorException, InvalidRequestException; void initializeTypes(Method theMethod, Class> theOuterCollectionType, Class> theInnerCollectionType, Class theParameterType); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/NullParameter.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/NullParameter.java index 428bbd27f62..b115c43546d 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/NullParameter.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/NullParameter.java @@ -39,7 +39,7 @@ class NullParameter 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 { // nothing return null; } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/OperationMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/OperationMethodBinding.java index 153c1cead64..5c827dc8234 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/OperationMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/OperationMethodBinding.java @@ -49,7 +49,7 @@ import ca.uhn.fhir.rest.api.RequestTypeEnum; import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.client.BaseHttpClientInvocation; 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.BaseServerResponseException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException; @@ -223,7 +223,7 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding { } @Override - public Object invokeServer(RestfulServer theServer, RequestDetails theRequest, Object[] theMethodParams) throws BaseServerResponseException { + public Object invokeServer(IRestfulServer theServer, RequestDetails theRequest, Object[] theMethodParams) throws BaseServerResponseException { if (theRequest.getRequestType() == RequestTypeEnum.POST) { // always ok } else if (theRequest.getRequestType() == RequestTypeEnum.GET) { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/OperationParameter.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/OperationParameter.java index b89132dc997..901c7e6110c 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/OperationParameter.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/OperationParameter.java @@ -24,7 +24,6 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collection; -import java.util.Date; import java.util.List; import java.util.Map; @@ -156,7 +155,7 @@ public class OperationParameter implements IParameter { @SuppressWarnings("unchecked") @Override - public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, byte[] theRequestContents, BaseMethodBinding theMethodBinding) throws InternalErrorException, InvalidRequestException { + public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, BaseMethodBinding theMethodBinding) throws InternalErrorException, InvalidRequestException { List matchingParamValues = new ArrayList(); if (theRequest.getRequestType() == RequestTypeEnum.GET) { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ParseAction.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ParseAction.java new file mode 100644 index 00000000000..89d59b848bc --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ParseAction.java @@ -0,0 +1,44 @@ +package ca.uhn.fhir.rest.method; + +import java.io.IOException; +import java.io.Writer; + +import org.hl7.fhir.instance.model.api.IBaseResource; + +import ca.uhn.fhir.model.api.TagList; +import ca.uhn.fhir.parser.IParser; + +/** + * @author Peter Van Houte + * + * @param A functional class that parses an outcome + */ +public abstract class ParseAction { + + protected T theOutcome; + + protected ParseAction(T outcome) { + this.theOutcome = outcome; + } + + public abstract void execute(IParser parser, Writer writer) throws IOException; + + public static ParseAction create(TagList outcome) { + return outcome == null ? null : new ParseAction(outcome) { + @Override + public void execute(IParser theParser, Writer theWriter) throws IOException { + theParser.encodeTagListToWriter(this.theOutcome, theWriter); + } + }; + } + + public static ParseAction create(IBaseResource outcome) { + return outcome == null ? null : new ParseAction(outcome) { + @Override + public void execute(IParser theParser, Writer theWriter) throws IOException { + theParser.encodeResourceToWriter(this.theOutcome, theWriter); + } + }; + } + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ReadMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ReadMethodBinding.java index 4805c990855..e490a305b2d 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ReadMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ReadMethodBinding.java @@ -49,7 +49,7 @@ import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.ETagSupportEnum; 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.SimpleBundleProvider; import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; @@ -204,7 +204,7 @@ public class ReadMethodBinding extends BaseResourceReturningMethodBinding implem } @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 { theMethodParams[myIdIndex] = MethodUtil.convertIdToType(theRequest.getId(), myIdParameterType); if (myVersionIdIndex != null) { theMethodParams[myVersionIdIndex] = new IdDt(theRequest.getId().getVersionIdPart()); @@ -214,7 +214,7 @@ public class ReadMethodBinding extends BaseResourceReturningMethodBinding implem IBundleProvider retVal = toResourceList(response); if (theRequest.getServer().getETagSupport() == ETagSupportEnum.ENABLED) { - String ifNoneMatch = theRequest.getServletRequest().getHeader(Constants.HEADER_IF_NONE_MATCH_LC); + String ifNoneMatch = theRequest.getHeader(Constants.HEADER_IF_NONE_MATCH_LC); if (retVal.size() == 1 && StringUtils.isNotBlank(ifNoneMatch)) { List responseResources = retVal.getResources(0, 1); IBaseResource responseResource = responseResources.get(0); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/RequestDetails.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/RequestDetails.java index c26ede12486..79835e79967 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/RequestDetails.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/RequestDetails.java @@ -1,5 +1,9 @@ package ca.uhn.fhir.rest.method; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; + /* * #%L * HAPI FHIR - Core Library @@ -25,36 +29,31 @@ import java.util.Collections; 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 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.server.RestfulServer; +import ca.uhn.fhir.rest.server.IRestfulResponse; +import ca.uhn.fhir.rest.server.IRestfulServerDefaults; -public class RequestDetails { +public abstract class RequestDetails { + private byte[] myRequestContents; private String myCompartmentName; private String myCompleteUrl; private String myFhirServerBase; private IdDt myId; private String myOperation; private Map myParameters; - private byte[] myRawRequest; private String myRequestPath; private RequestTypeEnum myRequestType; private String myResourceName; private boolean myRespondGzip; private RestOperationTypeEnum myRestOperationType; private String mySecondaryOperation; - private RestfulServer myServer; - private HttpServletRequest myServletRequest; - private HttpServletResponse myServletResponse; private Map> myUnqualifiedToQualifiedNames; - + private IRestfulResponse myResponse; + public String getCompartmentName() { return myCompartmentName; } @@ -79,10 +78,6 @@ public class RequestDetails { return myParameters; } - public byte[] getRawRequest() { - return myRawRequest; - } - /** * The part of the request URL that comes after the server base. *

@@ -109,17 +104,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; @@ -177,10 +162,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; @@ -206,28 +187,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; } + protected abstract byte[] getByteStreamRequestContents(); + + 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/SearchMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SearchMethodBinding.java index a4ce070aa0c..20c4f6b7159 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 @@ -48,7 +48,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; @@ -283,7 +283,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 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 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/Dstu1BundleFactory.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/Dstu1BundleFactory.java index 15c16ae9514..e81147c2e8d 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/Dstu1BundleFactory.java +++ b/hapi-fhir-base/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-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 index 31156e72c7a..da77bd1c9c2 100644 --- 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 @@ -2,7 +2,7 @@ package ca.uhn.fhir.rest.server; import java.util.List; -import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.rest.method.RequestDetails; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; /* @@ -24,14 +24,12 @@ import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; * limitations under the License. * #L% */ -public interface IRestfulServer { +public interface IRestfulServer extends 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. - */ - public FhirContext getFhirContext(); - - public List getInterceptors(); + 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 index 7b5e65d033c..73a0479b72d 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/IRestfulServerDefaults.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/IRestfulServerDefaults.java @@ -1,16 +1,46 @@ package ca.uhn.fhir.rest.server; +import ca.uhn.fhir.context.FhirContext; + public interface IRestfulServerDefaults { - /** - * 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 - */ - public boolean isDefaultPrettyPrint(); + /** + * 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 #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 myResourceType); + + Object getRequestResource(RequestDetails theRequest, ParamStyle myParamStyle, Class myResourceBundleType); + + T loadResourceFromRequest(RequestDetails theRequest, BaseMethodBinding theMethodBinding, Class theResourceType); + + IBaseResource parseResourceFromRequest(RequestDetails theRequest, BaseMethodBinding theMethodBinding, Class 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/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 6a42349da5e..69e88dbce41 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 @@ -23,6 +23,8 @@ 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; @@ -39,12 +41,14 @@ 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.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; @@ -56,14 +60,17 @@ 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.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.ParseAction; import ca.uhn.fhir.rest.method.RequestDetails; import ca.uhn.fhir.rest.server.exceptions.AuthenticationException; import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; @@ -71,11 +78,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 implements IRestfulServerDefaults, IRestfulServer { +public class RestfulServer extends HttpServlet implements IRestfulServer { private static final ExceptionHandlingInterceptor DEFAULT_EXCEPTION_HANDLER = new ExceptionHandlingInterceptor(); private static final long serialVersionUID = 1L; @@ -316,15 +324,12 @@ public class RestfulServer extends HttpServlet implements IRestfulServerDefaults 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; } @@ -337,9 +342,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServerDefaults return myDefaultResponseEncoding; } - /** - * Returns the server support for ETags (will not be null). Default is {@link #DEFAULT_ETAG_SUPPORT} - */ + @Override public ETagSupportEnum getETagSupport() { return myETagSupport; } @@ -411,7 +414,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServerDefaults public IServerAddressStrategy getServerAddressStrategy() { return myServerAddressStrategy; } - + /** * Returns the server base URL (with no trailing '/') for a given request */ @@ -463,7 +466,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServerDefaults return myServerVersion; } - private void handlePagingRequest(RequestDetails theRequest, HttpServletResponse theResponse, String thePagingAction) throws IOException { + private void handlePagingRequest(ServletRequestDetails theRequest, HttpServletResponse theResponse, String thePagingAction) throws IOException { IBundleProvider resultList = getPagingProvider().retrieveResultList(thePagingAction); if (resultList == null) { ourLog.info("Client requested unknown paging ID[{}]", thePagingAction); @@ -476,21 +479,21 @@ public class RestfulServer extends HttpServlet implements IRestfulServerDefaults return; } - Integer count = RestfulServerUtils.extractCountParameter(theRequest.getServletRequest()); + Integer count = RestfulServerUtils.extractCountParameter(theRequest); if (count == null) { count = getPagingProvider().getDefaultPageSize(); } else if (count > getPagingProvider().getMaximumPageSize()) { count = getPagingProvider().getMaximumPageSize(); } - Integer offsetI = RestfulServerUtils.tryToExtractNamedParameter(theRequest.getServletRequest(), Constants.PARAM_PAGINGOFFSET); + 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()); + EncodingEnum responseEncoding = RestfulServerUtils.determineResponseEncodingNoDefault(theRequest, getDefaultResponseEncoding()); boolean prettyPrint = RestfulServerUtils.prettyPrintResponse(this, theRequest); boolean requestIsBrowser = requestIsBrowser(theRequest.getServletRequest()); Set summaryMode = RestfulServerUtils.determineSummaryMode(theRequest); @@ -506,7 +509,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServerDefaults } } - String linkSelfBase = getServerAddressStrategy().determineServerBase(getServletContext(), theRequest.getServletRequest()); + String linkSelfBase = myServerAddressStrategy.determineServerBase(getServletContext(), theRequest.getServletRequest()); String linkSelf = linkSelfBase + theRequest.getCompleteUrl().substring(theRequest.getCompleteUrl().indexOf('?')); bundleFactory.initializeBundleFromBundleProvider(this, resultList, responseEncoding, theRequest.getFhirServerBase(), linkSelf, prettyPrint, start, count, thePagingAction, null, includes); @@ -521,7 +524,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServerDefaults return; } } - RestfulServerUtils.streamResponseAsBundle(this, theResponse, bundle, theRequest.getFhirServerBase(), summaryMode, respondGzip, requestIsBrowser, theRequest); + theRequest.getResponse().streamResponseAsBundle(bundle, summaryMode, respondGzip, requestIsBrowser); } else { IBaseResource resBundle = bundleFactory.getResourceBundle(); for (int i = getInterceptors().size() - 1; i >= 0; i--) { @@ -532,14 +535,14 @@ public class RestfulServer extends HttpServlet implements IRestfulServerDefaults return; } } - RestfulServerUtils.streamResponseAsResource(this, theResponse, resBundle, prettyPrint, summaryMode, Constants.STATUS_HTTP_200_OK, theRequest.isRespondGzip(), false, theRequest); + theRequest.getResponse().streamResponseAsResource(resBundle, prettyPrint, summaryMode, Constants.STATUS_HTTP_200_OK, theRequest.isRespondGzip(), false); } } 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); @@ -1338,4 +1341,86 @@ public class RestfulServer extends HttpServlet implements IRestfulServerDefaults 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/RestfulServerUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtil.java new file mode 100644 index 00000000000..d86c5ab8a3f --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtil.java @@ -0,0 +1,262 @@ +package ca.uhn.fhir.rest.server; + +import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.apache.commons.lang3.StringUtils.isNotBlank; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Enumeration; + +import org.apache.commons.io.IOUtils; +import org.apache.http.entity.ContentType; +import org.hl7.fhir.instance.model.api.IBaseBinary; +import org.hl7.fhir.instance.model.api.IBaseResource; + +import ca.uhn.fhir.context.ConfigurationException; +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.FhirVersionEnum; +import ca.uhn.fhir.model.api.Bundle; +import ca.uhn.fhir.model.api.BundleEntry; +import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; +import ca.uhn.fhir.model.api.TagList; +import ca.uhn.fhir.parser.IParser; +import ca.uhn.fhir.rest.api.RestOperationTypeEnum; +import ca.uhn.fhir.rest.method.BaseMethodBinding; +import ca.uhn.fhir.rest.method.MethodUtil; +import ca.uhn.fhir.rest.method.RequestDetails; +import ca.uhn.fhir.rest.method.BaseMethodBinding.IRequestReader; +import ca.uhn.fhir.rest.param.ResourceParameter; +import ca.uhn.fhir.rest.param.ResourceParameter.Mode; +import ca.uhn.fhir.rest.param.TransactionParameter.ParamStyle; +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; + +public class RestfulServerUtil implements IRestfulServerUtil { + + /** + * @see BaseMethodBinding#loadRequestContents(RequestDetails) + */ + private static volatile IRequestReader ourRequestReader; + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestfulServerUtil.class); + private byte[] requestContents; + + @Override + public Object getResourceParameter(RequestDetails theRequest, Mode myMode, BaseMethodBinding theMethodBinding, + Class myResourceType) { + 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); + } + } + + + protected byte[] loadRequestContents(RequestDetails theRequest) { + if(requestContents != null) { + return requestContents; + } + /* + * 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(theRequest); + 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())); + } + + + } + + + public Reader createRequestReader(RequestDetails theRequest, Charset charset) { + Reader requestReader = new InputStreamReader(new ByteArrayInputStream(loadRequestContents(theRequest)), charset); + return requestReader; + } + + public Reader createRequestReader(RequestDetails theRequest) throws IOException { + return createRequestReader(theRequest, determineRequestCharset(theRequest)); + } + + public static Charset determineRequestCharset(RequestDetails theRequest) { + String ct = theRequest.getServletRequest().getHeader(Constants.HEADER_CONTENT_TYPE); + + Charset charset = null; + if (isNotBlank(ct)) { + ContentType parsedCt = ContentType.parse(ct); + charset = parsedCt.getCharset(); + } + if (charset == null) { + charset = Charset.forName("UTF-8"); + } + return charset; + } + + @SuppressWarnings("unchecked") + @Override + public T loadResourceFromRequest(RequestDetails theRequest, BaseMethodBinding theMethodBinding, Class theResourceType) { + FhirContext ctx = theRequest.getServer().getFhirContext(); + + final Charset charset = determineRequestCharset(theRequest); + 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); + if (ctValue != null) { + if (ctValue.startsWith("application/x-www-form-urlencoded")) { + String msg = theRequest.getServer().getFhirContext().getLocalizer().getMessage(ResourceParameter.class, "invalidContentTypeInRequest", ctValue, theMethodBinding.getRestOperationType()); + throw new InvalidRequestException(msg); + } + } + if (isBlank(ctValue)) { + /* + * If the client didn't send a content type, try to guess + */ + String body; + try { + body = IOUtils.toString(requestReader); + } catch (IOException e) { + // This shouldn't happen since we're reading from a byte array.. + throw new InternalErrorException(e); + } + encoding = MethodUtil.detectEncodingNoDefault(body); + if (encoding == null) { + String msg = ctx.getLocalizer().getMessage(ResourceParameter.class, "noContentTypeInRequest", restOperationType); + throw new InvalidRequestException(msg); + } else { + requestReader = new InputStreamReader(new ByteArrayInputStream(loadRequestContents(theRequest)), charset); + } + } else { + String msg = ctx.getLocalizer().getMessage(ResourceParameter.class, "invalidContentTypeInRequest", ctValue, restOperationType); + throw new InvalidRequestException(msg); + } + } + + IParser parser = encoding.newParser(ctx); + + T retVal; + if (theResourceType != null) { + retVal = parser.parseResource(theResourceType, requestReader); + } else { + retVal = (T) parser.parseResource(requestReader); + } + + if (theRequest.getId() != null && theRequest.getId().hasIdPart()) { + retVal.setId(theRequest.getId()); + } + + 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(); + MethodUtil.parseTagValue(tagList, nextTagComplete); + } + if (tagList.isEmpty() == false) { + ((IResource) retVal).getResourceMetadata().put(ResourceMetadataKeyEnum.TAG_LIST, tagList); + } + } + return retVal; + } + + @Override + public IBaseResource parseResourceFromRequest(RequestDetails theRequest, BaseMethodBinding theMethodBinding, Class theResourceType) { + IBaseResource retVal; + if (IBaseBinary.class.isAssignableFrom(theResourceType)) { + FhirContext ctx = theRequest.getServer().getFhirContext(); + String ct = theRequest.getServletRequest().getHeader(Constants.HEADER_CONTENT_TYPE); + IBaseBinary binary = (IBaseBinary) ctx.getResourceDefinition("Binary").newInstance(); + binary.setContentType(ct); + binary.setContent(loadRequestContents(theRequest)); + + retVal = binary; + } else { + retVal = loadResourceFromRequest(theRequest, theMethodBinding, theResourceType); + } + return retVal; + } + + + @Override + public Object getRequestResource(RequestDetails theRequest, ParamStyle myParamStyle, Class myResourceBundleType) { + // TODO: don't use a default encoding, just fail! + EncodingEnum encoding = RestfulServerUtils.determineRequestEncoding(theRequest); + + IParser parser = encoding.newParser(theRequest.getServer().getFhirContext()); + + Reader reader; + try { + reader = 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())); + } + + 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()); + } + } + 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/RestfulServerUtils.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java index f612bbb640c..92694476b96 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; @@ -32,17 +31,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; @@ -189,10 +186,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(';'); @@ -218,8 +215,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); @@ -234,12 +231,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(); @@ -281,7 +277,7 @@ public class RestfulServerUtils { int equalsIndex = nextQualifier.indexOf('='); if (equalsIndex != -1) { String nextQualifierKey = nextQualifier.substring(0, equalsIndex).trim(); - String nextQualifierValue = nextQualifier.substring(equalsIndex+1, nextQualifier.length()).trim(); + String nextQualifierValue = nextQualifier.substring(equalsIndex + 1, nextQualifier.length()).trim(); if (nextQualifierKey.equals("q")) { try { q = Float.parseFloat(nextQualifierValue); @@ -350,10 +346,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; } @@ -394,14 +390,14 @@ public class RestfulServerUtils { return retVal; } - public static Integer extractCountParameter(HttpServletRequest theRequest) { + public static Integer extractCountParameter(RequestDetails theRequest) { 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: @@ -418,17 +414,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(); @@ -450,7 +435,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); } } @@ -522,10 +508,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; } @@ -535,54 +520,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() + '"'); } } @@ -593,26 +583,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 @@ -630,38 +613,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()); @@ -669,17 +650,19 @@ 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) { - String countString = theRequest.getParameter(name); + static Integer tryToExtractNamedParameter(RequestDetails theRequest, String name) { + String[] countString = theRequest.getParameters().get(name); Integer count = null; - if (isNotBlank(countString)) { + if (countString != null && countString.length > 0 && isNotBlank(countString[0])) { try { - count = Integer.parseInt(countString); + count = Integer.parseInt(countString[0]); } catch (NumberFormatException e) { ourLog.debug("Failed to parse _count value '{}': {}", countString, e); } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestulfulServerConfiguration.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestulfulServerConfiguration.java index 432f57cf06a..195a77b62fe 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestulfulServerConfiguration.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestulfulServerConfiguration.java @@ -1,67 +1,27 @@ package ca.uhn.fhir.rest.server; -import java.io.IOException; -import java.io.InputStream; -import java.util.LinkedList; +import java.util.Collection; import java.util.List; -import java.util.jar.Manifest; - -import javax.servlet.ServletContext; -import javax.servlet.http.HttpServletRequest; - -import org.apache.commons.io.IOUtils; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.rest.method.BaseMethodBinding; public class RestulfulServerConfiguration { - private List resourceBindings; + private Collection resourceBindings; private List> serverBindings; private String implementationDescription; private String serverVersion; private String serverName; private FhirContext fhirContext; - private ServletContext servletContext; private IServerAddressStrategy serverAddressStrategy; private String conformanceDate; - public RestulfulServerConfiguration() { - } - - public RestulfulServerConfiguration(RestfulServer theRestfulServer) { - this.resourceBindings = new LinkedList(theRestfulServer.getResourceBindings()); - this.serverBindings = theRestfulServer.getServerBindings(); - this.implementationDescription = theRestfulServer.getImplementationDescription(); - this.serverVersion = theRestfulServer.getServerVersion(); - this.serverName = theRestfulServer.getServerName(); - this.fhirContext = theRestfulServer.getFhirContext(); - this.serverAddressStrategy= theRestfulServer.getServerAddressStrategy(); - this.servletContext = theRestfulServer.getServletContext(); - if (servletContext != null) { - InputStream inputStream = null; - try { - inputStream = getServletContext().getResourceAsStream("/META-INF/MANIFEST.MF"); - if (inputStream != null) { - Manifest manifest = new Manifest(inputStream); - this.conformanceDate = manifest.getMainAttributes().getValue("Build-Time"); - } - } catch (IOException e) { - // fall through - } - finally { - if (inputStream != null) { - IOUtils.closeQuietly(inputStream); - } - } - } - } - /** * Get the resourceBindings * @return the resourceBindings */ - public List getResourceBindings() { + public Collection getResourceBindings() { return resourceBindings; } @@ -69,7 +29,7 @@ public class RestulfulServerConfiguration { * Set the resourceBindings * @param resourceBindings the resourceBindings to set */ - public RestulfulServerConfiguration setResourceBindings(List resourceBindings) { + public RestulfulServerConfiguration setResourceBindings(Collection resourceBindings) { this.resourceBindings = resourceBindings; return this; } @@ -142,23 +102,6 @@ public class RestulfulServerConfiguration { return this; } - /** - * Get the servletContext - * @return the servletContext - */ - public ServletContext getServletContext() { - return servletContext; - } - - /** - * Set the servletContext - * @param servletContext the servletContext to set - */ - public RestulfulServerConfiguration setServletContext(ServletContext servletContext) { - this.servletContext = servletContext; - 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. @@ -209,7 +152,4 @@ public class RestulfulServerConfiguration { this.conformanceDate = conformanceDate; } - public String getServerBaseForRequest(HttpServletRequest theRequest) { - return getServerAddressStrategy().determineServerBase(getServletContext(), theRequest); - } } 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 5b469f01259..2fed137b4ea 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,6 @@ 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.exceptions.BaseServerResponseException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.util.OperationOutcomeUtil; @@ -71,7 +70,7 @@ public class ExceptionHandlingInterceptor extends InterceptorAdapter { } } - RestfulServerUtils.streamResponseAsResource(theRequestDetails.getServer(), theResponse, oo, true, Collections.singleton(SummaryEnum.FALSE), statusCode, false, false, theRequestDetails); + theRequestDetails.getResponse().streamResponseAsResource(oo, true, Collections.singleton(SummaryEnum.FALSE), statusCode, false, false); // theResponse.setStatus(statusCode); // theRequestDetails.getServer().addHeadersToResponse(theResponse); 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..67ce92734e6 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 @@ -31,10 +31,6 @@ import org.hl7.fhir.instance.model.api.IIdType; import ca.uhn.fhir.model.api.Bundle; import ca.uhn.fhir.model.api.TagList; -import ca.uhn.fhir.model.base.resource.BaseOperationOutcome; -import ca.uhn.fhir.rest.annotation.Read; -import ca.uhn.fhir.rest.annotation.ResourceParam; -import ca.uhn.fhir.rest.annotation.Search; import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.method.RequestDetails; import ca.uhn.fhir.rest.server.exceptions.AuthenticationException; @@ -148,6 +144,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 +183,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 +219,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 +257,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 1f8de109994..7c5e135282f 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 @@ -198,7 +198,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..f3723d9d21d --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/servlet/ServletRequestDetails.java @@ -0,0 +1,144 @@ +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 HttpServletRequest myServletRequest; + private HttpServletResponse myServletResponse; + /** + * @see BaseMethodBinding#loadRequestContents(RequestDetails) + */ + private static volatile IRequestReader ourRequestReader; + private byte[] requestContents; + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ServletRequestDetails.class); + private RestfulServer myServer; + + public ServletRequestDetails() { + super(); + setResponse(new ServletRestfulResponse(this)); + } + + public RestfulServer getServer() { + return myServer; + } + + public void setServer(RestfulServer theServer) { + this.myServer = theServer; + } + + + 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; + } + + @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)); + } + + public HttpServletRequest getServletRequest() { + return myServletRequest; + } + + public void setServletRequest(HttpServletRequest myServletRequest) { + this.myServletRequest = myServletRequest; + } + + public HttpServletResponse getServletResponse() { + return myServletResponse; + } + + public void setServletResponse(HttpServletResponse myServletResponse) { + this.myServletResponse = myServletResponse; + } + + @Override + public Reader getReader() throws IOException { + return getServletRequest().getReader(); + } + + @Override + public InputStream getInputStream() throws IOException { + return getServletRequest().getInputStream(); + } + + @Override + public String getServerBaseForRequest() { + return getServer().getServerBaseForRequest(getServletRequest()); + } + + 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())); + } + } + +} 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..d19375d3142 --- /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/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractConformanceRestServer.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractConformanceRestServer.java index ae17a41f5af..5c7e64b632e 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractConformanceRestServer.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractConformanceRestServer.java @@ -23,6 +23,8 @@ import ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.context.RuntimeResourceDefinition; 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.server.Constants; import ca.uhn.fhir.rest.server.HardcodedServerAddressStrategy; @@ -35,10 +37,10 @@ import ca.uhn.fhir.util.ReflectionUtil; * Conformance Rest Service * @author Peter Van Houte */ -@Produces(MediaType.APPLICATION_JSON) +@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) public abstract class AbstractConformanceRestServer extends AbstractJaxRsRestServer implements IConformanceRestServer { - public static final String PATH = "/"; + public static final String PATH = "/"; private static final org.slf4j.Logger ourLog = LoggerFactory.getLogger(AbstractConformanceRestServer.class); private ResourceBinding myServerBinding = new ResourceBinding(); @@ -73,9 +75,8 @@ public abstract class AbstractConformanceRestServer extends AbstractJaxRsRestSer @GET @OPTIONS @Path("/metadata") - @Produces(MediaType.APPLICATION_JSON) public Response conformance(String string) { - String conformanceString = getParser().encodeResourceToString(myConformance); + String conformanceString = getParser(createRequestDetails(null, RequestTypeEnum.OPTIONS, RestOperationTypeEnum.METADATA)).encodeResourceToString(myConformance); ResponseBuilder entity = Response.status(Constants.STATUS_HTTP_200_OK).entity(conformanceString); entity.header("Access-Control-Allow-Origin", "*"); return entity.build(); diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsRestServer.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsRestServer.java index c2f663d42c6..1bda6dbdc42 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsRestServer.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsRestServer.java @@ -1,49 +1,40 @@ package ca.uhn.fhir.jaxrs.server; import java.util.HashMap; -import java.util.List; -import java.util.Map.Entry; -import javax.ws.rs.Produces; import javax.ws.rs.core.Context; import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; -import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.model.api.Bundle; -import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.jaxrs.server.util.JaxRsRequestDetails; import ca.uhn.fhir.parser.IParser; -import ca.uhn.fhir.rest.server.Constants; +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; +import ca.uhn.fhir.rest.server.RestfulServerUtils; /** * Abstract Jax Rs Rest Server - * @author axmpm + * @author Peter Van Houte * */ -@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) -public abstract class AbstractJaxRsRestServer { +public abstract class AbstractJaxRsRestServer implements IRestfulServerDefaults { - private static Logger ourLog = LoggerFactory.getLogger(AbstractJaxRsRestServer.class); public static FhirContext CTX = FhirContext.forDstu2(); @Context - protected UriInfo info; + private UriInfo info; @Context - HttpHeaders headers; + private HttpHeaders headers; - private IParser jsonParser = getFhirContext().newJsonParser(); - private IParser xmlParser = getFhirContext().newXmlParser(); - private String baseUri; - - public static FhirContext getFhirContext() { + public FhirContext getFhirContext() { return CTX; } @@ -51,72 +42,57 @@ public abstract class AbstractJaxRsRestServer { * param and query methods */ protected HashMap getQueryMap() { - MultivaluedMap queryParameters = info.getQueryParameters(); + MultivaluedMap queryParameters = getInfo().getQueryParameters(); HashMap params = new HashMap(); for (String key : queryParameters.keySet()) { params.put(key, queryParameters.get(key).toArray(new String[] {})); } return params; } - - private String getParam(String string) { - for (Entry> entry : info.getQueryParameters().entrySet()) { - if (string.equalsIgnoreCase(entry.getKey())) { - return entry.getValue().iterator().next(); - } - } - return null; - } - - protected Integer getIntParam(String string) { - String param = getParam(string); - return param == null ? 0 : Integer.valueOf(param); - } - protected String getBaseUri() { - if(this.baseUri == null) { - this.baseUri = info.getBaseUri().toASCIIString(); - } - ourLog.debug("BaseUri is equal to %s", baseUri); - return this.baseUri; + public IServerAddressStrategy getServerAddressStrategy() { + HardcodedServerAddressStrategy addressStrategy = new HardcodedServerAddressStrategy(); + addressStrategy.setValue(getBaseUri()); + return addressStrategy; } - /** + protected String getBaseUri() { + return getInfo().getBaseUri().toASCIIString(); + } + + /** * PARSING METHODS */ - public IParser getParser() { - IParser parser = MediaType.APPLICATION_XML.equals(getParserType()) ? xmlParser : jsonParser; - return parser.setPrettyPrint(getPrettyPrint()); + public IParser getParser(JaxRsRequestDetails theRequestDetails) { + return RestfulServerUtils.getNewParser(getFhirContext(), theRequestDetails); } - private boolean getPrettyPrint() { - String printPretty = getParam("_pretty"); - return printPretty == null || printPretty.trim().length() == 0 ? true : Boolean.valueOf(printPretty); + protected JaxRsRequestDetails createRequestDetails(final String resourceString, RequestTypeEnum requestType, RestOperationTypeEnum restOperation) { + JaxRsRequestDetails theRequest = new JaxRsRequestDetails(headers, resourceString); + theRequest.setFhirServerBase(getBaseUri()); + theRequest.setRestOperationType(restOperation); + theRequest.setServer(this); + theRequest.setParameters(getQueryMap()); + theRequest.setRequestType(requestType); + return theRequest; } + - protected String getParserType() { - if ((headers != null && headers.getMediaType() != null && headers.getMediaType().getSubtype() != null - && headers.getMediaType().getSubtype().contains("xml")) || getDefaultResponseEncoding() == EncodingEnum.XML - || "xml".equals(getParam("_format"))) { - return MediaType.APPLICATION_XML; - } else { - return MediaType.APPLICATION_JSON; - } - } + /** + * Get the info + * @return the info + */ + public UriInfo getInfo() { + return info; + } - Response createResponse(IBaseResource resource) { - Bundle resultingBundle = new Bundle(); - resultingBundle.addEntry().setResource((IResource) resource); - return ok(encodeResponse(resultingBundle)); - } - - protected Response ok(String entity) { - return Response.status(Constants.STATUS_HTTP_200_OK).header("Content-Type", getParserType()).entity(entity).build(); - } - - private String encodeResponse(Bundle resource) { - return resource == null ? "null" : getParser().encodeBundleToString(resource); - } + /** + * Set the info + * @param info the info to set + */ + public void setInfo(UriInfo info) { + this.info = info; + } /** * DEFAULT VALUES @@ -124,5 +100,24 @@ public abstract class AbstractJaxRsRestServer { public EncodingEnum getDefaultResponseEncoding() { return EncodingEnum.JSON; } + + @Override + public boolean isDefaultPrettyPrint() { + return true; + } + @Override + public ETagSupportEnum getETagSupport() { + return ETagSupportEnum.DISABLED; + } + + @Override + public AddProfileTagEnum getAddProfileTag() { + return AddProfileTagEnum.NEVER; + } + + @Override + public boolean isUseBrowserFriendlyContentTypes() { + return true; + } } diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractResourceRestServer.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractResourceRestServer.java index 913694a92e3..79f9decf717 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractResourceRestServer.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractResourceRestServer.java @@ -1,10 +1,10 @@ package ca.uhn.fhir.jaxrs.server; -import java.lang.reflect.InvocationTargetException; -import java.util.List; -import java.util.Map; +import java.io.IOException; +import java.net.URL; import javax.interceptor.Interceptors; +import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.POST; @@ -15,216 +15,166 @@ import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.apache.commons.lang3.StringUtils; import ca.uhn.fhir.jaxrs.server.interceptor.ExceptionInterceptor; +import ca.uhn.fhir.jaxrs.server.util.JaxRsRequestDetails; import ca.uhn.fhir.jaxrs.server.util.MethodBindings; -import ca.uhn.fhir.jaxrs.server.util.RestfulServerDefaults; -import ca.uhn.fhir.model.api.Bundle; -import ca.uhn.fhir.model.api.BundleEntry; import ca.uhn.fhir.model.api.IResource; -import ca.uhn.fhir.model.dstu2.resource.Parameters; import ca.uhn.fhir.model.primitive.IdDt; -import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.RequestTypeEnum; -import ca.uhn.fhir.rest.method.CreateMethodBinding; -import ca.uhn.fhir.rest.method.OperationMethodBinding; +import ca.uhn.fhir.rest.api.RestOperationTypeEnum; +import ca.uhn.fhir.rest.method.BaseMethodBinding; import ca.uhn.fhir.rest.method.RequestDetails; -import ca.uhn.fhir.rest.method.SearchMethodBinding; import ca.uhn.fhir.rest.server.Constants; -import ca.uhn.fhir.rest.server.IBundleProvider; -import ca.uhn.fhir.rest.server.IResourceProvider; -import ca.uhn.fhir.rest.server.IRestfulServerDefaults; -import ca.uhn.fhir.rest.server.IVersionSpecificBundleFactory; -import ca.uhn.fhir.rest.server.ResourceBinding; +import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.util.UrlUtil; /** * Fhir Physician Rest Service * @author axmpm * */ -@SuppressWarnings({ "unused"}) -@Produces({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML}) -public abstract class AbstractResourceRestServer extends AbstractJaxRsRestServer implements IResourceProvider { +@Produces({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN}) +@Consumes({MediaType.APPLICATION_FORM_URLENCODED,MediaType.APPLICATION_JSON, "application/json+fhir", "application/xml+fhir"}) +public abstract class AbstractResourceRestServer extends AbstractJaxRsRestServer implements IResourceRestServer { + + private static MethodBindings bindings; - private static final Logger ourLog = LoggerFactory.getLogger(AbstractResourceRestServer.class); - - private ResourceBinding myServerBinding = new ResourceBinding(); - private IRestfulServerDefaults serverDefaults = new RestfulServerDefaults(); - private MethodBindings bindings = new MethodBindings(); - public AbstractResourceRestServer(Class subclass) { - bindings.findMethods(this, subclass, getFhirContext()); + initBindings(subclass); } - @GET - @Interceptors(ExceptionInterceptor.class) - Response search() throws Exception { - return execute(); - } + private void initBindings(Class subclass) { + if(bindings == null) { + MethodBindings methodBindings = new MethodBindings(); + methodBindings.findMethods(this, subclass, getFhirContext()); + bindings = methodBindings; + } + } - protected Response customOperation(final IBaseResource resource, RequestTypeEnum requestType) - throws Exception, IllegalAccessException, InvocationTargetException { - OperationMethodBinding method = bindings.getBinding(OperationMethodBinding.class); - final RequestDetails theRequest = createRequestDetails(resource, requestType); - final Object[] paramsServer = bindings.createParams(resource, method, theRequest); - Parameters result = (Parameters) method.getMethod().invoke(this, paramsServer); - return ok(getParser().encodeResourceToString(result)); - } - - Response create(final Map params, R resource) throws Exception { - CreateMethodBinding method = bindings.getBinding(CreateMethodBinding.class); - final RequestDetails theRequest = createRequestDetails(resource, null); - final Object[] paramsServer = bindings.createParams(resource, method, theRequest); - MethodOutcome result = (MethodOutcome) method.getMethod().invoke(this, paramsServer); - return createResponse(result.getResource()); + @Override + protected String getBaseUri() { + try { + return new URL(getInfo().getBaseUri().toURL(), getResourceType().getSimpleName()).toExternalForm(); + } catch(Exception e) { + // cannot happen + return null; + } } @POST + @Override @Interceptors(ExceptionInterceptor.class) public Response create(final String resourceString) throws Exception { - return create(getQueryMap(), parseResource(resourceString)); + return executeMethod(resourceString, RequestTypeEnum.POST, RestOperationTypeEnum.CREATE, null); } @POST @Interceptors(ExceptionInterceptor.class) @Path("/_search") + @Override public Response searchWithPost() throws Exception { - return search(); + return executeMethod(null, RequestTypeEnum.POST, RestOperationTypeEnum.SEARCH_TYPE, null); } - + @GET - @Path("/{id}") - @Interceptors(ExceptionInterceptor.class) - public Response find(@PathParam("id") final String id) { - final R resource = find(new IdDt(id)); - return createSingleResponse(resource); - } - + @Override + @Interceptors(ExceptionInterceptor.class) + public Response search() throws Exception { + return executeMethod(null, RequestTypeEnum.GET, RestOperationTypeEnum.SEARCH_TYPE, null); + } + @PUT + @Override @Path("/{id}") @Interceptors(ExceptionInterceptor.class) public Response update(@PathParam("id") final String id, final String resourceString) throws Exception { - final R resource = parseResource(resourceString); -// final MethodOutcome update = update(new IdDt(resource.getId()), practitioner); -// return createResponse(update.getResource()); - return createResponse(resource); + return executeMethod(resourceString, RequestTypeEnum.PUT, RestOperationTypeEnum.UPDATE, id); } - + @DELETE + @Override @Path("/{id}") @Interceptors(ExceptionInterceptor.class) - public Response delete(@PathParam("id") final String id) + public Response delete(@PathParam("id") final String id) throws Exception { + return executeMethod(null, RequestTypeEnum.DELETE, RestOperationTypeEnum.DELETE, id); + } + + + @GET + @Override + @Path("/{id}") + @Interceptors(ExceptionInterceptor.class) + public Response find(@PathParam("id") final String id) throws Exception { + return executeMethod(null, RequestTypeEnum.GET, RestOperationTypeEnum.READ, id); + } + + protected Response customOperation(final String resource, RequestTypeEnum requestType, String id, String operationName, RestOperationTypeEnum operationType) throws Exception { -// final MethodOutcome delete = delete(new IdDt(id)); -// return createResponse(delete.getResource()); - return null; + return executeMethod(resource, requestType, operationType, id, bindings.getBinding(operationType, operationName)); } @GET + @Override @Path("/{id}/_history/{version}") @Interceptors(ExceptionInterceptor.class) - public Response findHistory(@PathParam("id") final String id, @PathParam("version") final String version) { - final IdDt dt = new IdDt(getBaseUri(), getResourceType().getSimpleName(), id, version); - final R resource = findHistory(dt); - return createSingleResponse(resource); + public Response findHistory(@PathParam("id") final String id, @PathParam("version") final String versionString) + throws BaseServerResponseException, IOException { + BaseMethodBinding method = bindings.getBinding(RestOperationTypeEnum.VREAD); + final RequestDetails theRequest = createRequestDetails(null, RequestTypeEnum.GET, RestOperationTypeEnum.VREAD); + if (id == null) { + throw new InvalidRequestException("Don't know how to handle request path: " + getInfo().getRequestUri().toASCIIString()); + } + theRequest.setId(new IdDt(getBaseUri(), id, UrlUtil.unescape(versionString))); + return (Response) method.invokeServer(this, theRequest); } - + @GET + @Override @Path("/{id}/{compartment}") @Interceptors(ExceptionInterceptor.class) - public Response findCompartment(@PathParam("id") final String id, @PathParam("compartment") final String compartment) { - final IdDt dt = new IdDt(getBaseUri(), getResourceType().getSimpleName(), id); - final R resource = find(new IdDt(id)); - return createResponse(resource); - } - - /** - * PARSING METHODS - */ - private Response createSingleResponse(final R resource) { - return ok(getParser().encodeResourceToString(resource)); - } - - Response createResponse(final IBaseResource resource) { - final Bundle resultingBundle = new Bundle(); - resultingBundle.addEntry().setResource((IResource) resource); - return ok(encodeToString(resultingBundle)); - } - - Response createResponse(final List resources) { - final Bundle resultingBundle = new Bundle(); - for (final R resource : resources) { - addBundleEntry(resultingBundle, resource); - } - return ok(encodeToString(resultingBundle)); - } - - protected Response ok(String entity) { - return Response.status(Constants.STATUS_HTTP_200_OK).header("Content-Type", getParserType()).entity(entity).build(); - } - - protected String encodeToString(final Bundle resource) { - return resource != null ? getParser().encodeBundleToString(resource) : "null"; - } - - private R parseResource(final String resource) { - return getParser().parseResource(getResourceType(), resource); - } - - @Deprecated - private void addBundleEntry(final Bundle resultingBundle, final R resource) { - final BundleEntry entry = resultingBundle.addEntry(); - entry.setResource(resource); - if (resource != null && resource.getId() != null) { - entry.setId(resource.getId()); + public Response findCompartment(@PathParam("id") final String id, @PathParam("compartment") final String compartment) throws BaseServerResponseException, IOException { + BaseMethodBinding method = bindings.getBinding(RestOperationTypeEnum.SEARCH_TYPE, compartment); + final RequestDetails theRequest = createRequestDetails(null, RequestTypeEnum.GET, RestOperationTypeEnum.VREAD); + if (id == null) { + throw new InvalidRequestException("Don't know how to handle request path: " + getInfo().getRequestUri().toASCIIString()); } + theRequest.setCompartmentName(compartment); + theRequest.setId(new IdDt(getBaseUri(), id)); + return (Response) method.invokeServer(this, theRequest); } + private > Response executeMethod(final String resourceString, RequestTypeEnum requestType, RestOperationTypeEnum restOperation, String id) + throws BaseServerResponseException, IOException { + BaseMethodBinding method = bindings.getBinding(restOperation); + return executeMethod(resourceString, requestType, restOperation, id, method); + } + + private Response executeMethod(final String resourceString, RequestTypeEnum requestType, RestOperationTypeEnum restOperation, String id, + BaseMethodBinding method) + throws IOException { + final RequestDetails theRequest = createRequestDetails(resourceString, requestType, restOperation, id); + return (Response) method.invokeServer(this, theRequest); + } - private RequestDetails createRequestDetails(final IBaseResource resource, RequestTypeEnum requestType) { - final RequestDetails theRequest = new RequestDetails() { -// @Override -// public String getHeader(String headerIfNoneExist) { -// List requestHeader = headers.getRequestHeader(headerIfNoneExist); -// return (requestHeader == null || requestHeader.size() == 0) ? null : requestHeader.get(0); -// } - }; - theRequest.setFhirServerBase(getBaseUri()); -// theRequest.setServer(this); - theRequest.setParameters(getQueryMap()); -// theRequest.setRequestContent(resource); - theRequest.setRequestType(requestType); + + protected JaxRsRequestDetails createRequestDetails(final String resourceString, RequestTypeEnum requestType, RestOperationTypeEnum restOperation, String id) { + JaxRsRequestDetails theRequest = super.createRequestDetails(resourceString, requestType, restOperation); + theRequest.setId(StringUtils.isBlank(id) ? null : new IdDt(getResourceType().getName(), UrlUtil.unescape(id))); + if(restOperation == RestOperationTypeEnum.UPDATE) { + String contentLocation = theRequest.getHeader(Constants.HEADER_CONTENT_LOCATION); + if (contentLocation != null) { + theRequest.setId(new IdDt(contentLocation)); + } + } return theRequest; } - public Response execute() { - SearchMethodBinding method = bindings.getBinding(SearchMethodBinding.class); - final RequestDetails theRequest = createRequestDetails(null, null); - final Object[] paramsServer = bindings.createParams(null, method, theRequest); - Object result = null; //method.invokeServer(null, paramsServer); - final IBundleProvider bundle = (IBundleProvider) result; - IVersionSpecificBundleFactory bundleFactory = getFhirContext().newBundleFactory(); -// bundleFactory.initializeBundleFromBundleProvider(this, bundle, EncodingEnum.JSON, info.getAbsolutePath().toASCIIString(), -// info.getAbsolutePath().toASCIIString(), getPrettyPrint(), getIntParam("_getpagesoffset"), getIntParam("_count"), null, -// BundleTypeEnum.SEARCHSET, Collections.emptySet()); - IBaseResource resource = bundleFactory.getResourceBundle(); - return ok(getParser().encodeResourceToString(resource)); - } - - public R find(final IdDt theId) { - throw new UnsupportedOperationException(); - } - - public R findHistory(final IdDt theId) { - throw new UnsupportedOperationException(); - } - - @Override + @Override public abstract Class getResourceType(); } diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/IConformanceRestServer.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/IConformanceRestServer.java index db1b2af5ee6..61d08098113 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/IConformanceRestServer.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/IConformanceRestServer.java @@ -1,7 +1,8 @@ package ca.uhn.fhir.jaxrs.server; import ca.uhn.fhir.rest.server.IResourceProvider; +import ca.uhn.fhir.rest.server.IRestfulServerDefaults; -public interface IConformanceRestServer extends IResourceProvider { +public interface IConformanceRestServer extends IResourceProvider, IRestfulServerDefaults { } diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/IResourceRestServer.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/IResourceRestServer.java new file mode 100644 index 00000000000..0b8001c0395 --- /dev/null +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/IResourceRestServer.java @@ -0,0 +1,36 @@ +package ca.uhn.fhir.jaxrs.server; + +import java.io.IOException; + +import javax.ws.rs.core.Response; + +import ca.uhn.fhir.jaxrs.server.util.JaxRsRequestDetails; +import ca.uhn.fhir.rest.server.IResourceProvider; +import ca.uhn.fhir.rest.server.IRestfulServer; +import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; + +public interface IResourceRestServer extends IRestfulServer, IResourceProvider { + + Response search() + throws Exception; + + Response create(String resourceString) + throws Exception; + + Response searchWithPost() + throws Exception; + + Response find(String id) throws Exception; + + Response update(String id, String resourceString) + throws Exception; + + Response delete(String id) + throws Exception; + + Response findHistory(String id, String version) throws BaseServerResponseException, IOException; + + Response findCompartment(String id, String compartment) throws BaseServerResponseException, IOException; + + +} diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/JaxRsRestfulResponse.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/JaxRsRestfulResponse.java new file mode 100644 index 00000000000..19bad6d12cc --- /dev/null +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/JaxRsRestfulResponse.java @@ -0,0 +1,78 @@ +package ca.uhn.fhir.jaxrs.server; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.StringWriter; +import java.io.UnsupportedEncodingException; +import java.io.Writer; +import java.util.Map.Entry; +import java.util.zip.GZIPOutputStream; + +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.ResponseBuilder; + +import org.hl7.fhir.instance.model.api.IBaseBinary; + +import ca.uhn.fhir.jaxrs.server.util.JaxRsRequestDetails; +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; + +public class JaxRsRestfulResponse extends RestfulResponse { + + public JaxRsRestfulResponse(String resourceString, JaxRsRequestDetails jaxRsRequestDetails) { + super(jaxRsRequestDetails); + } + + @Override + public Writer getResponseWriter(int statusCode, String contentType, String charset, boolean respondGzip) + throws UnsupportedEncodingException, IOException { + if (respondGzip) { + addHeader(Constants.HEADER_CONTENT_ENCODING, Constants.ENCODING_GZIP); + return new OutputStreamWriter(new GZIPOutputStream(new ByteArrayOutputStream()), Constants.CHARSET_NAME_UTF8); + } else { + return new StringWriter(); + } + } + + @Override + public Response sendWriterResponse(int status, String contentType, String charset, Writer writer) { + return Response.status(status)/*.header(HttpHeaders.CONTENT_TYPE, charset)*/.header(Constants.HEADER_CONTENT_TYPE, contentType).entity(writer.toString()).build(); + } + + @Override + public Object sendAttachmentResponse(IBaseBinary bin, int statusCode, String contentType) throws IOException { + ResponseBuilder response = Response.status(statusCode); + for (Entry header : getHeaders().entrySet()) { + response.header(header.getKey(), header.getValue()); + } + if (bin.getContent() != null && bin.getContent().length > 0) { + response.header(Constants.HEADER_CONTENT_TYPE, contentType).entity(bin.getContent()); + } + return response.build(); + } + + @Override + public Object returnResponse(ParseAction outcome, int operationStatus, boolean allowPrefer, MethodOutcome response, + String resourceName) + throws IOException { + Writer writer = new StringWriter(); + IParser parser = RestfulServerUtils.getNewParser(getRequestDetails().getServer().getFhirContext(), getRequestDetails()); + if(outcome != null) { + outcome.execute(parser, writer); + } + return Response.status(operationStatus).header(Constants.HEADER_CONTENT_TYPE, getParserType()).entity(writer.toString()).build(); + } + + protected String getParserType() { + EncodingEnum encodingEnum = RestfulServerUtils.determineResponseEncodingWithDefault(getRequestDetails()); + return encodingEnum == EncodingEnum.JSON ? MediaType.APPLICATION_JSON : MediaType.APPLICATION_XML; + } + +} diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/interceptor/ExceptionInterceptor.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/interceptor/ExceptionInterceptor.java index d8fead97eda..73428c0dcee 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/interceptor/ExceptionInterceptor.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/interceptor/ExceptionInterceptor.java @@ -36,7 +36,7 @@ public class ExceptionInterceptor { @Context private HttpHeaders headers; - FhirContext fhirContext = AbstractJaxRsRestServer.getFhirContext(); + FhirContext fhirContext = AbstractJaxRsRestServer.CTX; @AroundInvoke public Object intercept(final InvocationContext ctx) throws Exception { diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsRequestDetails.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsRequestDetails.java new file mode 100644 index 00000000000..9aa9998791e --- /dev/null +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsRequestDetails.java @@ -0,0 +1,68 @@ +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 ca.uhn.fhir.jaxrs.server.AbstractJaxRsRestServer; +import ca.uhn.fhir.jaxrs.server.JaxRsRestfulResponse; +import ca.uhn.fhir.rest.method.RequestDetails; +import ca.uhn.fhir.rest.param.ResourceParameter; + +public class JaxRsRequestDetails extends RequestDetails { + + private String theResourceString; + private HttpHeaders headers; + private AbstractJaxRsRestServer myServer; + + public AbstractJaxRsRestServer getServer() { + return myServer; + } + + public void setServer(AbstractJaxRsRestServer theServer) { + this.myServer = theServer; + } + + public JaxRsRequestDetails(HttpHeaders headers, String resourceString) { + this.headers = headers; + this.theResourceString = resourceString; + setResponse(new JaxRsRestfulResponse(resourceString, this)); + } + + @Override + public String getHeader(String headerIfNoneExist) { + List requestHeader = headers.getRequestHeader(headerIfNoneExist); + return (requestHeader == null || requestHeader.size() == 0) ? 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 theResourceString.getBytes(ResourceParameter.determineRequestCharset(this)); + } + + @Override + public Reader getReader() + throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public InputStream getInputStream() { + 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/MethodBindings.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/MethodBindings.java index a536459b2fc..7716919130b 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/MethodBindings.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/MethodBindings.java @@ -3,77 +3,66 @@ package ca.uhn.fhir.jaxrs.server.util; import java.lang.reflect.Method; import java.util.concurrent.ConcurrentHashMap; -import org.hl7.fhir.instance.model.api.IBaseResource; +import org.apache.commons.lang3.StringUtils; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jaxrs.server.AbstractResourceRestServer; +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.CreateMethodBinding; -import ca.uhn.fhir.rest.method.DeleteMethodBinding; -import ca.uhn.fhir.rest.method.IParameter; import ca.uhn.fhir.rest.method.OperationMethodBinding; -import ca.uhn.fhir.rest.method.RequestDetails; import ca.uhn.fhir.rest.method.SearchMethodBinding; -import ca.uhn.fhir.rest.method.UpdateMethodBinding; -import ca.uhn.fhir.rest.param.ResourceParameter; import ca.uhn.fhir.util.ReflectionUtil; -@SuppressWarnings({"unchecked", "rawtypes"}) public class MethodBindings { - - /** BaseOutcomeReturningMethodBinding */ - private ConcurrentHashMap createMethods = new ConcurrentHashMap(); - private ConcurrentHashMap updateMethods = new ConcurrentHashMap(); - private ConcurrentHashMap delete = new ConcurrentHashMap(); - /** BaseResourceReturingMethodBinding */ - private ConcurrentHashMap searchMethods = new ConcurrentHashMap(); - private ConcurrentHashMap operationMethods = new ConcurrentHashMap(); + /** ALL METHOD BINDINGS */ + ConcurrentHashMap>> allBindings = new ConcurrentHashMap>>(); public > void findMethods(T theProvider, Class subclass, FhirContext fhirContext) { for (final Method m : ReflectionUtil.getDeclaredMethods(subclass)) { final BaseMethodBinding foundMethodBinding = BaseMethodBinding.bindMethod(m, fhirContext, theProvider); - if(foundMethodBinding == null) { + if (foundMethodBinding == null) { continue; } - ConcurrentHashMap map = getMap(foundMethodBinding.getClass()); - if (map.contains(theProvider.getResourceType().getName())) { - throw new IllegalArgumentException("Multiple Search Method Bindings Found : " + foundMethodBinding.getMethod() + " -- " - + foundMethodBinding.getMethod()); + ConcurrentHashMap> map = getAllBindingsMap(foundMethodBinding.getRestOperationType()); + if (foundMethodBinding instanceof OperationMethodBinding) { + OperationMethodBinding binding = (OperationMethodBinding) foundMethodBinding; + putIfAbsent(map, binding.getName(), binding); + } else if (foundMethodBinding instanceof SearchMethodBinding) { + Search search = m.getAnnotation(Search.class); + String compartmentName = StringUtils.defaultIfBlank(search.compartmentName(), ""); + putIfAbsent(map, compartmentName, foundMethodBinding); } else { - map.put(theProvider.getResourceType().getName(), foundMethodBinding); + putIfAbsent(map, "", foundMethodBinding); } } } + + private void putIfAbsent(ConcurrentHashMap> map, String key, BaseMethodBinding binding) { + if (map.containsKey(key)) { + throw new IllegalArgumentException("Multiple Search Method Bindings Found : " + map.get(key) + " -- " + binding.getMethod()); + } + map.put(key, binding); + } + + private ConcurrentHashMap> getAllBindingsMap(final RestOperationTypeEnum restOperationTypeEnum) { + allBindings.putIfAbsent(restOperationTypeEnum, new ConcurrentHashMap>()); + return allBindings.get(restOperationTypeEnum); + } + + public BaseMethodBinding getBinding(RestOperationTypeEnum operationType, String qualifier) { + String nonEmptyQualifier = StringUtils.defaultIfBlank(qualifier, ""); + ConcurrentHashMap> map = getAllBindingsMap(operationType); + if(!map.containsKey(nonEmptyQualifier)) { + throw new UnsupportedOperationException(); + } else { + return map.get(nonEmptyQualifier); + } + } - private ConcurrentHashMap getMap(Class class1) { - if(class1.isAssignableFrom(CreateMethodBinding.class)) return (ConcurrentHashMap) createMethods; - if(class1.isAssignableFrom(UpdateMethodBinding.class)) return (ConcurrentHashMap) updateMethods; - if(class1.isAssignableFrom(DeleteMethodBinding.class)) return (ConcurrentHashMap) delete; - if(class1.isAssignableFrom(SearchMethodBinding.class)) return (ConcurrentHashMap) searchMethods; - if(class1.isAssignableFrom(OperationMethodBinding.class)) return (ConcurrentHashMap) operationMethods; - return new ConcurrentHashMap(); - } - - public Object[] createParams(IBaseResource resource, final BaseMethodBinding method, final RequestDetails theRequest) { - final Object[] paramsServer = new Object[method.getParameters().size()]; - for (int i = 0; i < method.getParameters().size(); i++) { - final IParameter param = method.getParameters().get(i); - if(param instanceof ResourceParameter) { - paramsServer[i] = resource; - } else { - paramsServer[i] = param.translateQueryParametersIntoServerArgument(theRequest, null, method); - } - } - return paramsServer; - } - - public T getBinding(Class clazz) { - ConcurrentHashMap map = getMap((Class) clazz); - if(map.values().size() == 0) { - throw new UnsupportedOperationException(); - } - return (T) map.values().iterator().next(); + public BaseMethodBinding getBinding(RestOperationTypeEnum operationType) { + return getBinding(operationType, ""); } } diff --git a/hapi-fhir-jaxrsserver-example/pom.xml b/hapi-fhir-jaxrsserver-example/pom.xml index d7cdf79a677..1a98bb79cb0 100644 --- a/hapi-fhir-jaxrsserver-example/pom.xml +++ b/hapi-fhir-jaxrsserver-example/pom.xml @@ -13,7 +13,7 @@ hapi-fhir-jaxrsserver-example war - HAPI FHIR JPA Server - Example + HAPI FHIR JAX-RS Server - Example diff --git a/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/ConformanceRestServer.java b/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/ConformanceRestServer.java index 6a185ca92fd..44e1fac3d90 100644 --- a/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/ConformanceRestServer.java +++ b/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/ConformanceRestServer.java @@ -8,6 +8,8 @@ import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; +import ca.uhn.fhir.rest.server.Constants; + /** * Conformance Rest Service * @author Peter Van Houte @@ -15,7 +17,7 @@ import javax.ws.rs.core.MediaType; @Local @Path(ConformanceRestServer.PATH) @Stateless -@Produces(MediaType.APPLICATION_JSON) +@Produces({MediaType.APPLICATION_JSON, Constants.CT_FHIR_JSON, Constants.CT_FHIR_XML}) public class ConformanceRestServer extends ca.uhn.fhir.jaxrs.server.AbstractConformanceRestServer { private static final String SERVER_VERSION = "1.0.0"; diff --git a/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/FhirPatientRestServer.java b/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/FhirPatientRestServer.java index 43486a088ad..8c88816b2a4 100644 --- a/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/FhirPatientRestServer.java +++ b/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/FhirPatientRestServer.java @@ -1,5 +1,7 @@ package ca.uhn.fhir.jaxrs.server.example; +import java.util.ArrayList; +import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.concurrent.ConcurrentHashMap; @@ -10,12 +12,15 @@ import javax.interceptor.Interceptors; 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.AbstractResourceRestServer; import ca.uhn.fhir.jaxrs.server.interceptor.ExceptionInterceptor; +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; @@ -33,8 +38,16 @@ 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; /** * Fhir Physician Rest Service @@ -44,7 +57,7 @@ import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; @Local(IFhirPatientRestServer.class) @Path(FhirPatientRestServer.PATH) @Stateless -@Produces(MediaType.APPLICATION_JSON) +@Produces({MediaType.APPLICATION_JSON, Constants.CT_FHIR_JSON, Constants.CT_FHIR_XML}) public class FhirPatientRestServer extends AbstractResourceRestServer implements IFhirPatientRestServer { static final String PATH = "/Patient"; @@ -57,8 +70,8 @@ public class FhirPatientRestServer extends AbstractResourceRestServer i } static { - patients.put(""+counter, createPatient("Agfa")); - patients.put(""+(counter), createPatient("Healthcare")); + patients.put(""+counter, createPatient("Van Houte")); + patients.put(""+(counter), createPatient("Agnew")); for(int i = 0 ; i<20 ; i++) { patients.put(""+(counter), createPatient("Random Patient " + counter)); } @@ -135,7 +148,7 @@ public class FhirPatientRestServer extends AbstractResourceRestServer i } @Override - @Read(version = false) + @Read(version = true) public Patient findHistory(@IdParam final IdDt theId) { if (patients.containsKey(theId.getIdPart())) { final List list = patients.get(theId.getIdPart()); @@ -174,18 +187,28 @@ public class FhirPatientRestServer extends AbstractResourceRestServer i @Path("/{id}/$last") @Interceptors(ExceptionInterceptor.class) @Override - public Response operationLastGet(final String resource) + public Response operationLastGet(@PathParam("id") String id) throws Exception { - return customOperation(null, RequestTypeEnum.GET); + return customOperation(null, RequestTypeEnum.GET, id, "$last", RestOperationTypeEnum.EXTENDED_OPERATION_TYPE); } + @Search(compartmentName="Condition") + @Override + public List searchCompartment(@IdParam IdDt thePatientId) { + List retVal=new ArrayList(); + Condition condition = new Condition(); + condition.setId(new IdDt("665577")); + retVal.add(condition); + return retVal; + } + @POST @Path("/{id}/$last") @Interceptors(ExceptionInterceptor.class) @Override public Response operationLast(final String resource) throws Exception { - return customOperation(getParser().parseResource(resource), RequestTypeEnum.POST); + return customOperation(resource, RequestTypeEnum.POST, null, "$last", RestOperationTypeEnum.EXTENDED_OPERATION_TYPE); } // @ca.uhn.fhir.rest.annotation.Validate @@ -213,10 +236,46 @@ public class FhirPatientRestServer extends AbstractResourceRestServer i .setValue(new StringDt((counter-1)+"" + "inputVariable [ " + dummyInput.getValue()+ "]")); return parameters; } - + + @Override + public Class getResourceType() { + return Patient.class; + } + + /** THE DEFAULTS */ @Override - public Class getResourceType() { - return Patient.class; + public List getInterceptors() { + return Collections.emptyList(); + } + + @Override + public ETagSupportEnum getETagSupport() { + return ETagSupportEnum.DISABLED; + } + + @Override + public boolean isDefaultPrettyPrint() { + return true; + } + + @Override + public AddProfileTagEnum getAddProfileTag() { + return AddProfileTagEnum.NEVER; + } + + @Override + public boolean isUseBrowserFriendlyContentTypes() { + return true; + } + + @Override + public IPagingProvider getPagingProvider() { + return new FifoMemoryPagingProvider(10); + } + + @Override + public BundleInclusionRule getBundleInclusionRule() { + return BundleInclusionRule.BASED_ON_INCLUDES; } } diff --git a/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/IFhirPatientRestServer.java b/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/IFhirPatientRestServer.java index 269d7af1391..0603fb4016c 100644 --- a/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/IFhirPatientRestServer.java +++ b/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/IFhirPatientRestServer.java @@ -4,15 +4,16 @@ import java.util.List; import javax.ws.rs.core.Response; +import ca.uhn.fhir.jaxrs.server.IResourceRestServer; +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.api.MethodOutcome; import ca.uhn.fhir.rest.param.StringParam; -import ca.uhn.fhir.rest.server.IResourceProvider; -public interface IFhirPatientRestServer extends IResourceProvider { +public interface IFhirPatientRestServer extends IResourceRestServer { List search(StringParam name); @@ -29,11 +30,14 @@ public interface IFhirPatientRestServer extends IResourceProvider { MethodOutcome delete(IdDt theId); Response operationLastGet(String resource) - throws Exception; + throws Exception; Response operationLast(String resource) throws Exception; Parameters last(StringDt dummyInput); + + List searchCompartment(IdDt thePatientId); + } 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 1e265b19554..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,27 +1,5 @@ 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.io.IOException; -import java.io.InputStream; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -29,8 +7,8 @@ import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.TreeSet; -import java.util.jar.Manifest; +import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; @@ -82,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) { @@ -155,9 +133,11 @@ public class ServerConformanceProvider implements IServerConformanceProvider includes = new TreeSet(); @@ -219,7 +199,7 @@ public class ServerConformanceProvider implements IServerConformanceProvider includes, DynamicSearchMethodBinding searchMethodBinding) { includes.addAll(searchMethodBinding.getIncludes()); @@ -352,7 +317,7 @@ public class ServerConformanceProvider implements IServerConformanceProvider nextTarget : nextParameter.getDeclaredTypes()) { - RuntimeResourceDefinition targetDef = myRestfulServer.getFhirContext().getResourceDefinition(nextTarget); + RuntimeResourceDefinition targetDef = myServerConfiguration.getFhirContext().getResourceDefinition(nextTarget); if (targetDef != null) { ResourceTypeEnum code = ResourceTypeEnum.VALUESET_BINDER.fromCodeString(targetDef.getName()); if (code != null) { diff --git a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/InterceptorTest.java b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/InterceptorTest.java index 9aa92193469..c6914cc2e51 100644 --- a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/InterceptorTest.java +++ b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/InterceptorTest.java @@ -62,10 +62,10 @@ public class InterceptorTest { public void testInterceptorFires() throws Exception { when(myInterceptor1.incomingRequestPreProcessed(any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true); when(myInterceptor1.incomingRequestPostProcessed(any(RequestDetails.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true); - when(myInterceptor1.outgoingResponse(any(RequestDetails.class), any(IResource.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true); + when(myInterceptor1.outgoingResponse(any(RequestDetails.class), any(IResource.class))).thenReturn(true); when(myInterceptor2.incomingRequestPreProcessed(any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true); when(myInterceptor2.incomingRequestPostProcessed(any(RequestDetails.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true); - when(myInterceptor2.outgoingResponse(any(RequestDetails.class), any(IResource.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true); + when(myInterceptor2.outgoingResponse(any(RequestDetails.class), any(IResource.class))).thenReturn(true); HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1"); HttpResponse status = ourClient.execute(httpGet); @@ -79,8 +79,8 @@ public class InterceptorTest { order.verify(myInterceptor1, times(1)).incomingRequestPreHandled(any(RestOperationTypeEnum.class), any(ActionRequestDetails.class)); order.verify(myInterceptor2, times(1)).incomingRequestPreHandled(any(RestOperationTypeEnum.class), any(ActionRequestDetails.class)); - order.verify(myInterceptor2, times(1)).outgoingResponse(any(RequestDetails.class), any(IResource.class), any(HttpServletRequest.class), any(HttpServletResponse.class)); - order.verify(myInterceptor1, times(1)).outgoingResponse(any(RequestDetails.class), any(IResource.class), any(HttpServletRequest.class), any(HttpServletResponse.class)); + order.verify(myInterceptor2, times(1)).outgoingResponse(any(RequestDetails.class), any(IResource.class)); + order.verify(myInterceptor1, times(1)).outgoingResponse(any(RequestDetails.class), any(IResource.class)); verifyNoMoreInteractions(myInterceptor1); verifyNoMoreInteractions(myInterceptor2); } diff --git a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/ResourceMethodTest.java b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/ResourceMethodTest.java index 36b51ec23f3..021e923ef4b 100644 --- a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/ResourceMethodTest.java +++ b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/ResourceMethodTest.java @@ -21,6 +21,7 @@ import ca.uhn.fhir.rest.method.IParameter; import ca.uhn.fhir.rest.method.RequestDetails; import ca.uhn.fhir.rest.method.SearchMethodBinding; import ca.uhn.fhir.rest.method.SearchParameter; +import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; public class ResourceMethodTest { @@ -51,7 +52,7 @@ public class ResourceMethodTest { inputParams.add("lastName"); inputParams.add("mrn"); - RequestDetails params = RequestDetails.withResourceAndParams("Patient", RequestTypeEnum.GET, inputParams); + RequestDetails params = ServletRequestDetails.withResourceAndParams("Patient", RequestTypeEnum.GET, inputParams); boolean actual = rm.incomingServerRequestMatchesMethod(params); assertTrue( actual); // True } @@ -72,7 +73,7 @@ public class ResourceMethodTest { inputParams.add("mrn"); inputParams.add("foo"); - assertEquals(false, rm.incomingServerRequestMatchesMethod(RequestDetails.withResourceAndParams("Patient", RequestTypeEnum.GET, inputParams))); // False + assertEquals(false, rm.incomingServerRequestMatchesMethod(ServletRequestDetails.withResourceAndParams("Patient", RequestTypeEnum.GET, inputParams))); // False } @Test @@ -89,7 +90,7 @@ public class ResourceMethodTest { inputParams.add("firstName"); inputParams.add("mrn"); - assertEquals(true, rm.incomingServerRequestMatchesMethod(RequestDetails.withResourceAndParams("Patient", RequestTypeEnum.GET, inputParams))); // True + assertEquals(true, rm.incomingServerRequestMatchesMethod(ServletRequestDetails.withResourceAndParams("Patient", RequestTypeEnum.GET, inputParams))); // True } @Test @@ -106,7 +107,7 @@ public class ResourceMethodTest { inputParams.add("firstName"); inputParams.add("lastName"); - assertEquals(false, rm.incomingServerRequestMatchesMethod(RequestDetails.withResourceAndParams("Patient", RequestTypeEnum.GET, inputParams))); // False + assertEquals(false, rm.incomingServerRequestMatchesMethod(ServletRequestDetails.withResourceAndParams("Patient", RequestTypeEnum.GET, inputParams))); // False } @Test @@ -121,7 +122,7 @@ public class ResourceMethodTest { Set inputParams = new HashSet(); inputParams.add("mrn"); - assertEquals(true, rm.incomingServerRequestMatchesMethod(RequestDetails.withResourceAndParams("Patient", RequestTypeEnum.GET, inputParams))); // True + assertEquals(true, rm.incomingServerRequestMatchesMethod(ServletRequestDetails.withResourceAndParams("Patient", RequestTypeEnum.GET, inputParams))); // True } @Test(expected=IllegalStateException.class) diff --git a/hapi-fhir-structures-dstu2/src/main/java/ca/uhn/fhir/rest/server/provider/dstu2/Dstu2BundleFactory.java b/hapi-fhir-structures-dstu2/src/main/java/ca/uhn/fhir/rest/server/provider/dstu2/Dstu2BundleFactory.java index c59329cd265..686cead9059 100644 --- a/hapi-fhir-structures-dstu2/src/main/java/ca/uhn/fhir/rest/server/provider/dstu2/Dstu2BundleFactory.java +++ b/hapi-fhir-structures-dstu2/src/main/java/ca/uhn/fhir/rest/server/provider/dstu2/Dstu2BundleFactory.java @@ -19,8 +19,7 @@ package ca.uhn.fhir.rest.server.provider.dstu2; * limitations under the License. * #L% */ - -import static org.apache.commons.lang3.StringUtils.*; +import static org.apache.commons.lang3.StringUtils.isNotBlank; import java.util.ArrayList; import java.util.Collections; @@ -59,8 +58,8 @@ import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.EncodingEnum; import ca.uhn.fhir.rest.server.IBundleProvider; import ca.uhn.fhir.rest.server.IPagingProvider; +import ca.uhn.fhir.rest.server.IRestfulServer; import ca.uhn.fhir.rest.server.IVersionSpecificBundleFactory; -import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.server.RestfulServerUtils; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.util.ResourceReferenceInfo; @@ -324,7 +323,7 @@ public class Dstu2BundleFactory implements IVersionSpecificBundleFactory { } @Override - public void initializeBundleFromBundleProvider(RestfulServer theServer, IBundleProvider theResult, EncodingEnum theResponseEncoding, String theServerBase, String theCompleteUrl, + 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) { myBase = theServerBase; diff --git a/hapi-fhir-structures-dstu2/src/main/java/ca/uhn/fhir/rest/server/provider/dstu2/ServerConformanceProvider.java b/hapi-fhir-structures-dstu2/src/main/java/ca/uhn/fhir/rest/server/provider/dstu2/ServerConformanceProvider.java index 18c2a0c17eb..d9f625a4bcf 100644 --- a/hapi-fhir-structures-dstu2/src/main/java/ca/uhn/fhir/rest/server/provider/dstu2/ServerConformanceProvider.java +++ b/hapi-fhir-structures-dstu2/src/main/java/ca/uhn/fhir/rest/server/provider/dstu2/ServerConformanceProvider.java @@ -34,6 +34,7 @@ import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; +import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; @@ -97,7 +98,7 @@ public class ServerConformanceProvider implements IServerConformanceProvider systemOps, BaseMethodBinding nextMethodBinding) { @@ -204,7 +205,9 @@ public class ServerConformanceProvider implements IServerConformanceProvider includes = new TreeSet(); diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/interceptor/ResponseHighlightingInterceptorTest.java b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/interceptor/ResponseHighlightingInterceptorTest.java index e7c144394e8..ce8168b1513 100644 --- a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/interceptor/ResponseHighlightingInterceptorTest.java +++ b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/interceptor/ResponseHighlightingInterceptorTest.java @@ -61,6 +61,7 @@ import ca.uhn.fhir.rest.server.EncodingEnum; import ca.uhn.fhir.rest.server.IResourceProvider; import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; +import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import ca.uhn.fhir.util.PortUtil; public class ResponseHighlightingInterceptorTest { @@ -122,7 +123,7 @@ public class ResponseHighlightingInterceptorTest { Patient resource = new Patient(); resource.addName().addFamily("FAMILY"); - RequestDetails reqDetails = new RequestDetails(); + ServletRequestDetails reqDetails = new ServletRequestDetails(); reqDetails.setRequestType(RequestTypeEnum.GET); reqDetails.setParameters(new HashMap()); reqDetails.setServer(new RestfulServer()); @@ -157,7 +158,7 @@ public class ResponseHighlightingInterceptorTest { Patient resource = new Patient(); resource.addName().addFamily("FAMILY"); - RequestDetails reqDetails = new RequestDetails(); + ServletRequestDetails reqDetails = new ServletRequestDetails(); reqDetails.setRequestType(RequestTypeEnum.GET); HashMap params = new HashMap(); params.put(Constants.PARAM_PRETTY, new String[] { Constants.PARAM_PRETTY_VALUE_TRUE }); @@ -192,7 +193,7 @@ public class ResponseHighlightingInterceptorTest { Patient resource = new Patient(); resource.addName().addFamily("FAMILY"); - RequestDetails reqDetails = new RequestDetails(); + ServletRequestDetails reqDetails = new ServletRequestDetails(); reqDetails.setRequestType(RequestTypeEnum.GET); HashMap params = new HashMap(); params.put(Constants.PARAM_PRETTY, new String[] { Constants.PARAM_PRETTY_VALUE_TRUE }); @@ -226,7 +227,7 @@ public class ResponseHighlightingInterceptorTest { Patient resource = new Patient(); resource.addName().addFamily("FAMILY"); - RequestDetails reqDetails = new RequestDetails(); + ServletRequestDetails reqDetails = new ServletRequestDetails(); reqDetails.setRequestType(RequestTypeEnum.GET); reqDetails.setParameters(new HashMap()); reqDetails.setServer(new RestfulServer()); @@ -264,7 +265,7 @@ public class ResponseHighlightingInterceptorTest { Patient resource = new Patient(); resource.addName().addFamily("FAMILY"); - RequestDetails reqDetails = new RequestDetails(); + ServletRequestDetails reqDetails = new ServletRequestDetails(); reqDetails.setRequestType(RequestTypeEnum.GET); reqDetails.setParameters(new HashMap()); RestfulServer server = new RestfulServer(); @@ -299,7 +300,7 @@ public class ResponseHighlightingInterceptorTest { Patient resource = new Patient(); resource.addName().addFamily("FAMILY"); - RequestDetails reqDetails = new RequestDetails(); + ServletRequestDetails reqDetails = new ServletRequestDetails(); reqDetails.setRequestType(RequestTypeEnum.GET); reqDetails.setParameters(new HashMap()); RestfulServer server = new RestfulServer(); diff --git a/hapi-fhir-structures-hl7org-dstu2/src/main/java/ca/uhn/fhir/rest/server/provider/dstu2hl7org/Dstu2Hl7OrgBundleFactory.java b/hapi-fhir-structures-hl7org-dstu2/src/main/java/ca/uhn/fhir/rest/server/provider/dstu2hl7org/Dstu2Hl7OrgBundleFactory.java index ca942ad5f64..595b715249c 100644 --- a/hapi-fhir-structures-hl7org-dstu2/src/main/java/ca/uhn/fhir/rest/server/provider/dstu2hl7org/Dstu2Hl7OrgBundleFactory.java +++ b/hapi-fhir-structures-hl7org-dstu2/src/main/java/ca/uhn/fhir/rest/server/provider/dstu2hl7org/Dstu2Hl7OrgBundleFactory.java @@ -57,6 +57,7 @@ import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.EncodingEnum; import ca.uhn.fhir.rest.server.IBundleProvider; import ca.uhn.fhir.rest.server.IPagingProvider; +import ca.uhn.fhir.rest.server.IRestfulServer; import ca.uhn.fhir.rest.server.IVersionSpecificBundleFactory; import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.server.RestfulServerUtils; @@ -297,7 +298,7 @@ public class Dstu2Hl7OrgBundleFactory implements IVersionSpecificBundleFactory { } @Override - public void initializeBundleFromBundleProvider(RestfulServer theServer, IBundleProvider theResult, + 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) { myBase = theServerBase;