Add configurable default response encoding to server, and serve Binary

resources as FHIR resources instead of blobs if the user has explicitly
requested an encoding
This commit is contained in:
James Agnew 2015-02-25 11:18:37 -05:00
parent 2c4b37e5f0
commit ecd3620e27
15 changed files with 798 additions and 616 deletions

View File

@ -45,7 +45,6 @@ import ca.uhn.fhir.rest.annotation.TagListParam;
import ca.uhn.fhir.rest.client.BaseHttpClientInvocation;
import ca.uhn.fhir.rest.method.SearchMethodBinding.RequestType;
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.exceptions.BaseServerResponseException;
@ -188,10 +187,8 @@ abstract class BaseAddOrDeleteTagsMethodBinding extends BaseMethodBinding<Void>
}
}
EncodingEnum responseEncoding = RestfulServer.determineResponseEncoding(theRequest.getServletRequest());
HttpServletResponse response = theRequest.getServletResponse();
response.setContentType(responseEncoding.getResourceContentType());
response.setContentType(Constants.CT_TEXT);
response.setStatus(Constants.STATUS_HTTP_200_OK);
response.setCharacterEncoding(Constants.CHARSET_UTF_8);

View File

@ -47,7 +47,7 @@ import ca.uhn.fhir.parser.IParser;
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.RestfulServer;
import ca.uhn.fhir.rest.server.RestfulServerUtils;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
/**
@ -235,7 +235,7 @@ abstract class BaseHttpClientInvocationWithContents extends BaseHttpClientInvoca
contents = parser.encodeBundleToString(myBundle);
contentType = encoding.getBundleContentType();
} else if (myResources != null) {
Bundle bundle = RestfulServer.createBundleFromResourceList(myContext, "", myResources, "", "", myResources.size(), myBundleType);
Bundle bundle = RestfulServerUtils.createBundleFromResourceList(myContext, "", myResources, "", "", myResources.size(), myBundleType);
contents = parser.encodeBundleToString(bundle);
contentType = encoding.getBundleContentType();
} else if (myContents != null) {

View File

@ -49,6 +49,7 @@ import ca.uhn.fhir.rest.method.SearchMethodBinding.RequestType;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.RestfulServer;
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.rest.server.interceptor.IServerInterceptor;
@ -166,13 +167,13 @@ abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding<Metho
response = (MethodOutcome) invokeServerMethod(params);
} catch (InternalErrorException e) {
ourLog.error("Internal error during method invocation", e);
EncodingEnum encoding = RestfulServer.determineResponseEncoding(theRequest.getServletRequest());
streamOperationOutcome(e, theServer, encoding, servletResponse, theRequest);
EncodingEnum encodingNotNull = RestfulServerUtils.determineResponseEncodingWithDefault(theServer, theRequest.getServletRequest());
streamOperationOutcome(e, theServer, encodingNotNull, servletResponse, theRequest);
return;
} catch (BaseServerResponseException e) {
ourLog.info("Exception during method invocation: " + e.getMessage());
EncodingEnum encoding = RestfulServer.determineResponseEncoding(theRequest.getServletRequest());
streamOperationOutcome(e, theServer, encoding, servletResponse, theRequest);
EncodingEnum encodingNotNull = RestfulServerUtils.determineResponseEncodingWithDefault(theServer, theRequest.getServletRequest());
streamOperationOutcome(e, theServer, encodingNotNull, servletResponse, theRequest);
return;
}
@ -234,11 +235,11 @@ abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding<Metho
theServer.addHeadersToResponse(servletResponse);
if (outcome != null) {
EncodingEnum encoding = RestfulServer.determineResponseEncoding(theRequest.getServletRequest());
EncodingEnum encoding = RestfulServerUtils.determineResponseEncodingWithDefault(theServer, theRequest.getServletRequest());
servletResponse.setContentType(encoding.getResourceContentType());
Writer writer = servletResponse.getWriter();
IParser parser = encoding.newParser(getContext());
parser.setPrettyPrint(RestfulServer.prettyPrintResponse(theRequest));
parser.setPrettyPrint(RestfulServerUtils.prettyPrintResponse(theRequest));
try {
parser.encodeResourceToWriter(response.getOperationOutcome(), writer);
} finally {
@ -268,7 +269,7 @@ abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding<Metho
* @throws IOException
*/
protected IResource parseIncomingServerResource(Request theRequest) throws IOException {
EncodingEnum encoding = RestfulServer.determineRequestEncoding(theRequest);
EncodingEnum encoding = RestfulServerUtils.determineRequestEncoding(theRequest);
IParser parser = encoding.newParser(getContext());
BufferedReader requestReader = theRequest.getServletRequest().getReader();
@ -302,15 +303,15 @@ abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding<Metho
return null;
}
protected void streamOperationOutcome(BaseServerResponseException theE, RestfulServer theServer, EncodingEnum theEncoding, HttpServletResponse theResponse, Request theRequest) throws IOException {
protected void streamOperationOutcome(BaseServerResponseException theE, RestfulServer theServer, EncodingEnum theEncodingNotNull, HttpServletResponse theResponse, Request theRequest) throws IOException {
theResponse.setStatus(theE.getStatusCode());
theServer.addHeadersToResponse(theResponse);
if (theE.getOperationOutcome() != null) {
theResponse.setContentType(theEncoding.getResourceContentType());
IParser parser = theEncoding.newParser(theServer.getFhirContext());
parser.setPrettyPrint(RestfulServer.prettyPrintResponse(theRequest));
theResponse.setContentType(theEncodingNotNull.getResourceContentType());
IParser parser = theEncodingNotNull.newParser(theServer.getFhirContext());
parser.setPrettyPrint(RestfulServerUtils.prettyPrintResponse(theRequest));
Writer writer = theResponse.getWriter();
try {
parser.encodeResourceToWriter(theE.getOperationOutcome(), writer);

View File

@ -49,6 +49,7 @@ import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.IBundleProvider;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.RestfulServer.NarrativeModeEnum;
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.rest.server.exceptions.InvalidRequestException;
@ -212,13 +213,13 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Obje
public void invokeServer(RestfulServer theServer, Request theRequest) throws BaseServerResponseException, IOException {
// Pretty print
boolean prettyPrint = RestfulServer.prettyPrintResponse(theRequest);
boolean prettyPrint = RestfulServerUtils.prettyPrintResponse(theRequest);
// Narrative mode
NarrativeModeEnum narrativeMode = RestfulServer.determineNarrativeMode(theRequest);
NarrativeModeEnum narrativeMode = RestfulServerUtils.determineNarrativeMode(theRequest);
// Determine response encoding
EncodingEnum responseEncoding = RestfulServer.determineResponseEncoding(theRequest.getServletRequest());
EncodingEnum responseEncoding = RestfulServerUtils.determineResponseEncodingNoDefault(theRequest.getServletRequest());
// Is this request coming from a browser
String uaHeader = theRequest.getServletRequest().getHeader("user-agent");
@ -238,7 +239,7 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Obje
}
}
Integer count = RestfulServer.extractCountParameter(theRequest.getServletRequest());
Integer count = RestfulServerUtils.extractCountParameter(theRequest.getServletRequest());
boolean respondGzip = theRequest.isRespondGzip();
@ -249,11 +250,11 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Obje
if (getMethodReturnType() == MethodReturnTypeEnum.BUNDLE_RESOURCE) {
IResource resource = (IResource) resultObj;
RestfulServer.streamResponseAsResource(theServer, response, resource, responseEncoding, prettyPrint, requestIsBrowser, narrativeMode, respondGzip, theRequest.getFhirServerBase());
RestfulServerUtils.streamResponseAsResource(theServer, response, resource, responseEncoding, prettyPrint, requestIsBrowser, narrativeMode, respondGzip, theRequest.getFhirServerBase());
break;
} else {
IBundleProvider result = (IBundleProvider) resultObj;
Bundle bundle = RestfulServer.createBundleFromBundleProvider(theServer, response, result, responseEncoding, theRequest.getFhirServerBase(), theRequest.getCompleteUrl(), prettyPrint, requestIsBrowser, narrativeMode, 0, count, null, getResponseBundleType());
Bundle bundle = RestfulServerUtils.createBundleFromBundleProvider(theServer, result, responseEncoding, theRequest.getFhirServerBase(), theRequest.getCompleteUrl(), prettyPrint, 0, count, null, getResponseBundleType());
for (int i = theServer.getInterceptors().size() - 1; i >= 0; i--) {
IServerInterceptor next = theServer.getInterceptors().get(i);
@ -263,7 +264,7 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Obje
}
}
RestfulServer.streamResponseAsBundle(theServer, response, bundle, responseEncoding, theRequest.getFhirServerBase(), prettyPrint, narrativeMode, respondGzip);
RestfulServerUtils.streamResponseAsBundle(theServer, response, bundle, responseEncoding, theRequest.getFhirServerBase(), prettyPrint, narrativeMode, respondGzip, requestIsBrowser);
break;
}
}
@ -285,7 +286,7 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Obje
}
}
RestfulServer.streamResponseAsResource(theServer, response, resource, responseEncoding, prettyPrint, requestIsBrowser, narrativeMode, respondGzip, theRequest.getFhirServerBase());
RestfulServerUtils.streamResponseAsResource(theServer, response, resource, responseEncoding, prettyPrint, requestIsBrowser, narrativeMode, respondGzip, theRequest.getFhirServerBase());
break;
}
}

View File

@ -48,6 +48,7 @@ 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.exceptions.BaseServerResponseException;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
@ -167,7 +168,7 @@ public class GetTagsMethodBinding extends BaseMethodBinding<TagList> {
}
}
EncodingEnum responseEncoding = RestfulServer.determineResponseEncoding(theRequest.getServletRequest());
EncodingEnum responseEncoding = RestfulServerUtils.determineResponseEncodingWithDefault(theServer, theRequest.getServletRequest());
HttpServletResponse response = theRequest.getServletResponse();
response.setContentType(responseEncoding.getResourceContentType());
@ -177,7 +178,7 @@ public class GetTagsMethodBinding extends BaseMethodBinding<TagList> {
theServer.addHeadersToResponse(response);
IParser parser = responseEncoding.newParser(getContext());
parser.setPrettyPrint(RestfulServer.prettyPrintResponse(theRequest));
parser.setPrettyPrint(RestfulServerUtils.prettyPrintResponse(theRequest));
PrintWriter writer = response.getWriter();
try {
parser.encodeTagListToWriter(resp, writer);

View File

@ -40,7 +40,7 @@ import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.annotation.TransactionParam;
import ca.uhn.fhir.rest.server.EncodingEnum;
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.rest.server.exceptions.InvalidRequestException;
@ -98,7 +98,7 @@ class TransactionParamBinder implements IParameter {
@Override
public Object translateQueryParametersIntoServerArgument(Request theRequest, Object theRequestContents) throws InternalErrorException, InvalidRequestException {
EncodingEnum encoding = RestfulServer.determineResponseEncoding(theRequest.getServletRequest());
EncodingEnum encoding = RestfulServerUtils.determineResponseEncodingWithDefault(theRequest.getServer(), theRequest.getServletRequest());
IParser parser = encoding.newParser(myContext);
BufferedReader reader;

View File

@ -23,30 +23,20 @@ package ca.uhn.fhir.rest.server;
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.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.UUID;
import java.util.zip.GZIPOutputStream;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -54,28 +44,15 @@ import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.http.client.utils.DateUtils;
import org.hl7.fhir.instance.model.IBaseResource;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.ProvidedResourceScanner;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.BundleEntry;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
import ca.uhn.fhir.model.api.Tag;
import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt;
import ca.uhn.fhir.model.base.resource.BaseBinary;
import ca.uhn.fhir.model.base.resource.BaseOperationOutcome;
import ca.uhn.fhir.model.base.resource.BaseOperationOutcome.BaseIssue;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.model.valueset.BundleEntrySearchModeEnum;
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.annotation.Destroy;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.method.BaseMethodBinding;
@ -101,10 +78,11 @@ public class RestfulServer extends HttpServlet {
* Default setting for {@link #setETagSupport(ETagSupportEnum) ETag Support}: {@link ETagSupportEnum#ENABLED}
*/
public static final ETagSupportEnum DEFAULT_ETAG_SUPPORT = ETagSupportEnum.ENABLED;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestfulServer.class);
static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestfulServer.class);
private static final long serialVersionUID = 1L;
private AddProfileTagEnum myAddProfileTag;
private EncodingEnum myDefaultResponseEncoding = EncodingEnum.XML;
private ETagSupportEnum myETagSupport = DEFAULT_ETAG_SUPPORT;
private FhirContext myFhirContext;
private String myImplementationDescription;
@ -120,6 +98,7 @@ public class RestfulServer extends HttpServlet {
private String myServerName = "HAPI FHIR Server";
/** This is configurable but by default we just use HAPI version */
private String myServerVersion = VersionUtil.getVersion();
private boolean myStarted;
private boolean myUseBrowserFriendlyContentTypes;
@ -138,8 +117,7 @@ public class RestfulServer extends HttpServlet {
/**
* This method is called prior to sending a response to incoming requests. It is used to add custom headers.
* <p>
* Use caution if overriding this method: it is recommended to call <code>super.addHeadersToResponse</code> to avoid
* inadvertantly disabling functionality.
* Use caution if overriding this method: it is recommended to call <code>super.addHeadersToResponse</code> to avoid inadvertantly disabling functionality.
* </p>
*/
public void addHeadersToResponse(HttpServletResponse theHttpResponse) {
@ -315,6 +293,14 @@ public class RestfulServer extends HttpServlet {
return myAddProfileTag;
}
/**
* Returns the default encoding to return (XML/JSON) if an incoming request does not specify a preference (either with the <code>_format</code> URL parameter, or with an <code>Accept</code> header
* in the request. The default is {@link EncodingEnum#XML}.
*/
public EncodingEnum getDefaultResponseEncoding() {
return myDefaultResponseEncoding;
}
/**
* Returns the server support for ETags (will not be <code>null</code>). Default is {@link #DEFAULT_ETAG_SUPPORT}
*/
@ -323,8 +309,8 @@ public class RestfulServer extends HttpServlet {
}
/**
* Gets the {@link FhirContext} associated with this server. For efficient processing, resource providers and plain
* providers should generally use this context if one is needed, as opposed to creating their own.
* Gets the {@link FhirContext} associated with this server. For efficient processing, resource providers and plain providers should generally use this context if one is needed, as opposed to
* creating their own.
*/
public FhirContext getFhirContext() {
return myFhirContext;
@ -354,6 +340,21 @@ public class RestfulServer extends HttpServlet {
return myPlainProviders;
}
/**
* Allows users of RestfulServer to override the getRequestPath method to let them build their custom request path implementation
*
* @param requestFullPath
* the full request path
* @param servletContextPath
* the servelet context path
* @param servletPath
* the servelet path
* @return created resource path
*/
protected String getRequestPath(String requestFullPath, String servletContextPath, String servletPath) {
return requestFullPath.substring(escapedLength(servletContextPath) + escapedLength(servletPath));
}
public Collection<ResourceBinding> getResourceBindings() {
return myResourceNameToProvider.values();
}
@ -366,8 +367,7 @@ public class RestfulServer extends HttpServlet {
}
/**
* Get the server address strategy, which is used to determine what base URL to provide clients to refer to this
* server. Defaults to an instance of {@link IncomingRequestAddressStrategy}
* Get the server address strategy, which is used to determine what base URL to provide clients to refer to this server. Defaults to an instance of {@link IncomingRequestAddressStrategy}
*/
public IServerAddressStrategy getServerAddressStrategy() {
return myServerAddressStrategy;
@ -384,11 +384,9 @@ public class RestfulServer extends HttpServlet {
}
/**
* Returns the server conformance provider, which is the provider that is used to generate the server's conformance
* (metadata) statement if one has been explicitly defined.
* Returns the server conformance provider, which is the provider that is used to generate the server's conformance (metadata) statement if one has been explicitly defined.
* <p>
* By default, the ServerConformanceProvider for the declared version of FHIR is used, but this can be changed, or
* set to <code>null</code> to use the appropriate one for the given FHIR version.
* By default, the ServerConformanceProvider for the declared version of FHIR is used, but this can be changed, or set to <code>null</code> to use the appropriate one for the given FHIR version.
* </p>
*/
public Object getServerConformanceProvider() {
@ -396,8 +394,7 @@ public class RestfulServer extends HttpServlet {
}
/**
* Gets the server's name, as exported in conformance profiles exported by the server. This is informational only,
* but can be helpful to set with something appropriate.
* Gets the server's name, as exported in conformance profiles exported by the server. This is informational only, but can be helpful to set with something appropriate.
*
* @see RestfulServer#setServerName(String)
*/
@ -410,8 +407,7 @@ public class RestfulServer extends HttpServlet {
}
/**
* Gets the server's version, as exported in conformance profiles exported by the server. This is informational
* only, but can be helpful to set with something appropriate.
* Gets the server's version, as exported in conformance profiles exported by the server. This is informational only, but can be helpful to set with something appropriate.
*/
public String getServerVersion() {
return myServerVersion;
@ -430,27 +426,28 @@ public class RestfulServer extends HttpServlet {
return;
}
Integer count = extractCountParameter(theRequest.getServletRequest());
Integer count = RestfulServerUtils.extractCountParameter(theRequest.getServletRequest());
if (count == null) {
count = getPagingProvider().getDefaultPageSize();
} else if (count > getPagingProvider().getMaximumPageSize()) {
count = getPagingProvider().getMaximumPageSize();
}
Integer offsetI = tryToExtractNamedParameter(theRequest.getServletRequest(), Constants.PARAM_PAGINGOFFSET);
Integer offsetI = RestfulServerUtils.tryToExtractNamedParameter(theRequest.getServletRequest(), Constants.PARAM_PAGINGOFFSET);
if (offsetI == null || offsetI < 0) {
offsetI = 0;
}
int start = Math.min(offsetI, resultList.size() - 1);
EncodingEnum responseEncoding = determineResponseEncoding(theRequest.getServletRequest());
boolean prettyPrint = prettyPrintResponse(theRequest);
EncodingEnum responseEncoding = RestfulServerUtils.determineResponseEncodingNoDefault(theRequest.getServletRequest());
boolean prettyPrint = RestfulServerUtils.prettyPrintResponse(theRequest);
boolean requestIsBrowser = requestIsBrowser(theRequest.getServletRequest());
NarrativeModeEnum narrativeMode = determineNarrativeMode(theRequest);
NarrativeModeEnum narrativeMode = RestfulServerUtils.determineNarrativeMode(theRequest);
boolean respondGzip = theRequest.isRespondGzip();
Bundle bundle = createBundleFromBundleProvider(this, theResponse, resultList, responseEncoding, theRequest.getFhirServerBase(), theRequest.getCompleteUrl(), prettyPrint, requestIsBrowser, narrativeMode, start, count, thePagingAction, null);
Bundle bundle = RestfulServerUtils.createBundleFromBundleProvider(this, resultList, responseEncoding, theRequest.getFhirServerBase(), theRequest.getCompleteUrl(), prettyPrint,
start, count, thePagingAction, null);
for (int i = getInterceptors().size() - 1; i >= 0; i--) {
IServerInterceptor next = getInterceptors().get(i);
@ -461,7 +458,7 @@ public class RestfulServer extends HttpServlet {
}
}
streamResponseAsBundle(this, theResponse, bundle, responseEncoding, theRequest.getFhirServerBase(), prettyPrint, narrativeMode, respondGzip);
RestfulServerUtils.streamResponseAsBundle(this, theResponse, bundle, responseEncoding, theRequest.getFhirServerBase(), prettyPrint, narrativeMode, respondGzip, requestIsBrowser);
}
@ -684,8 +681,7 @@ public class RestfulServer extends HttpServlet {
} catch (Throwable e) {
/*
* We have caught an exception while handling an incoming server request. Start by notifying the
* interceptors..
* We have caught an exception while handling an incoming server request. Start by notifying the interceptors..
*/
for (int i = getInterceptors().size() - 1; i >= 0; i--) {
IServerInterceptor next = getInterceptors().get(i);
@ -740,7 +736,8 @@ public class RestfulServer extends HttpServlet {
ourLog.error("Unknown error during processing", e);
}
streamResponseAsResource(this, theResponse, oo, determineResponseEncoding(theRequest), true, requestIsBrowser, NarrativeModeEnum.NORMAL, statusCode, false, fhirServerBase);
RestfulServerUtils.streamResponseAsResource(this, theResponse, oo, RestfulServerUtils.determineResponseEncodingNoDefault(theRequest), true, requestIsBrowser, NarrativeModeEnum.NORMAL,
statusCode, false, fhirServerBase);
theResponse.setStatus(statusCode);
addHeadersToResponse(theResponse);
@ -753,9 +750,8 @@ public class RestfulServer extends HttpServlet {
}
/**
* Initializes the server. Note that this method is final to avoid accidentally introducing bugs in implementations,
* but subclasses may put initialization code in {@link #initialize()}, which is called immediately before beginning
* initialization of the restful server's internal init.
* Initializes the server. Note that this method is final to avoid accidentally introducing bugs in implementations, but subclasses may put initialization code in {@link #initialize()}, which is
* called immediately before beginning initialization of the restful server's internal init.
*/
@Override
public final void init() throws ServletException {
@ -778,7 +774,8 @@ public class RestfulServer extends HttpServlet {
String resourceName = myFhirContext.getResourceDefinition(resourceType).getName();
if (typeToProvider.containsKey(resourceName)) {
throw new ServletException("Multiple resource providers return resource type[" + resourceName + "]: First[" + typeToProvider.get(resourceName).getClass().getCanonicalName() + "] and Second[" + nextProvider.getClass().getCanonicalName() + "]");
throw new ServletException("Multiple resource providers return resource type[" + resourceName + "]: First[" + typeToProvider.get(resourceName).getClass().getCanonicalName()
+ "] and Second[" + nextProvider.getClass().getCanonicalName() + "]");
}
typeToProvider.put(resourceName, nextProvider);
providedResourceScanner.scanForProvidedResources(nextProvider);
@ -816,8 +813,7 @@ public class RestfulServer extends HttpServlet {
}
/**
* This method may be overridden by subclasses to do perform initialization that needs to be performed prior to the
* server being used.
* This method may be overridden by subclasses to do perform initialization that needs to be performed prior to the server being used.
*/
protected void initialize() throws ServletException {
// nothing by default
@ -864,9 +860,8 @@ public class RestfulServer extends HttpServlet {
}
/**
* Sets the profile tagging behaviour for the server. When set to a value other than {@link AddProfileTagEnum#NEVER}
* (which is the default), the server will automatically add a profile tag based on the class of the resource(s)
* being returned.
* Sets the profile tagging behaviour for the server. When set to a value other than {@link AddProfileTagEnum#NEVER} (which is the default), the server will automatically add a profile tag based
* on the class of the resource(s) being returned.
*
* @param theAddProfileTag
* The behaviour enum (must not be null)
@ -877,8 +872,16 @@ public class RestfulServer extends HttpServlet {
}
/**
* Sets (enables/disables) the server support for ETags. Must not be <code>null</code>. Default is
* {@link #DEFAULT_ETAG_SUPPORT}
* Sets the default encoding to return (XML/JSON) if an incoming request does not specify a preference (either with the <code>_format</code> URL parameter, or with an <code>Accept</code> header in
* the request. The default is {@link EncodingEnum#XML}.
*/
public void setDefaultResponseEncoding(EncodingEnum theDefaultResponseEncoding) {
Validate.notNull(theDefaultResponseEncoding, "theDefaultResponseEncoding can not be null");
myDefaultResponseEncoding = theDefaultResponseEncoding;
}
/**
* Sets (enables/disables) the server support for ETags. Must not be <code>null</code>. Default is {@link #DEFAULT_ETAG_SUPPORT}
*
* @param theETagSupport
* The ETag support mode
@ -974,8 +977,7 @@ public class RestfulServer extends HttpServlet {
}
/**
* Provide a server address strategy, which is used to determine what base URL to provide clients to refer to this
* server. Defaults to an instance of {@link IncomingRequestAddressStrategy}
* Provide a server address strategy, which is used to determine what base URL to provide clients to refer to this server. Defaults to an instance of {@link IncomingRequestAddressStrategy}
*/
public void setServerAddressStrategy(IServerAddressStrategy theServerAddressStrategy) {
Validate.notNull(theServerAddressStrategy, "Server address strategy can not be null");
@ -983,17 +985,15 @@ public class RestfulServer extends HttpServlet {
}
/**
* Returns the server conformance provider, which is the provider that is used to generate the server's conformance
* (metadata) statement.
* Returns the server conformance provider, which is the provider that is used to generate the server's conformance (metadata) statement.
* <p>
* By default, the ServerConformanceProvider implementation for the declared version of FHIR is used, but this can
* be changed, or set to <code>null</code> if you do not wish to export a conformance statement.
* By default, the ServerConformanceProvider implementation for the declared version of FHIR is used, but this can be changed, or set to <code>null</code> if you do not wish to export a
* conformance statement.
* </p>
* Note that this method can only be called before the server is initialized.
*
* @throws IllegalStateException
* Note that this method can only be called prior to {@link #init() initialization} and will throw an
* {@link IllegalStateException} if called after that.
* Note that this method can only be called prior to {@link #init() initialization} and will throw an {@link IllegalStateException} if called after that.
*/
public void setServerConformanceProvider(Object theServerConformanceProvider) {
if (myStarted) {
@ -1003,24 +1003,22 @@ public class RestfulServer extends HttpServlet {
}
/**
* Sets the server's name, as exported in conformance profiles exported by the server. This is informational only,
* but can be helpful to set with something appropriate.
* Sets the server's name, as exported in conformance profiles exported by the server. This is informational only, but can be helpful to set with something appropriate.
*/
public void setServerName(String theServerName) {
myServerName = theServerName;
}
/**
* Gets the server's version, as exported in conformance profiles exported by the server. This is informational
* only, but can be helpful to set with something appropriate.
* Gets the server's version, as exported in conformance profiles exported by the server. This is informational only, but can be helpful to set with something appropriate.
*/
public void setServerVersion(String theServerVersion) {
myServerVersion = theServerVersion;
}
/**
* If set to <code>true</code> (default is false), the server will use browser friendly content-types (instead of
* standard FHIR ones) when it detects that the request is coming from a browser instead of a FHIR
* If set to <code>true</code> (default is false), the server will use browser friendly content-types (instead of standard FHIR ones) when it detects that the request is coming from a browser
* instead of a FHIR
*/
public void setUseBrowserFriendlyContentTypes(boolean theUseBrowserFriendlyContentTypes) {
myUseBrowserFriendlyContentTypes = theUseBrowserFriendlyContentTypes;
@ -1039,491 +1037,6 @@ public class RestfulServer extends HttpServlet {
theResponse.getWriter().write(theException.getMessage());
}
private static void addProfileToBundleEntry(FhirContext theContext, IResource theResource, String theServerBase) {
TagList tl = ResourceMetadataKeyEnum.TAG_LIST.get(theResource);
if (tl == null) {
tl = new TagList();
ResourceMetadataKeyEnum.TAG_LIST.put(theResource, tl);
}
RuntimeResourceDefinition nextDef = theContext.getResourceDefinition(theResource);
String profile = nextDef.getResourceProfile(theServerBase);
if (isNotBlank(profile)) {
tl.add(new Tag(Tag.HL7_ORG_PROFILE_TAG, profile, null));
}
}
public static Bundle createBundleFromBundleProvider(RestfulServer theServer, HttpServletResponse theHttpResponse, IBundleProvider theResult, EncodingEnum theResponseEncoding, String theServerBase, String theCompleteUrl, boolean thePrettyPrint, boolean theRequestIsBrowser,
NarrativeModeEnum theNarrativeMode, int theOffset, Integer theLimit, String theSearchId, BundleTypeEnum theBundleType) {
theHttpResponse.setStatus(200);
if (theRequestIsBrowser && theServer.isUseBrowserFriendlyContentTypes()) {
theHttpResponse.setContentType(theResponseEncoding.getBrowserFriendlyBundleContentType());
} else if (theNarrativeMode == NarrativeModeEnum.ONLY) {
theHttpResponse.setContentType(Constants.CT_HTML);
} else {
theHttpResponse.setContentType(theResponseEncoding.getBundleContentType());
}
theHttpResponse.setCharacterEncoding(Constants.CHARSET_UTF_8);
theServer.addHeadersToResponse(theHttpResponse);
int numToReturn;
String searchId = null;
List<IResource> resourceList;
if (theServer.getPagingProvider() == null) {
numToReturn = theResult.size();
resourceList = theResult.getResources(0, numToReturn);
validateResourceListNotNull(resourceList);
} else {
IPagingProvider pagingProvider = theServer.getPagingProvider();
if (theLimit == null) {
numToReturn = pagingProvider.getDefaultPageSize();
} else {
numToReturn = Math.min(pagingProvider.getMaximumPageSize(), theLimit);
}
numToReturn = Math.min(numToReturn, theResult.size() - theOffset);
resourceList = theResult.getResources(theOffset, numToReturn + theOffset);
validateResourceListNotNull(resourceList);
if (theSearchId != null) {
searchId = theSearchId;
} else {
if (theResult.size() > numToReturn) {
searchId = pagingProvider.storeResultList(theResult);
Validate.notNull(searchId, "Paging provider returned null searchId");
}
}
}
for (IResource next : resourceList) {
if (next.getId() == null || next.getId().isEmpty()) {
if (!(next instanceof BaseOperationOutcome)) {
throw new InternalErrorException("Server method returned resource of type[" + next.getClass().getSimpleName() + "] with no ID specified (IResource#setId(IdDt) must be called)");
}
}
}
if (theServer.getAddProfileTag() != AddProfileTagEnum.NEVER) {
for (IResource nextRes : resourceList) {
RuntimeResourceDefinition def = theServer.getFhirContext().getResourceDefinition(nextRes);
if (theServer.getAddProfileTag() == AddProfileTagEnum.ALWAYS || !def.isStandardProfile()) {
addProfileToBundleEntry(theServer.getFhirContext(), nextRes, theServerBase);
}
}
}
Bundle bundle = createBundleFromResourceList(theServer.getFhirContext(), theServer.getServerName(), resourceList, theServerBase, theCompleteUrl, theResult.size(), theBundleType);
bundle.setPublished(theResult.getPublished());
if (theServer.getPagingProvider() != null) {
int limit;
limit = theLimit != null ? theLimit : theServer.getPagingProvider().getDefaultPageSize();
limit = Math.min(limit, theServer.getPagingProvider().getMaximumPageSize());
if (searchId != null) {
if (theOffset + numToReturn < theResult.size()) {
bundle.getLinkNext().setValue(createPagingLink(theServerBase, searchId, theOffset + numToReturn, numToReturn, theResponseEncoding, thePrettyPrint));
}
if (theOffset > 0) {
int start = Math.max(0, theOffset - limit);
bundle.getLinkPrevious().setValue(createPagingLink(theServerBase, searchId, start, limit, theResponseEncoding, thePrettyPrint));
}
}
}
return bundle;
}
public static Bundle createBundleFromResourceList(FhirContext theContext, String theAuthor, List<IResource> theResult, String theServerBase, String theCompleteUrl, int theTotalResults, BundleTypeEnum theBundleType) {
Bundle bundle = new Bundle();
bundle.getAuthorName().setValue(theAuthor);
bundle.getBundleId().setValue(UUID.randomUUID().toString());
bundle.getPublished().setToCurrentTimeInLocalTimeZone();
bundle.getLinkBase().setValue(theServerBase);
bundle.getLinkSelf().setValue(theCompleteUrl);
bundle.getType().setValueAsEnum(theBundleType);
List<IResource> includedResources = new ArrayList<IResource>();
Set<IdDt> addedResourceIds = new HashSet<IdDt>();
for (IResource next : theResult) {
if (next.getId().isEmpty() == false) {
addedResourceIds.add(next.getId());
}
}
for (IResource next : theResult) {
Set<String> containedIds = new HashSet<String>();
for (IResource nextContained : next.getContained().getContainedResources()) {
if (nextContained.getId().isEmpty() == false) {
containedIds.add(nextContained.getId().getValue());
}
}
if (theContext.getNarrativeGenerator() != null) {
String title = theContext.getNarrativeGenerator().generateTitle(next);
ourLog.trace("Narrative generator created title: {}", title);
if (StringUtils.isNotBlank(title)) {
ResourceMetadataKeyEnum.TITLE.put(next, title);
}
} else {
ourLog.trace("No narrative generator specified");
}
List<BaseResourceReferenceDt> references = theContext.newTerser().getAllPopulatedChildElementsOfType(next, BaseResourceReferenceDt.class);
do {
List<IResource> addedResourcesThisPass = new ArrayList<IResource>();
for (BaseResourceReferenceDt nextRef : references) {
IResource nextRes = nextRef.getResource();
if (nextRes != null) {
if (nextRes.getId().hasIdPart()) {
if (containedIds.contains(nextRes.getId().getValue())) {
// Don't add contained IDs as top level resources
continue;
}
IdDt id = nextRes.getId();
if (id.hasResourceType() == false) {
String resName = theContext.getResourceDefinition(nextRes).getName();
id = id.withResourceType(resName);
}
if (!addedResourceIds.contains(id)) {
addedResourceIds.add(id);
addedResourcesThisPass.add(nextRes);
}
}
}
}
// Linked resources may themselves have linked resources
references = new ArrayList<BaseResourceReferenceDt>();
for (IResource iResource : addedResourcesThisPass) {
List<BaseResourceReferenceDt> newReferences = theContext.newTerser().getAllPopulatedChildElementsOfType(iResource, BaseResourceReferenceDt.class);
references.addAll(newReferences);
}
includedResources.addAll(addedResourcesThisPass);
} while (references.isEmpty() == false);
bundle.addResource(next, theContext, theServerBase);
}
/*
* Actually add the resources to the bundle
*/
for (IResource next : includedResources) {
BundleEntry entry = bundle.addResource(next, theContext, theServerBase);
if (theContext.getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU1)) {
if (entry.getSearchMode().isEmpty()) {
entry.getSearchMode().setValueAsEnum(BundleEntrySearchModeEnum.INCLUDE);
}
}
}
bundle.getTotalResults().setValue(theTotalResults);
return bundle;
}
public static String createPagingLink(String theServerBase, String theSearchId, int theOffset, int theCount, EncodingEnum theResponseEncoding, boolean thePrettyPrint) {
StringBuilder b = new StringBuilder();
b.append(theServerBase);
b.append('?');
b.append(Constants.PARAM_PAGINGACTION);
b.append('=');
try {
b.append(URLEncoder.encode(theSearchId, "UTF-8"));
} catch (UnsupportedEncodingException e) {
throw new Error("UTF-8 not supported", e);// should not happen
}
b.append('&');
b.append(Constants.PARAM_PAGINGOFFSET);
b.append('=');
b.append(theOffset);
b.append('&');
b.append(Constants.PARAM_COUNT);
b.append('=');
b.append(theCount);
b.append('&');
b.append(Constants.PARAM_FORMAT);
b.append('=');
b.append(theResponseEncoding.getRequestContentType());
if (thePrettyPrint) {
b.append('&');
b.append(Constants.PARAM_PRETTY);
b.append('=');
b.append(Constants.PARAM_PRETTY_VALUE_TRUE);
}
return b.toString();
}
public static NarrativeModeEnum determineNarrativeMode(RequestDetails theRequest) {
Map<String, String[]> requestParams = theRequest.getParameters();
String[] narrative = requestParams.remove(Constants.PARAM_NARRATIVE);
NarrativeModeEnum narrativeMode = null;
if (narrative != null && narrative.length > 0) {
narrativeMode = NarrativeModeEnum.valueOfCaseInsensitive(narrative[0]);
}
if (narrativeMode == null) {
narrativeMode = NarrativeModeEnum.NORMAL;
}
return narrativeMode;
}
public static EncodingEnum determineRequestEncoding(Request theReq) {
Enumeration<String> acceptValues = theReq.getServletRequest().getHeaders(Constants.HEADER_CONTENT_TYPE);
if (acceptValues != null) {
while (acceptValues.hasMoreElements()) {
String nextAcceptHeaderValue = acceptValues.nextElement();
if (nextAcceptHeaderValue != null && isNotBlank(nextAcceptHeaderValue)) {
for (String nextPart : nextAcceptHeaderValue.split(",")) {
int scIdx = nextPart.indexOf(';');
if (scIdx == 0) {
continue;
}
if (scIdx != -1) {
nextPart = nextPart.substring(0, scIdx);
}
nextPart = nextPart.trim();
EncodingEnum retVal = Constants.FORMAT_VAL_TO_ENCODING.get(nextPart);
if (retVal != null) {
return retVal;
}
}
}
}
}
return EncodingEnum.XML;
}
/**
* Determine whether a response should be given in JSON or XML format based on the incoming HttpServletRequest's
* <code>"_format"</code> parameter and <code>"Accept:"</code> HTTP header.
*/
public static EncodingEnum determineResponseEncoding(HttpServletRequest theReq) {
String[] format = theReq.getParameterValues(Constants.PARAM_FORMAT);
if (format != null) {
for (String nextFormat : format) {
EncodingEnum retVal = Constants.FORMAT_VAL_TO_ENCODING.get(nextFormat);
if (retVal != null) {
return retVal;
}
}
}
Enumeration<String> acceptValues = theReq.getHeaders(Constants.HEADER_ACCEPT);
if (acceptValues != null) {
while (acceptValues.hasMoreElements()) {
String nextAcceptHeaderValue = acceptValues.nextElement();
if (nextAcceptHeaderValue != null && isNotBlank(nextAcceptHeaderValue)) {
for (String nextPart : nextAcceptHeaderValue.split(",")) {
int scIdx = nextPart.indexOf(';');
if (scIdx == 0) {
continue;
}
if (scIdx != -1) {
nextPart = nextPart.substring(0, scIdx);
}
nextPart = nextPart.trim();
EncodingEnum retVal = Constants.FORMAT_VAL_TO_ENCODING.get(nextPart);
if (retVal != null) {
return retVal;
}
}
}
}
}
return EncodingEnum.XML;
}
public static Integer extractCountParameter(HttpServletRequest theRequest) {
String name = Constants.PARAM_COUNT;
return tryToExtractNamedParameter(theRequest, name);
}
public static IParser getNewParser(FhirContext theContext, EncodingEnum theResponseEncoding, boolean thePrettyPrint, NarrativeModeEnum theNarrativeMode) {
IParser parser;
switch (theResponseEncoding) {
case JSON:
parser = theContext.newJsonParser();
break;
case XML:
default:
parser = theContext.newXmlParser();
break;
}
return parser.setPrettyPrint(thePrettyPrint).setSuppressNarratives(theNarrativeMode == NarrativeModeEnum.SUPPRESS);
}
private 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 boolean prettyPrintResponse(Request theRequest) {
Map<String, String[]> requestParams = theRequest.getParameters();
String[] pretty = requestParams.remove(Constants.PARAM_PRETTY);
boolean prettyPrint;
if (pretty != null && pretty.length > 0) {
if (Constants.PARAM_PRETTY_VALUE_TRUE.equals(pretty[0])) {
prettyPrint = true;
} else {
prettyPrint = false;
}
} else {
prettyPrint = false;
Enumeration<String> acceptValues = theRequest.getServletRequest().getHeaders(Constants.HEADER_ACCEPT);
if (acceptValues != null) {
while (acceptValues.hasMoreElements()) {
String nextAcceptHeaderValue = acceptValues.nextElement();
if (nextAcceptHeaderValue.contains("pretty=true")) {
prettyPrint = true;
}
}
}
}
return prettyPrint;
}
public static void streamResponseAsBundle(RestfulServer theServer, HttpServletResponse theHttpResponse, Bundle bundle, EncodingEnum theResponseEncoding, String theServerBase, boolean thePrettyPrint, NarrativeModeEnum theNarrativeMode, boolean theRespondGzip) throws IOException {
assert !theServerBase.endsWith("/");
Writer writer = getWriter(theHttpResponse, theRespondGzip);
try {
if (theNarrativeMode == NarrativeModeEnum.ONLY) {
for (IResource next : bundle.toListOfResources()) {
writer.append(next.getText().getDiv().getValueAsString());
writer.append("<hr/>");
}
} else {
RestfulServer.getNewParser(theServer.getFhirContext(), theResponseEncoding, thePrettyPrint, theNarrativeMode).encodeBundleToWriter(bundle, writer);
}
} finally {
writer.close();
}
}
public static void streamResponseAsResource(RestfulServer theServer, HttpServletResponse theHttpResponse, IResource theResource, EncodingEnum theResponseEncoding, boolean thePrettyPrint, boolean theRequestIsBrowser, NarrativeModeEnum theNarrativeMode, boolean theRespondGzip, String theServerBase)
throws IOException {
int stausCode = 200;
streamResponseAsResource(theServer, theHttpResponse, theResource, theResponseEncoding, thePrettyPrint, theRequestIsBrowser, theNarrativeMode, stausCode, theRespondGzip, theServerBase);
}
private static void streamResponseAsResource(RestfulServer theServer, HttpServletResponse theHttpResponse, IResource theResource, EncodingEnum theResponseEncoding, boolean thePrettyPrint, boolean theRequestIsBrowser, NarrativeModeEnum theNarrativeMode, int stausCode, boolean theRespondGzip,
String theServerBase) throws IOException {
theHttpResponse.setStatus(stausCode);
if (theResource.getId() != null && theResource.getId().hasIdPart() && isNotBlank(theServerBase)) {
String resName = theServer.getFhirContext().getResourceDefinition(theResource).getName();
IdDt fullId = theResource.getId().withServerBase(theServerBase, resName);
theHttpResponse.addHeader(Constants.HEADER_CONTENT_LOCATION, fullId.getValue());
}
if (theServer.getETagSupport() == ETagSupportEnum.ENABLED) {
if (theResource.getId().hasVersionIdPart()) {
theHttpResponse.addHeader(Constants.HEADER_ETAG, "W/\"" + theResource.getId().getVersionIdPart() + '"');
}
}
if (theServer.getAddProfileTag() != AddProfileTagEnum.NEVER) {
RuntimeResourceDefinition def = theServer.getFhirContext().getResourceDefinition(theResource);
if (theServer.getAddProfileTag() == AddProfileTagEnum.ALWAYS || !def.isStandardProfile()) {
addProfileToBundleEntry(theServer.getFhirContext(), theResource, theServerBase);
}
}
if (theResource instanceof BaseBinary) {
BaseBinary bin = (BaseBinary) theResource;
if (isNotBlank(bin.getContentType())) {
theHttpResponse.setContentType(bin.getContentType());
} else {
theHttpResponse.setContentType(Constants.CT_OCTET_STREAM);
}
if (bin.getContent() == null || bin.getContent().length == 0) {
return;
}
theHttpResponse.addHeader(Constants.HEADER_CONTENT_DISPOSITION, "Attachment;");
theHttpResponse.setContentLength(bin.getContent().length);
ServletOutputStream oos = theHttpResponse.getOutputStream();
oos.write(bin.getContent());
oos.close();
return;
}
if (theRequestIsBrowser && theServer.isUseBrowserFriendlyContentTypes()) {
theHttpResponse.setContentType(theResponseEncoding.getBrowserFriendlyBundleContentType());
} else if (theNarrativeMode == NarrativeModeEnum.ONLY) {
theHttpResponse.setContentType(Constants.CT_HTML);
} else {
theHttpResponse.setContentType(theResponseEncoding.getResourceContentType());
}
theHttpResponse.setCharacterEncoding(Constants.CHARSET_UTF_8);
theServer.addHeadersToResponse(theHttpResponse);
InstantDt lastUpdated = ResourceMetadataKeyEnum.UPDATED.get(theResource);
if (lastUpdated != null && lastUpdated.isEmpty() == false) {
theHttpResponse.addHeader(Constants.HEADER_LAST_MODIFIED, DateUtils.formatDate(lastUpdated.getValue()));
}
TagList list = (TagList) 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());
}
}
}
Writer writer = getWriter(theHttpResponse, theRespondGzip);
try {
if (theNarrativeMode == NarrativeModeEnum.ONLY) {
writer.append(theResource.getText().getDiv().getValueAsString());
} else {
RestfulServer.getNewParser(theServer.getFhirContext(), theResponseEncoding, thePrettyPrint, theNarrativeMode).encodeResourceToWriter(theResource, writer);
}
} finally {
writer.close();
}
}
private static Integer tryToExtractNamedParameter(HttpServletRequest theRequest, String name) {
String countString = theRequest.getParameter(name);
Integer count = null;
if (isNotBlank(countString)) {
try {
count = Integer.parseInt(countString);
} catch (NumberFormatException e) {
ourLog.debug("Failed to parse _count value '{}': {}", countString, e);
}
}
return count;
}
private static void validateResourceListNotNull(List<IResource> theResourceList) {
if (theResourceList == null) {
throw new InternalErrorException("IBundleProvider returned a null list of resources - This is not allowed");
}
}
public enum NarrativeModeEnum {
NORMAL, ONLY, SUPPRESS;
@ -1531,20 +1044,4 @@ public class RestfulServer extends HttpServlet {
return valueOf(NarrativeModeEnum.class, theCode.toUpperCase());
}
}
/**
* Allows users of RestfulServer to override the getRequestPath method to let them build their custom request path
* implementation
*
* @param requestFullPath
* the full request path
* @param servletContextPath
* the servelet context path
* @param servletPath
* the servelet path
* @return created resource path
*/
protected String getRequestPath(String requestFullPath, String servletContextPath, String servletPath) {
return requestFullPath.substring(escapedLength(servletContextPath) + escapedLength(servletPath));
}
}

View File

@ -0,0 +1,551 @@
package ca.uhn.fhir.rest.server;
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;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
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.commons.lang3.Validate;
import org.apache.http.client.utils.DateUtils;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
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.Tag;
import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt;
import ca.uhn.fhir.model.base.resource.BaseBinary;
import ca.uhn.fhir.model.base.resource.BaseOperationOutcome;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.model.valueset.BundleEntrySearchModeEnum;
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.method.Request;
import ca.uhn.fhir.rest.method.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
public class RestfulServerUtils {
static Integer tryToExtractNamedParameter(HttpServletRequest theRequest, String name) {
String countString = theRequest.getParameter(name);
Integer count = null;
if (isNotBlank(countString)) {
try {
count = Integer.parseInt(countString);
} catch (NumberFormatException e) {
RestfulServer.ourLog.debug("Failed to parse _count value '{}': {}", countString, e);
}
}
return count;
}
static void streamResponseAsResource(RestfulServer theServer, HttpServletResponse theHttpResponse, IResource theResource, EncodingEnum theResponseEncoding, boolean thePrettyPrint, boolean theRequestIsBrowser, RestfulServer.NarrativeModeEnum theNarrativeMode, int stausCode, boolean theRespondGzip,
String theServerBase) throws IOException {
theHttpResponse.setStatus(stausCode);
if (theResource.getId() != null && theResource.getId().hasIdPart() && isNotBlank(theServerBase)) {
String resName = theServer.getFhirContext().getResourceDefinition(theResource).getName();
IdDt fullId = theResource.getId().withServerBase(theServerBase, resName);
theHttpResponse.addHeader(Constants.HEADER_CONTENT_LOCATION, fullId.getValue());
}
if (theServer.getETagSupport() == ETagSupportEnum.ENABLED) {
if (theResource.getId().hasVersionIdPart()) {
theHttpResponse.addHeader(Constants.HEADER_ETAG, "W/\"" + theResource.getId().getVersionIdPart() + '"');
}
}
if (theServer.getAddProfileTag() != AddProfileTagEnum.NEVER) {
RuntimeResourceDefinition def = theServer.getFhirContext().getResourceDefinition(theResource);
if (theServer.getAddProfileTag() == AddProfileTagEnum.ALWAYS || !def.isStandardProfile()) {
addProfileToBundleEntry(theServer.getFhirContext(), theResource, theServerBase);
}
}
if (theResource instanceof BaseBinary && theResponseEncoding == null) {
BaseBinary bin = (BaseBinary) theResource;
if (isNotBlank(bin.getContentType())) {
theHttpResponse.setContentType(bin.getContentType());
} else {
theHttpResponse.setContentType(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;");
theHttpResponse.setContentLength(bin.getContent().length);
ServletOutputStream oos = theHttpResponse.getOutputStream();
oos.write(bin.getContent());
oos.close();
return;
}
EncodingEnum responseEncoding = theResponseEncoding != null ? theResponseEncoding : theServer.getDefaultResponseEncoding();
if (theRequestIsBrowser && theServer.isUseBrowserFriendlyContentTypes()) {
theHttpResponse.setContentType(responseEncoding.getBrowserFriendlyBundleContentType());
} else if (theNarrativeMode == RestfulServer.NarrativeModeEnum.ONLY) {
theHttpResponse.setContentType(Constants.CT_HTML);
} else {
theHttpResponse.setContentType(responseEncoding.getResourceContentType());
}
theHttpResponse.setCharacterEncoding(Constants.CHARSET_UTF_8);
theServer.addHeadersToResponse(theHttpResponse);
InstantDt lastUpdated = ResourceMetadataKeyEnum.UPDATED.get(theResource);
if (lastUpdated != null && lastUpdated.isEmpty() == false) {
theHttpResponse.addHeader(Constants.HEADER_LAST_MODIFIED, DateUtils.formatDate(lastUpdated.getValue()));
}
TagList list = (TagList) 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());
}
}
}
Writer writer = getWriter(theHttpResponse, theRespondGzip);
try {
if (theNarrativeMode == RestfulServer.NarrativeModeEnum.ONLY) {
writer.append(theResource.getText().getDiv().getValueAsString());
} else {
getNewParser(theServer.getFhirContext(), responseEncoding, thePrettyPrint, theNarrativeMode).encodeResourceToWriter(theResource, writer);
}
} finally {
writer.close();
}
}
public static boolean prettyPrintResponse(Request theRequest) {
Map<String, String[]> requestParams = theRequest.getParameters();
String[] pretty = requestParams.remove(Constants.PARAM_PRETTY);
boolean prettyPrint;
if (pretty != null && pretty.length > 0) {
if (Constants.PARAM_PRETTY_VALUE_TRUE.equals(pretty[0])) {
prettyPrint = true;
} else {
prettyPrint = false;
}
} else {
prettyPrint = false;
Enumeration<String> acceptValues = theRequest.getServletRequest().getHeaders(Constants.HEADER_ACCEPT);
if (acceptValues != null) {
while (acceptValues.hasMoreElements()) {
String nextAcceptHeaderValue = acceptValues.nextElement();
if (nextAcceptHeaderValue.contains("pretty=true")) {
prettyPrint = true;
}
}
}
}
return prettyPrint;
}
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 EncodingEnum determineRequestEncoding(Request theReq) {
Enumeration<String> acceptValues = theReq.getServletRequest().getHeaders(Constants.HEADER_CONTENT_TYPE);
if (acceptValues != null) {
while (acceptValues.hasMoreElements()) {
String nextAcceptHeaderValue = acceptValues.nextElement();
if (nextAcceptHeaderValue != null && isNotBlank(nextAcceptHeaderValue)) {
for (String nextPart : nextAcceptHeaderValue.split(",")) {
int scIdx = nextPart.indexOf(';');
if (scIdx == 0) {
continue;
}
if (scIdx != -1) {
nextPart = nextPart.substring(0, scIdx);
}
nextPart = nextPart.trim();
EncodingEnum retVal = Constants.FORMAT_VAL_TO_ENCODING.get(nextPart);
if (retVal != null) {
return retVal;
}
}
}
}
}
return EncodingEnum.XML;
}
public static String createPagingLink(String theServerBase, String theSearchId, int theOffset, int theCount, EncodingEnum theResponseEncoding, boolean thePrettyPrint) {
StringBuilder b = new StringBuilder();
b.append(theServerBase);
b.append('?');
b.append(Constants.PARAM_PAGINGACTION);
b.append('=');
try {
b.append(URLEncoder.encode(theSearchId, "UTF-8"));
} catch (UnsupportedEncodingException e) {
throw new Error("UTF-8 not supported", e);// should not happen
}
b.append('&');
b.append(Constants.PARAM_PAGINGOFFSET);
b.append('=');
b.append(theOffset);
b.append('&');
b.append(Constants.PARAM_COUNT);
b.append('=');
b.append(theCount);
if (theResponseEncoding != null) {
b.append('&');
b.append(Constants.PARAM_FORMAT);
b.append('=');
b.append(theResponseEncoding.getRequestContentType());
}
if (thePrettyPrint) {
b.append('&');
b.append(Constants.PARAM_PRETTY);
b.append('=');
b.append(Constants.PARAM_PRETTY_VALUE_TRUE);
}
return b.toString();
}
private static void addProfileToBundleEntry(FhirContext theContext, IResource theResource, String theServerBase) {
TagList tl = ResourceMetadataKeyEnum.TAG_LIST.get(theResource);
if (tl == null) {
tl = new TagList();
ResourceMetadataKeyEnum.TAG_LIST.put(theResource, tl);
}
RuntimeResourceDefinition nextDef = theContext.getResourceDefinition(theResource);
String profile = nextDef.getResourceProfile(theServerBase);
if (isNotBlank(profile)) {
tl.add(new Tag(Tag.HL7_ORG_PROFILE_TAG, profile, null));
}
}
public static Bundle createBundleFromResourceList(FhirContext theContext, String theAuthor, List<IResource> theResult, String theServerBase, String theCompleteUrl, int theTotalResults, BundleTypeEnum theBundleType) {
Bundle bundle = new Bundle();
bundle.getAuthorName().setValue(theAuthor);
bundle.getBundleId().setValue(UUID.randomUUID().toString());
bundle.getPublished().setToCurrentTimeInLocalTimeZone();
bundle.getLinkBase().setValue(theServerBase);
bundle.getLinkSelf().setValue(theCompleteUrl);
bundle.getType().setValueAsEnum(theBundleType);
List<IResource> includedResources = new ArrayList<IResource>();
Set<IdDt> addedResourceIds = new HashSet<IdDt>();
for (IResource next : theResult) {
if (next.getId().isEmpty() == false) {
addedResourceIds.add(next.getId());
}
}
for (IResource next : theResult) {
Set<String> containedIds = new HashSet<String>();
for (IResource nextContained : next.getContained().getContainedResources()) {
if (nextContained.getId().isEmpty() == false) {
containedIds.add(nextContained.getId().getValue());
}
}
if (theContext.getNarrativeGenerator() != null) {
String title = theContext.getNarrativeGenerator().generateTitle(next);
RestfulServer.ourLog.trace("Narrative generator created title: {}", title);
if (StringUtils.isNotBlank(title)) {
ResourceMetadataKeyEnum.TITLE.put(next, title);
}
} else {
RestfulServer.ourLog.trace("No narrative generator specified");
}
List<BaseResourceReferenceDt> references = theContext.newTerser().getAllPopulatedChildElementsOfType(next, BaseResourceReferenceDt.class);
do {
List<IResource> addedResourcesThisPass = new ArrayList<IResource>();
for (BaseResourceReferenceDt nextRef : references) {
IResource nextRes = nextRef.getResource();
if (nextRes != null) {
if (nextRes.getId().hasIdPart()) {
if (containedIds.contains(nextRes.getId().getValue())) {
// Don't add contained IDs as top level resources
continue;
}
IdDt id = nextRes.getId();
if (id.hasResourceType() == false) {
String resName = theContext.getResourceDefinition(nextRes).getName();
id = id.withResourceType(resName);
}
if (!addedResourceIds.contains(id)) {
addedResourceIds.add(id);
addedResourcesThisPass.add(nextRes);
}
}
}
}
// Linked resources may themselves have linked resources
references = new ArrayList<BaseResourceReferenceDt>();
for (IResource iResource : addedResourcesThisPass) {
List<BaseResourceReferenceDt> newReferences = theContext.newTerser().getAllPopulatedChildElementsOfType(iResource, BaseResourceReferenceDt.class);
references.addAll(newReferences);
}
includedResources.addAll(addedResourcesThisPass);
} while (references.isEmpty() == false);
bundle.addResource(next, theContext, theServerBase);
}
/*
* Actually add the resources to the bundle
*/
for (IResource next : includedResources) {
BundleEntry entry = bundle.addResource(next, theContext, theServerBase);
if (theContext.getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU1)) {
if (entry.getSearchMode().isEmpty()) {
entry.getSearchMode().setValueAsEnum(BundleEntrySearchModeEnum.INCLUDE);
}
}
}
bundle.getTotalResults().setValue(theTotalResults);
return bundle;
}
public static RestfulServer.NarrativeModeEnum determineNarrativeMode(RequestDetails theRequest) {
Map<String, String[]> requestParams = theRequest.getParameters();
String[] narrative = requestParams.remove(Constants.PARAM_NARRATIVE);
RestfulServer.NarrativeModeEnum narrativeMode = null;
if (narrative != null && narrative.length > 0) {
narrativeMode = RestfulServer.NarrativeModeEnum.valueOfCaseInsensitive(narrative[0]);
}
if (narrativeMode == null) {
narrativeMode = RestfulServer.NarrativeModeEnum.NORMAL;
}
return narrativeMode;
}
/**
* Determine whether a response should be given in JSON or XML format based on the incoming HttpServletRequest's
* <code>"_format"</code> parameter and <code>"Accept:"</code> HTTP header.
*/
public static EncodingEnum determineResponseEncodingWithDefault(RestfulServer theServer, HttpServletRequest theReq) {
EncodingEnum retVal = determineResponseEncodingNoDefault(theReq);
if (retVal == null) {
retVal =theServer.getDefaultResponseEncoding();
}
return retVal;
}
public static IParser getNewParser(FhirContext theContext, EncodingEnum theResponseEncoding, boolean thePrettyPrint, RestfulServer.NarrativeModeEnum theNarrativeMode) {
IParser parser;
switch (theResponseEncoding) {
case JSON:
parser = theContext.newJsonParser();
break;
case XML:
default:
parser = theContext.newXmlParser();
break;
}
return parser.setPrettyPrint(thePrettyPrint).setSuppressNarratives(theNarrativeMode == RestfulServer.NarrativeModeEnum.SUPPRESS);
}
public static EncodingEnum determineResponseEncodingNoDefault(HttpServletRequest theReq) {
String[] format = theReq.getParameterValues(Constants.PARAM_FORMAT);
if (format != null) {
for (String nextFormat : format) {
EncodingEnum retVal = Constants.FORMAT_VAL_TO_ENCODING.get(nextFormat);
if (retVal != null) {
return retVal;
}
}
}
Enumeration<String> acceptValues = theReq.getHeaders(Constants.HEADER_ACCEPT);
if (acceptValues != null) {
while (acceptValues.hasMoreElements()) {
String nextAcceptHeaderValue = acceptValues.nextElement();
if (nextAcceptHeaderValue != null && isNotBlank(nextAcceptHeaderValue)) {
for (String nextPart : nextAcceptHeaderValue.split(",")) {
int scIdx = nextPart.indexOf(';');
if (scIdx == 0) {
continue;
}
if (scIdx != -1) {
nextPart = nextPart.substring(0, scIdx);
}
nextPart = nextPart.trim();
EncodingEnum retVal = Constants.FORMAT_VAL_TO_ENCODING.get(nextPart);
if (retVal != null) {
return retVal;
}
}
}
}
}
return null;
}
public static Integer extractCountParameter(HttpServletRequest theRequest) {
String name = Constants.PARAM_COUNT;
return RestfulServerUtils.tryToExtractNamedParameter(theRequest, name);
}
public static void streamResponseAsBundle(RestfulServer theServer, HttpServletResponse theHttpResponse, Bundle bundle, EncodingEnum theResponseEncoding, String theServerBase, boolean thePrettyPrint, RestfulServer.NarrativeModeEnum theNarrativeMode, boolean theRespondGzip, boolean theRequestIsBrowser) throws IOException {
assert !theServerBase.endsWith("/");
theHttpResponse.setStatus(200);
EncodingEnum responseEncoding = theResponseEncoding!= null? theResponseEncoding : theServer.getDefaultResponseEncoding();
if (theRequestIsBrowser && theServer.isUseBrowserFriendlyContentTypes()) {
theHttpResponse.setContentType(responseEncoding.getBrowserFriendlyBundleContentType());
} else if (theNarrativeMode == RestfulServer.NarrativeModeEnum.ONLY) {
theHttpResponse.setContentType(Constants.CT_HTML);
} else {
theHttpResponse.setContentType(responseEncoding.getBundleContentType());
}
theHttpResponse.setCharacterEncoding(Constants.CHARSET_UTF_8);
theServer.addHeadersToResponse(theHttpResponse);
Writer writer = RestfulServerUtils.getWriter(theHttpResponse, theRespondGzip);
try {
if (theNarrativeMode == RestfulServer.NarrativeModeEnum.ONLY) {
for (IResource next : bundle.toListOfResources()) {
writer.append(next.getText().getDiv().getValueAsString());
writer.append("<hr/>");
}
} else {
RestfulServerUtils.getNewParser(theServer.getFhirContext(), responseEncoding, thePrettyPrint, theNarrativeMode).encodeBundleToWriter(bundle, writer);
}
} finally {
writer.close();
}
}
public static void streamResponseAsResource(RestfulServer theServer, HttpServletResponse theHttpResponse, IResource theResource, EncodingEnum theResponseEncoding, boolean thePrettyPrint, boolean theRequestIsBrowser, RestfulServer.NarrativeModeEnum theNarrativeMode, boolean theRespondGzip, String theServerBase)
throws IOException {
int stausCode = 200;
RestfulServerUtils.streamResponseAsResource(theServer, theHttpResponse, theResource, theResponseEncoding, thePrettyPrint, theRequestIsBrowser, theNarrativeMode, stausCode, theRespondGzip, theServerBase);
}
private static void validateResourceListNotNull(List<IResource> theResourceList) {
if (theResourceList == null) {
throw new InternalErrorException("IBundleProvider returned a null list of resources - This is not allowed");
}
}
public static Bundle createBundleFromBundleProvider(RestfulServer theServer, IBundleProvider theResult, EncodingEnum theResponseEncoding, String theServerBase, String theCompleteUrl, boolean thePrettyPrint, int theOffset, Integer theLimit, String theSearchId, BundleTypeEnum theBundleType) {
int numToReturn;
String searchId = null;
List<IResource> resourceList;
if (theServer.getPagingProvider() == null) {
numToReturn = theResult.size();
resourceList = theResult.getResources(0, numToReturn);
RestfulServerUtils.validateResourceListNotNull(resourceList);
} else {
IPagingProvider pagingProvider = theServer.getPagingProvider();
if (theLimit == null) {
numToReturn = pagingProvider.getDefaultPageSize();
} else {
numToReturn = Math.min(pagingProvider.getMaximumPageSize(), theLimit);
}
numToReturn = Math.min(numToReturn, theResult.size() - theOffset);
resourceList = theResult.getResources(theOffset, numToReturn + theOffset);
RestfulServerUtils.validateResourceListNotNull(resourceList);
if (theSearchId != null) {
searchId = theSearchId;
} else {
if (theResult.size() > numToReturn) {
searchId = pagingProvider.storeResultList(theResult);
Validate.notNull(searchId, "Paging provider returned null searchId");
}
}
}
for (IResource next : resourceList) {
if (next.getId() == null || next.getId().isEmpty()) {
if (!(next instanceof BaseOperationOutcome)) {
throw new InternalErrorException("Server method returned resource of type[" + next.getClass().getSimpleName() + "] with no ID specified (IResource#setId(IdDt) must be called)");
}
}
}
if (theServer.getAddProfileTag() != AddProfileTagEnum.NEVER) {
for (IResource nextRes : resourceList) {
RuntimeResourceDefinition def = theServer.getFhirContext().getResourceDefinition(nextRes);
if (theServer.getAddProfileTag() == AddProfileTagEnum.ALWAYS || !def.isStandardProfile()) {
RestfulServerUtils.addProfileToBundleEntry(theServer.getFhirContext(), nextRes, theServerBase);
}
}
}
Bundle bundle = RestfulServerUtils.createBundleFromResourceList(theServer.getFhirContext(), theServer.getServerName(), resourceList, theServerBase, theCompleteUrl, theResult.size(), theBundleType);
bundle.setPublished(theResult.getPublished());
if (theServer.getPagingProvider() != null) {
int limit;
limit = theLimit != null ? theLimit : theServer.getPagingProvider().getDefaultPageSize();
limit = Math.min(limit, theServer.getPagingProvider().getMaximumPageSize());
if (searchId != null) {
if (theOffset + numToReturn < theResult.size()) {
bundle.getLinkNext().setValue(RestfulServerUtils.createPagingLink(theServerBase, searchId, theOffset + numToReturn, numToReturn, theResponseEncoding, thePrettyPrint));
}
if (theOffset > 0) {
int start = Math.max(0, theOffset - limit);
bundle.getLinkPrevious().setValue(RestfulServerUtils.createPagingLink(theServerBase, searchId, start, limit, theResponseEncoding, thePrettyPrint));
}
}
}
return bundle;
}
}

View File

@ -92,6 +92,11 @@ public class ResourceProviderDstu2Test {
}
}
public void testTryToCreateResourceWithNumericId() {
String resource = "<Patient xmlns=\"http://hl7.org/fhir\"><id value=\"1777\"/><meta><versionId value=\"1\"/><lastUpdated value=\"2015-02-25T15:47:48Z\"/></meta></Patient>";
}
/**
* Test for issue #60
*/

View File

@ -14,6 +14,7 @@ import org.slf4j.LoggerFactory;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.IResource;
@ -33,7 +34,7 @@ import ca.uhn.fhir.model.dstu.valueset.ConditionStatusEnum;
import ca.uhn.fhir.model.dstu.valueset.NameUseEnum;
import ca.uhn.fhir.model.dstu.valueset.PractitionerRoleEnum;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.RestfulServerUtils;
/**
* Initially contributed by Alexander Kley for bug #29
@ -196,7 +197,7 @@ public class ContainedResourceEncodingTest {
List<IResource> list = new ArrayList<IResource>();
list.add(dr);
Bundle bundle = RestfulServer.createBundleFromResourceList(new FhirContext(), null, list, null, null, 0, null);
Bundle bundle = RestfulServerUtils.createBundleFromResourceList(new FhirContext(), null, list, null, null, 0, null);
IParser parser = this.ctx.newXmlParser().setPrettyPrint(true);
String xml = parser.encodeBundleToString(bundle);
@ -235,7 +236,7 @@ public class ContainedResourceEncodingTest {
List<IResource> list = new ArrayList<IResource>();
list.add(dr);
Bundle bundle = RestfulServer.createBundleFromResourceList(new FhirContext(), null, list, null, null, 0, null);
Bundle bundle = RestfulServerUtils.createBundleFromResourceList(new FhirContext(), null, list, null, null, 0, null);
IParser parser = this.ctx.newXmlParser().setPrettyPrint(true);
String xml = parser.encodeBundleToString(bundle);

View File

@ -1,5 +1,6 @@
package ca.uhn.fhir.rest.server;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.*;
import java.util.Collections;
@ -62,12 +63,47 @@ public class BinaryTest {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Binary/foo");
HttpResponse status = ourClient.execute(httpGet);
byte[] responseContent = IOUtils.toByteArray(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals(200, status.getStatusLine().getStatusCode());
assertEquals("foo", status.getFirstHeader("content-type").getValue());
assertArrayEquals(new byte[] { 1, 2, 3, 4 }, responseContent);
}
@Test
public void testReadWithExplicitTypeXml() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Binary/foo?_format=xml");
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent(), "UTF-8");
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info(responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
assertThat(status.getFirstHeader("content-type").getValue(), startsWith(Constants.CT_FHIR_XML + ";"));
Binary bin = ourCtx.newXmlParser().parseResource(Binary.class, responseContent);
assertEquals("foo", bin.getContentType());
assertArrayEquals(new byte[] { 1, 2, 3, 4 }, bin.getContent());
}
@Test
public void testReadWithExplicitTypeJson() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Binary/foo?_format=json");
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent(), "UTF-8");
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info(responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
assertThat(status.getFirstHeader("content-type").getValue(), startsWith(Constants.CT_FHIR_JSON + ";"));
Binary bin = ourCtx.newJsonParser().parseResource(Binary.class, responseContent);
assertEquals("foo", bin.getContentType());
assertArrayEquals(new byte[] { 1, 2, 3, 4 }, bin.getContent());
}
@Test
public void testCreate() throws Exception {
@ -104,6 +140,8 @@ public class BinaryTest {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Binary?");
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals(200, status.getStatusLine().getStatusCode());
assertEquals(Constants.CT_ATOM_XML + "; charset=UTF-8", status.getFirstHeader("content-type").getValue());

View File

@ -110,8 +110,8 @@ public class PagingTest {
}
@Test
public void testSearchSmallPages() throws Exception {
@Test()
public void testExplicitEncoding() throws Exception {
when(myPagingProvider.getDefaultPageSize()).thenReturn(5);
when(myPagingProvider.getMaximumPageSize()).thenReturn(9);
when(myPagingProvider.storeResultList(any(IBundleProvider.class))).thenReturn("ABCD");
@ -120,7 +120,7 @@ public class PagingTest {
String link;
String base = "http://localhost:" + ourPort;
{
HttpGet httpGet = new HttpGet(base + "/Patient?_count=2");
HttpGet httpGet = new HttpGet(base + "/Patient?_count=2&_format=xml");
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
@ -145,9 +145,51 @@ public class PagingTest {
assertEquals(2, bundle.getEntries().size());
assertEquals("2", bundle.getEntries().get(0).getId().getIdPart());
assertEquals("3", bundle.getEntries().get(1).getId().getIdPart());
assertEquals(base + '?' + Constants.PARAM_PAGINGACTION + "=ABCD&" + Constants.PARAM_PAGINGOFFSET + "=4&" + Constants.PARAM_COUNT + "=2&_format=xml", bundle.getLinkNext().getValue());
assertEquals(base + '/' + '?' + Constants.PARAM_PAGINGACTION + "=ABCD&" + Constants.PARAM_PAGINGOFFSET + "=2&" + Constants.PARAM_COUNT + "=2&_format=xml", bundle.getLinkSelf().getValue());
assertEquals(base + '?' + Constants.PARAM_PAGINGACTION + "=ABCD&" + Constants.PARAM_PAGINGOFFSET + "=0&" + Constants.PARAM_COUNT + "=2&_format=xml", bundle.getLinkPrevious().getValue());
assertEquals(base + '?' + Constants.PARAM_PAGINGACTION + "=ABCD&" + Constants.PARAM_PAGINGOFFSET + "=4&" + Constants.PARAM_COUNT + "=2", bundle.getLinkNext().getValue());
assertEquals(base + '/' + '?' + Constants.PARAM_PAGINGACTION + "=ABCD&" + Constants.PARAM_PAGINGOFFSET + "=2&" + Constants.PARAM_COUNT + "=2", bundle.getLinkSelf().getValue());
assertEquals(base + '?' + Constants.PARAM_PAGINGACTION + "=ABCD&" + Constants.PARAM_PAGINGOFFSET + "=0&" + Constants.PARAM_COUNT + "=2", bundle.getLinkPrevious().getValue());
}
}
@Test
public void testSearchSmallPages() throws Exception {
when(myPagingProvider.getDefaultPageSize()).thenReturn(5);
when(myPagingProvider.getMaximumPageSize()).thenReturn(9);
when(myPagingProvider.storeResultList(any(IBundleProvider.class))).thenReturn("ABCD");
when(myPagingProvider.retrieveResultList(eq("ABCD"))).thenReturn(ourBundleProvider);
String link;
String base = "http://localhost:" + ourPort;
{
HttpGet httpGet = new HttpGet(base + "/Patient?_count=2");
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals(200, status.getStatusLine().getStatusCode());
Bundle bundle = ourContext.newXmlParser().parseBundle(responseContent);
assertEquals(2, bundle.getEntries().size());
assertEquals("0", bundle.getEntries().get(0).getId().getIdPart());
assertEquals("1", bundle.getEntries().get(1).getId().getIdPart());
assertEquals(base + '?' + Constants.PARAM_PAGINGACTION + "=ABCD&" + Constants.PARAM_PAGINGOFFSET + "=2&" + Constants.PARAM_COUNT + "=2", bundle.getLinkNext().getValue());
assertNull(bundle.getLinkPrevious().getValue());
link = bundle.getLinkNext().getValue();
}
{
HttpGet httpGet = new HttpGet(link);
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals(200, status.getStatusLine().getStatusCode());
Bundle bundle = ourContext.newXmlParser().parseBundle(responseContent);
assertEquals(2, bundle.getEntries().size());
assertEquals("2", bundle.getEntries().get(0).getId().getIdPart());
assertEquals("3", bundle.getEntries().get(1).getId().getIdPart());
assertEquals(base + '?' + Constants.PARAM_PAGINGACTION + "=ABCD&" + Constants.PARAM_PAGINGOFFSET + "=4&" + Constants.PARAM_COUNT + "=2", bundle.getLinkNext().getValue());
assertEquals(base + '/' + '?' + Constants.PARAM_PAGINGACTION + "=ABCD&" + Constants.PARAM_PAGINGOFFSET + "=2&" + Constants.PARAM_COUNT + "=2", bundle.getLinkSelf().getValue());
assertEquals(base + '?' + Constants.PARAM_PAGINGACTION + "=ABCD&" + Constants.PARAM_PAGINGOFFSET + "=0&" + Constants.PARAM_COUNT + "=2", bundle.getLinkPrevious().getValue());
}
}

View File

@ -104,7 +104,7 @@ public class RestfulServerMethodTest {
p.getManagingOrganization().setResource(o);
Bundle bundle = RestfulServer.createBundleFromResourceList(ourCtx, "", resources, "http://foo", "http://foo", 2, null);
Bundle bundle = RestfulServerUtils.createBundleFromResourceList(ourCtx, "", resources, "http://foo", "http://foo", 2, null);
assertEquals(2, bundle.getEntries().size());
}

View File

@ -1,7 +1,9 @@
package ca.uhn.fhir.rest.server;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import java.util.Collections;
import java.util.List;
@ -58,6 +60,41 @@ public class BinaryTest {
ourLast = null;
}
@Test
public void testReadWithExplicitTypeXml() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Binary/foo?_format=xml");
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent(), "UTF-8");
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info(responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
assertThat(status.getFirstHeader("content-type").getValue(), startsWith(Constants.CT_FHIR_XML + ";"));
Binary bin = ourCtx.newXmlParser().parseResource(Binary.class, responseContent);
assertEquals("foo", bin.getContentType());
assertArrayEquals(new byte[] { 1, 2, 3, 4 }, bin.getContent());
}
@Test
public void testReadWithExplicitTypeJson() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Binary/foo?_format=json");
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent(), "UTF-8");
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info(responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
assertThat(status.getFirstHeader("content-type").getValue(), startsWith(Constants.CT_FHIR_JSON + ";"));
Binary bin = ourCtx.newJsonParser().parseResource(Binary.class, responseContent);
assertEquals("foo", bin.getContentType());
assertArrayEquals(new byte[] { 1, 2, 3, 4 }, bin.getContent());
}
@Test
public void testCreate() throws Exception {
HttpPost http = new HttpPost("http://localhost:" + ourPort + "/Binary");

View File

@ -116,6 +116,17 @@
Add support for DSTU2 style security labels in the parser and encoder. Thanks to
Mohammad Jafari for the contribution!
</action>
<action type="fix">
Server requests for Binary resources where the client has explicitly requested XML or JSON responses
(either with a <![CDATA[<code>_format</code>]]> URL parameter, or an <![CDATA[<code>Accept</code>]]> request header)
will be responded to using the Binary FHIR resource type instead of as Binary blobs. This is
in accordance with the recommended behaviour in the FHIR specification.
</action>
<action type="add">
Add a new property to RestfulServer called "DefaultResponseEncoding", which allows
users to configure a default encoding (XML/JSON) to use if none is specified in the
client request. Currently defaults to XML.
</action>
</release>
<release version="0.8" date="2014-Dec-17">
<action type="add">