create seperate response and server interfaces and classes for servlets and jaxrs

This commit is contained in:
petervanhoute 2015-10-07 17:11:16 +02:00
parent b4df6f9612
commit 7f910974a2
70 changed files with 1746 additions and 908 deletions

View File

@ -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<Void>
}
@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<Void>
}
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<Void>
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

View File

@ -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<T> implements IClientResponseHandler<T> {
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<IParameter> myParameters;
@ -131,7 +127,7 @@ public abstract class BaseMethodBinding<T> implements IClientResponseHandler<T>
}
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<T> implements IClientResponseHandler<T>
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<T> implements IClientResponseHandler<T>
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<T> implements IClientResponseHandler<T>
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<T> implements IClientResponseHandler<T>
/**
* @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<T> implements IClientResponseHandler<T>
/**
* @see BaseMethodBinding#loadRequestContents(RequestDetails)
*/
private static interface IRequestReader {
public static interface IRequestReader {
InputStream getInputStream(RequestDetails theRequestDetails) throws IOException;
}

View File

@ -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<Metho
}
}
private void addLocationHeader(RequestDetails theRequest, HttpServletResponse theResponse, MethodOutcome response, String headerLocation) {
StringBuilder b = new StringBuilder();
b.append(theRequest.getFhirServerBase());
b.append('/');
b.append(getResourceName());
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());
}
protected abstract void addParametersForServerRequest(RequestDetails theRequest, Object[] theParams);
/**
@ -137,21 +122,18 @@ abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding<Metho
}
}
@Override
public void invokeServer(RestfulServer theServer, RequestDetails theRequest) throws BaseServerResponseException, IOException {
byte[] requestContents = loadRequestContents(theRequest);
public Object invokeServer(IRestfulServer<?> 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<Metho
IBaseResource resource = response != null ? response.getResource() : null;
for (int i = theServer.getInterceptors().size() - 1; i >= 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<Metho
+ " returned null, which is not allowed for create operation");
}
if (response.getCreated() == null || Boolean.TRUE.equals(response.getCreated())) {
servletResponse.setStatus(Constants.STATUS_HTTP_201_CREATED);
return Constants.STATUS_HTTP_201_CREATED;
} else {
servletResponse.setStatus(Constants.STATUS_HTTP_200_OK);
return Constants.STATUS_HTTP_200_OK;
}
addContentLocationHeaders(theRequest, servletResponse, response);
allowPrefer = true;
break;
case UPDATE:
if (response == null || response.getCreated() == null || Boolean.FALSE.equals(response.getCreated())) {
servletResponse.setStatus(Constants.STATUS_HTTP_200_OK);
return Constants.STATUS_HTTP_200_OK;
} else {
servletResponse.setStatus(Constants.STATUS_HTTP_201_CREATED);
return Constants.STATUS_HTTP_201_CREATED;
}
addContentLocationHeaders(theRequest, servletResponse, response);
allowPrefer = true;
break;
case VALIDATE:
case DELETE:
@ -221,21 +200,29 @@ abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding<Metho
if (isReturnVoid() == false) {
throw new InternalErrorException("Method " + getMethod().getName() + " in type " + getMethod().getDeclaringClass().getCanonicalName() + " returned null");
}
servletResponse.setStatus(Constants.STATUS_HTTP_204_NO_CONTENT);
return Constants.STATUS_HTTP_204_NO_CONTENT;
} else {
if (response.getOperationOutcome() == null) {
servletResponse.setStatus(Constants.STATUS_HTTP_204_NO_CONTENT);
return Constants.STATUS_HTTP_204_NO_CONTENT;
} else {
servletResponse.setStatus(Constants.STATUS_HTTP_200_OK);
return Constants.STATUS_HTTP_200_OK;
}
}
}
}
theServer.addHeadersToResponse(servletResponse);
private Object returnResponse(RequestDetails theRequest, MethodOutcome response, IBaseResource originalOutcome, IBaseResource resource) throws IOException {
boolean allowPrefer = false;
int operationStatus = getOperationStatus(response);
IBaseResource outcome = originalOutcome;
if(EnumSet.of(RestOperationTypeEnum.CREATE, RestOperationTypeEnum.UPDATE).contains(getRestOperationType())) {
allowPrefer = true;
}
if (resource != null && allowPrefer) {
String prefer = theRequest.getServletRequest().getHeader(Constants.HEADER_PREFER);
String prefer = theRequest.getHeader(Constants.HEADER_PREFER);
PreferReturnEnum preferReturn = RestfulServerUtils.parsePreferHeader(prefer);
if (preferReturn != null) {
if (preferReturn == PreferReturnEnum.REPRESENTATION) {
@ -244,31 +231,7 @@ abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding<Metho
}
}
if (outcome != null) {
EncodingEnum encoding = RestfulServerUtils.determineResponseEncodingWithDefault(theServer, theRequest.getServletRequest());
servletResponse.setContentType(encoding.getResourceContentType());
Writer writer = servletResponse.getWriter();
IParser parser = encoding.newParser(getContext());
parser.setPrettyPrint(RestfulServerUtils.prettyPrintResponse(theServer, theRequest));
try {
parser.encodeResourceToWriter(outcome, writer);
} finally {
writer.close();
}
} else {
servletResponse.setContentType(Constants.CT_TEXT_WITH_UTF8);
Writer writer = servletResponse.getWriter();
writer.close();
}
// getMethod().in
}
private void addContentLocationHeaders(RequestDetails theRequest, HttpServletResponse servletResponse, MethodOutcome response) {
if (response != null && response.getId() != null) {
addLocationHeader(theRequest, servletResponse, response, Constants.HEADER_LOCATION);
addLocationHeader(theRequest, servletResponse, response, Constants.HEADER_CONTENT_LOCATION);
}
return theRequest.getResponse().returnResponse(ParseAction.create(outcome), operationStatus, allowPrefer, response, getResourceName());
}
public boolean isReturnVoid() {

View File

@ -29,7 +29,6 @@ import org.hl7.fhir.instance.model.api.IIdType;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.annotation.ResourceParam;
import ca.uhn.fhir.rest.client.BaseHttpClientInvocation;
import ca.uhn.fhir.rest.param.ResourceParameter;

View File

@ -35,8 +35,6 @@ import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.servlet.http.HttpServletResponse;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
@ -55,8 +53,8 @@ import ca.uhn.fhir.rest.client.exceptions.InvalidResponseException;
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.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.BaseServerResponseException;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
@ -233,10 +231,10 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Obje
throw new IllegalStateException("Should not get here!");
}
public abstract Object invokeServer(RestfulServer theServer, RequestDetails theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException;
public abstract Object invokeServer(IRestfulServer<?> 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<Obje
Set<SummaryEnum> 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<Obje
for (int i = theServer.getInterceptors().size() - 1; i >= 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<Include> includes = getRequestIncludesFromParams(params);
@ -350,28 +345,26 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Obje
if (bundle != null) {
for (int i = theServer.getInterceptors().size() - 1; i >= 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<Obje
for (int i = theServer.getInterceptors().size() - 1; i >= 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;
}
/**

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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) {

View File

@ -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();
}

View File

@ -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()) {

View File

@ -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<String> value = getElementsValueOrNull(theRequest);
if (value == null || value.isEmpty()) {
return null;

View File

@ -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<TagList> {
}
@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<TagList> {
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);
}
}

View File

@ -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();
}

View File

@ -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<? extends Collection<?>> theOuterCollectionType, Class<? extends Collection<?>> theInnerCollectionType, Class<?> theParameterType);

View File

@ -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;
}

View File

@ -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) {

View File

@ -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<Object> matchingParamValues = new ArrayList<Object>();
if (theRequest.getRequestType() == RequestTypeEnum.GET) {

View File

@ -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<T> {
protected T theOutcome;
protected ParseAction(T outcome) {
this.theOutcome = outcome;
}
public abstract void execute(IParser parser, Writer writer) throws IOException;
public static ParseAction<TagList> create(TagList outcome) {
return outcome == null ? null : new ParseAction<TagList>(outcome) {
@Override
public void execute(IParser theParser, Writer theWriter) throws IOException {
theParser.encodeTagListToWriter(this.theOutcome, theWriter);
}
};
}
public static ParseAction<IBaseResource> create(IBaseResource outcome) {
return outcome == null ? null : new ParseAction<IBaseResource>(outcome) {
@Override
public void execute(IParser theParser, Writer theWriter) throws IOException {
theParser.encodeResourceToWriter(this.theOutcome, theWriter);
}
};
}
}

View File

@ -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<IBaseResource> responseResources = retVal.getResources(0, 1);
IBaseResource responseResource = responseResources.get(0);

View File

@ -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<String, String[]> 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<String, List<String>> 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.
* <p>
@ -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<String, List<String>> 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<String> theParamNames) {
RequestDetails retVal = new RequestDetails();
retVal.setResourceName(theResourceName);
retVal.setRequestType(theRequestType);
Map<String, String[]> paramNames = new HashMap<String, String[]>();
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<String> getHeaders(String name);
/**
* Retrieves the body of the request as character data using
* a <code>BufferedReader</code>. 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 <code>Reader</code> 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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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

View File

@ -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();
}

View File

@ -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) {

View File

@ -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)) {

View File

@ -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<SummaryEnum> value = getSummaryValueOrNull(theRequest);
if (value == null || value.isEmpty()) {
return null;

View File

@ -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

View File

@ -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) {

View File

@ -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<QualifiedParamList> paramList = new ArrayList<QualifiedParamList>();
String name = getName();

View File

@ -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 extends IBaseResource> T loadResourceFromRequest(RequestDetails theRequest, BaseMethodBinding<?> theMethodBinding, Class<T> theResourceType) {
public static <T extends IBaseResource> T loadResourceFromRequest(RequestDetails theRequest, BaseMethodBinding<?> theMethodBinding,
Class<T> 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<String> enumeration = theRequest.getServletRequest().getHeaders(Constants.HEADER_CATEGORY); enumeration.hasMoreElements();) {
String nextTagComplete = enumeration.nextElement();
for (String nextTagComplete : theRequest.getHeaders(Constants.HEADER_CATEGORY)) {
MethodUtil.parseTagValue(tagList, nextTagComplete);
}
if (tagList.isEmpty() == false) {
@ -212,23 +210,23 @@ public class ResourceParameter implements IParameter {
}
}
return retVal;
}
}
public static IBaseResource parseResourceFromRequest(RequestDetails theRequest, BaseMethodBinding<?> theMethodBinding, Class<? extends IBaseResource> theResourceType) {
IBaseResource retVal;
if (IBaseBinary.class.isAssignableFrom(theResourceType)) {
FhirContext ctx = theRequest.getServer().getFhirContext();
String ct = theRequest.getServletRequest().getHeader(Constants.HEADER_CONTENT_TYPE);
String ct = theRequest.getHeader(Constants.HEADER_CONTENT_TYPE);
IBaseBinary binary = (IBaseBinary) ctx.getResourceDefinition("Binary").newInstance();
binary.setContentType(ct);
binary.setContent(theRequest.getRawRequest());
binary.setContent(theRequest.loadRequestContents(theRequest));
retVal = binary;
} else {
retVal = loadResourceFromRequest(theRequest, theMethodBinding, theResourceType);
}
return retVal;
}
}
public enum Mode {
BODY, ENCODING, RESOURCE

View File

@ -1,5 +1,7 @@
package ca.uhn.fhir.rest.param;
import java.io.IOException;
/*
* #%L
* HAPI FHIR - Core Library
@ -48,6 +50,7 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
public class TransactionParameter implements IParameter {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TransactionParameter.class);
private FhirContext myContext;
private ParamStyle myParamStyle;
private Class<? extends IBaseResource> myResourceBundleType;
@ -96,35 +99,41 @@ public class TransactionParameter implements IParameter {
// nothing
}
@Override
public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, byte[] theRequestContents, BaseMethodBinding<?> theMethodBinding) throws InternalErrorException, InvalidRequestException {
public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, BaseMethodBinding<?> theMethodBinding) throws InternalErrorException, InvalidRequestException {
// TODO: don't use a default encoding, just fail!
EncodingEnum encoding = RestfulServerUtils.determineRequestEncoding(theRequest);
IParser parser = encoding.newParser(myContext);
IParser parser = encoding.newParser(theRequest.getServer().getFhirContext());
Reader reader;
try {
reader = ResourceParameter.createRequestReader(theRequest);
}
catch (IOException e) {
ourLog.error("Could not load request resource", e);
throw new InvalidRequestException(String.format("Could not load request resource: %s", e.getMessage()));
}
Reader reader = ResourceParameter.createRequestReader(theRequest, theRequestContents);
switch (myParamStyle) {
case DSTU1_BUNDLE: {
Bundle bundle;
bundle = parser.parseBundle(reader);
return bundle;
}
case RESOURCE_LIST: {
Bundle bundle = parser.parseBundle(reader);
ArrayList<IResource> resourceList = new ArrayList<IResource>();
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<IResource> resourceList = new ArrayList<IResource>();
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

View File

@ -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<Include> 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<Include> theIncludes) {
int numToReturn;
String searchId = null;
List<IBaseResource> resourceList;

View File

@ -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<SummaryEnum> summaryMode, int operationStatus, boolean respondGzip, boolean addContentLocationHeader)
throws IOException;
Object streamResponseAsBundle(Bundle bundle, Set<SummaryEnum> 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;
}

View File

@ -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<T extends RequestDetails> 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<IServerInterceptor> getInterceptors();
List<IServerInterceptor> getInterceptors();
IPagingProvider getPagingProvider();
BundleInclusionRule getBundleInclusionRule();
}

View File

@ -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 <code>Accept</code> header in the request, or a <code>_pretty</code>
* parameter in the request URL.
* <p>
* The default is <code>false</code>
* </p>
*
* @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 <code>Accept</code> header in the request, or a <code>_pretty</code>
* parameter in the request URL.
* <p>
* The default is <code>false</code>
* </p>
*
* @return Returns the default pretty print setting
*/
boolean isDefaultPrettyPrint();
/**
* @return Returns the server support for ETags (will not be <code>null</code>). 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 <code>_format</code> URL parameter, or with an <code>Accept</code> header
* in the request. The default is {@link EncodingEnum#XML}. Will not return null.
*/
EncodingEnum getDefaultResponseEncoding();
/**
* @return If <code>true</code> 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();
}

View File

@ -0,0 +1,24 @@
package ca.uhn.fhir.rest.server;
import org.hl7.fhir.instance.model.api.IBaseResource;
import ca.uhn.fhir.rest.method.BaseMethodBinding;
import ca.uhn.fhir.rest.method.RequestDetails;
import ca.uhn.fhir.rest.param.ResourceParameter.Mode;
import ca.uhn.fhir.rest.param.TransactionParameter.ParamStyle;
public interface IRestfulServerUtil {
Object getResourceParameter(
RequestDetails requestDetails,
Mode myMode,
BaseMethodBinding<?> theMethodBinding,
Class<? extends IBaseResource> myResourceType);
Object getRequestResource(RequestDetails theRequest, ParamStyle myParamStyle, Class<? extends IBaseResource> myResourceBundleType);
<T extends IBaseResource> T loadResourceFromRequest(RequestDetails theRequest, BaseMethodBinding<?> theMethodBinding, Class<T> theResourceType);
IBaseResource parseResourceFromRequest(RequestDetails theRequest, BaseMethodBinding<?> theMethodBinding, Class<? extends IBaseResource> theResourceType);
}

View File

@ -40,7 +40,7 @@ public interface IVersionSpecificBundleFactory {
void addRootPropertiesToBundle(String theAuthor, String theServerBase, String theCompleteUrl, Integer theTotalResults, BundleTypeEnum theBundleType, IPrimitiveType<Date> 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<Include> theIncludes);
Bundle getDstu1Bundle();

View File

@ -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<T extends RequestDetails> implements IRestfulResponse {
private T theRequestDetails;
private ConcurrentHashMap<String, String> theHeaders = new ConcurrentHashMap<String, String>();
public RestfulResponse(T requestDetails) {
this.theRequestDetails = requestDetails;
}
@Override
public final Object streamResponseAsResource(IBaseResource resource, boolean prettyPrint, Set<SummaryEnum> 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<SummaryEnum> 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<String, String> 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;
}
}

View File

@ -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<ServletRequestDetails> {
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 <code>null</code>). 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<SummaryEnum> 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;
}
}

View File

@ -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<? extends IBaseResource> 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 extends IBaseResource> T loadResourceFromRequest(RequestDetails theRequest, BaseMethodBinding<?> theMethodBinding, Class<T> 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<String> 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<? extends IBaseResource> 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<? extends IBaseResource> 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<IResource> resourceList = new ArrayList<IResource>();
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
}
}

View File

@ -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<String> acceptValues = theReq.getServletRequest().getHeaders(Constants.HEADER_CONTENT_TYPE);
Iterator<String> 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<String> acceptValues = theReq.getHeaders(Constants.HEADER_ACCEPT);
if (acceptValues != null) {
float bestQ = -1f;
EncodingEnum retVal = null;
while (acceptValues.hasMoreElements()) {
String nextAcceptHeaderValue = acceptValues.nextElement();
List<String> 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 <code>"_format"</code> parameter and <code>"Accept:"</code> 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<String> parseAcceptHeaderAndReturnHighestRankedOptions(HttpServletRequest theRequest) {
Set<String> retVal = new HashSet<String>();
@ -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<String> acceptValues = theRequest.getServletRequest().getHeaders(Constants.HEADER_ACCEPT);
List<String> 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<SummaryEnum> theSummaryMode, boolean theRespondGzip,
boolean theRequestIsBrowser, RequestDetails theRequestDetails) throws IOException {
assert!theServerBase.endsWith("/");
public static Object streamResponseAsBundle(IRestfulServerDefaults theServer, Bundle bundle, Set<SummaryEnum> 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<SummaryEnum> 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<SummaryEnum> 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);
}

View File

@ -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<ResourceBinding> resourceBindings;
private Collection<ResourceBinding> resourceBindings;
private List<BaseMethodBinding<?>> 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<ResourceBinding>(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<ResourceBinding> getResourceBindings() {
public Collection<ResourceBinding> getResourceBindings() {
return resourceBindings;
}
@ -69,7 +29,7 @@ public class RestulfulServerConfiguration {
* Set the resourceBindings
* @param resourceBindings the resourceBindings to set
*/
public RestulfulServerConfiguration setResourceBindings(List<ResourceBinding> resourceBindings) {
public RestulfulServerConfiguration setResourceBindings(Collection<ResourceBinding> 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);
}
}

View File

@ -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);

View File

@ -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
* <code>false</code> to indicate that the server itself should not also provide a response.
* @return Return <code>true</code> 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 <code>false</code>. 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 <code>true</code> 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 <code>false</code>. 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 <code>true</code> 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 <code>false</code>. 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 <code>true</code> 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 <code>false</code>. 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-<code>null</code> response or

View File

@ -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 {

View File

@ -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 {

View File

@ -171,7 +171,7 @@ public class ResponseHighlighterInterceptor extends InterceptorAdapter {
/*
* It's not a browser...
*/
Set<String> highestRankedAcceptValues = RestfulServerUtils.parseAcceptHeaderAndReturnHighestRankedOptions(theRequestDetails.getServletRequest());
Set<String> 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<String> accept = RestfulServerUtils.parseAcceptHeaderAndReturnHighestRankedOptions(theRequestDetails.getServletRequest());
Set<String> accept = RestfulServerUtils.parseAcceptHeaderAndReturnHighestRankedOptions(theServletRequest);
if (!accept.contains(Constants.CT_HTML)) {
return super.handleException(theRequestDetails, theException, theServletRequest, theServletResponse);
}

View File

@ -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<String> theParamNames) {
RequestDetails retVal = new ServletRequestDetails();
retVal.setResourceName(theResourceName);
retVal.setRequestType(theRequestType);
Map<String, String[]> paramNames = new HashMap<String, String[]>();
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<String> getHeaders(String name) {
Enumeration<String> headers = getServletRequest().getHeaders(name);
return headers == null ? Collections.<String>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()));
}
}
}

View File

@ -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<ServletRequestDetails> {
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<String, String> 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);
}
}

View File

@ -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();

View File

@ -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<String, String[]> getQueryMap() {
MultivaluedMap<String, String> queryParameters = info.getQueryParameters();
MultivaluedMap<String, String> queryParameters = getInfo().getQueryParameters();
HashMap<String, String[]> params = new HashMap<String, String[]>();
for (String key : queryParameters.keySet()) {
params.put(key, queryParameters.get(key).toArray(new String[] {}));
}
return params;
}
private String getParam(String string) {
for (Entry<String, List<String>> 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;
}
}

View File

@ -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<R extends IResource> 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<R extends IResource> extends AbstractJaxRsRestServer implements IResourceRestServer<R> {
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<String, String[]> 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<R> 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 <T extends BaseMethodBinding<?>> 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<String> 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<R> getResourceType();
}

View File

@ -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 {
}

View File

@ -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<T> extends IRestfulServer<JaxRsRequestDetails>, 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;
}

View File

@ -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<JaxRsRequestDetails> {
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<String, String> 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;
}
}

View File

@ -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 {

View File

@ -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<String> requestHeader = headers.getRequestHeader(headerIfNoneExist);
return (requestHeader == null || requestHeader.size() == 0) ? null : requestHeader.get(0);
}
@Override
public List<String> getHeaders(String name) {
List<String> requestHeader = headers.getRequestHeader(name);
return requestHeader == null ? Collections.<String> 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();
}
}

View File

@ -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<String, CreateMethodBinding> createMethods = new ConcurrentHashMap<String, CreateMethodBinding>();
private ConcurrentHashMap<String, UpdateMethodBinding> updateMethods = new ConcurrentHashMap<String, UpdateMethodBinding>();
private ConcurrentHashMap<String, DeleteMethodBinding> delete = new ConcurrentHashMap<String, DeleteMethodBinding>();
/** BaseResourceReturingMethodBinding */
private ConcurrentHashMap<String, SearchMethodBinding> searchMethods = new ConcurrentHashMap<String, SearchMethodBinding>();
private ConcurrentHashMap<String, OperationMethodBinding> operationMethods = new ConcurrentHashMap<String, OperationMethodBinding>();
/** ALL METHOD BINDINGS */
ConcurrentHashMap<RestOperationTypeEnum, ConcurrentHashMap<String, BaseMethodBinding<?>>> allBindings = new ConcurrentHashMap<RestOperationTypeEnum, ConcurrentHashMap<String,BaseMethodBinding<?>>>();
public <T extends AbstractResourceRestServer<?>> 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<String, BaseMethodBinding<?>> 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<String, BaseMethodBinding<?>> 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<String,BaseMethodBinding<?>> getAllBindingsMap(final RestOperationTypeEnum restOperationTypeEnum) {
allBindings.putIfAbsent(restOperationTypeEnum, new ConcurrentHashMap<String, BaseMethodBinding<?>>());
return allBindings.get(restOperationTypeEnum);
}
public BaseMethodBinding<?> getBinding(RestOperationTypeEnum operationType, String qualifier) {
String nonEmptyQualifier = StringUtils.defaultIfBlank(qualifier, "");
ConcurrentHashMap<String, BaseMethodBinding<?>> map = getAllBindingsMap(operationType);
if(!map.containsKey(nonEmptyQualifier)) {
throw new UnsupportedOperationException();
} else {
return map.get(nonEmptyQualifier);
}
}
private <T> ConcurrentHashMap<String, T> getMap(Class<T> class1) {
if(class1.isAssignableFrom(CreateMethodBinding.class)) return (ConcurrentHashMap<String, T>) createMethods;
if(class1.isAssignableFrom(UpdateMethodBinding.class)) return (ConcurrentHashMap<String, T>) updateMethods;
if(class1.isAssignableFrom(DeleteMethodBinding.class)) return (ConcurrentHashMap<String, T>) delete;
if(class1.isAssignableFrom(SearchMethodBinding.class)) return (ConcurrentHashMap<String, T>) searchMethods;
if(class1.isAssignableFrom(OperationMethodBinding.class)) return (ConcurrentHashMap<String, T>) 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> T getBinding(Class<T> clazz) {
ConcurrentHashMap map = getMap((Class<? extends BaseMethodBinding>) clazz);
if(map.values().size() == 0) {
throw new UnsupportedOperationException();
}
return (T) map.values().iterator().next();
public BaseMethodBinding<?> getBinding(RestOperationTypeEnum operationType) {
return getBinding(operationType, "");
}
}

View File

@ -13,7 +13,7 @@
<artifactId>hapi-fhir-jaxrsserver-example</artifactId>
<packaging>war</packaging>
<name>HAPI FHIR JPA Server - Example</name>
<name>HAPI FHIR JAX-RS Server - Example</name>
<repositories>
<repository>

View File

@ -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";

View File

@ -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<Patient> implements IFhirPatientRestServer {
static final String PATH = "/Patient";
@ -57,8 +70,8 @@ public class FhirPatientRestServer extends AbstractResourceRestServer<Patient> 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<Patient> i
}
@Override
@Read(version = false)
@Read(version = true)
public Patient findHistory(@IdParam final IdDt theId) {
if (patients.containsKey(theId.getIdPart())) {
final List<Patient> list = patients.get(theId.getIdPart());
@ -174,18 +187,28 @@ public class FhirPatientRestServer extends AbstractResourceRestServer<Patient> 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<IResource> searchCompartment(@IdParam IdDt thePatientId) {
List<IResource> retVal=new ArrayList<IResource>();
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<Patient> i
.setValue(new StringDt((counter-1)+"" + "inputVariable [ " + dummyInput.getValue()+ "]"));
return parameters;
}
@Override
public Class<Patient> getResourceType() {
return Patient.class;
}
/** THE DEFAULTS */
@Override
public Class<Patient> getResourceType() {
return Patient.class;
public List<IServerInterceptor> 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;
}
}

View File

@ -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<Patient> {
List<Patient> 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<IResource> searchCompartment(IdDt thePatientId);
}

View File

@ -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<Con
private boolean myCache = true;
private volatile Conformance myConformance;
private String myPublisher = "Not provided";
private RestulfulServerConfiguration myRestfulServer;
private RestulfulServerConfiguration myServerConfiguration;
public ServerConformanceProvider(RestfulServer theRestfulServer) {
myRestfulServer = new RestulfulServerConfiguration(theRestfulServer);
}
/*
* Add a no-arg constructor and seetter so that the
* ServerConfirmanceProvider can be Spring-wired with
@ -99,9 +73,13 @@ public class ServerConformanceProvider implements IServerConformanceProvider<Con
}
public void setRestfulServer (RestfulServer theRestfulServer) {
myRestfulServer = new RestulfulServerConfiguration(theRestfulServer);
myServerConfiguration = theRestfulServer.createConfiguration();
}
public ServerConformanceProvider(RestfulServer theRestfulServer) {
myServerConfiguration = theRestfulServer.createConfiguration();
}
/**
* Gets the value of the "publisher" that will be placed in the generated conformance statement. As this
* is a mandatory element, the value should not be null (although this is not enforced). The value defaults
@ -130,9 +108,9 @@ public class ServerConformanceProvider implements IServerConformanceProvider<Con
retVal.setFhirVersion("0.0.82-3059"); // TODO: pull from model
retVal.setAcceptUnknown(false); // TODO: make this configurable - this is a fairly big effort since the parser needs to be modified to actually allow it
retVal.getImplementation().setDescription(myRestfulServer.getImplementationDescription());
retVal.getSoftware().setName(myRestfulServer.getServerName());
retVal.getSoftware().setVersion(myRestfulServer.getServerVersion());
retVal.getImplementation().setDescription(myServerConfiguration.getImplementationDescription());
retVal.getSoftware().setName(myServerConfiguration.getServerName());
retVal.getSoftware().setVersion(myServerConfiguration.getServerVersion());
retVal.addFormat(Constants.CT_FHIR_XML);
retVal.addFormat(Constants.CT_FHIR_JSON);
@ -141,7 +119,7 @@ public class ServerConformanceProvider implements IServerConformanceProvider<Con
Set<RestfulOperationSystemEnum> systemOps = new HashSet<RestfulOperationSystemEnum>();
List<ResourceBinding> bindings = new ArrayList<ResourceBinding>(myRestfulServer.getResourceBindings());
List<ResourceBinding> bindings = new ArrayList<ResourceBinding>(myServerConfiguration.getResourceBindings());
Collections.sort(bindings, new Comparator<ResourceBinding>() {
@Override
public int compare(ResourceBinding theArg0, ResourceBinding theArg1) {
@ -155,9 +133,11 @@ public class ServerConformanceProvider implements IServerConformanceProvider<Con
RestResource resource = rest.addResource();
String resourceName = next.getResourceName();
RuntimeResourceDefinition def = myRestfulServer.getFhirContext().getResourceDefinition(resourceName);
RuntimeResourceDefinition def = myServerConfiguration.getFhirContext().getResourceDefinition(resourceName);
resource.getType().setValue(def.getName());
resource.getProfile().setReference(new IdDt(def.getResourceProfile(myRestfulServer.getServerBaseForRequest(theRequest))));
ServletContext servletContext = theRequest == null ? null : theRequest.getServletContext();
String serverBase = myServerConfiguration.getServerAddressStrategy().determineServerBase(servletContext, theRequest);
resource.getProfile().setReference(new IdDt(def.getResourceProfile(serverBase)));
TreeSet<String> includes = new TreeSet<String>();
@ -219,7 +199,7 @@ public class ServerConformanceProvider implements IServerConformanceProvider<Con
}
private DateTimeDt conformanceDate() {
String buildDate = getBuildDateFromManifest();
String buildDate = myServerConfiguration.getConformanceDate();
if (buildDate != null) {
try {
return new DateTimeDt(buildDate);
@ -230,21 +210,6 @@ public class ServerConformanceProvider implements IServerConformanceProvider<Con
return DateTimeDt.withCurrentTime();
}
private String getBuildDateFromManifest() {
if (myRestfulServer != null && myRestfulServer.getServletContext() != null) {
InputStream inputStream = myRestfulServer.getServletContext().getResourceAsStream("/META-INF/MANIFEST.MF");
if (inputStream != null) {
try {
Manifest manifest = new Manifest(inputStream);
return manifest.getMainAttributes().getValue("Build-Time");
} catch (IOException e) {
// fall through
}
}
}
return null;
}
private void handleDynamicSearchMethodBinding(RestResource resource, RuntimeResourceDefinition def, TreeSet<String> includes, DynamicSearchMethodBinding searchMethodBinding) {
includes.addAll(searchMethodBinding.getIncludes());
@ -352,7 +317,7 @@ public class ServerConformanceProvider implements IServerConformanceProvider<Con
param.setDocumentation(nextParamDescription);
param.getTypeElement().setValue(nextParameter.getParamType().getCode());
for (Class<? extends IResource> 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) {

View File

@ -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);
}

View File

@ -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<String> inputParams = new HashSet<String>();
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)

View File

@ -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<Include> theIncludes) {
myBase = theServerBase;

View File

@ -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<Con
private RestulfulServerConfiguration myServerConfiguration;
public ServerConformanceProvider(RestfulServer theRestfulServer) {
this.myServerConfiguration = new RestulfulServerConfiguration(theRestfulServer);
this.myServerConfiguration = theRestfulServer.createConfiguration();
}
public ServerConformanceProvider(RestulfulServerConfiguration theServerConfiguration) {
@ -115,7 +116,7 @@ public class ServerConformanceProvider implements IServerConformanceProvider<Con
}
public void setRestfulServer (RestfulServer theRestfulServer) {
myServerConfiguration = new RestulfulServerConfiguration(theRestfulServer);
myServerConfiguration = theRestfulServer.createConfiguration();
}
private void checkBindingForSystemOps(Rest rest, Set<SystemRestfulInteractionEnum> systemOps, BaseMethodBinding<?> nextMethodBinding) {
@ -204,7 +205,9 @@ public class ServerConformanceProvider implements IServerConformanceProvider<Con
String resourceName = nextEntry.getKey();
RuntimeResourceDefinition def = myServerConfiguration.getFhirContext().getResourceDefinition(resourceName);
resource.getTypeElement().setValue(def.getName());
resource.getProfile().setReference(new IdDt(def.getResourceProfile(myServerConfiguration.getServerBaseForRequest(theRequest))));
ServletContext servletContext = theRequest == null ? null : theRequest.getServletContext();
String serverBase = myServerConfiguration.getServerAddressStrategy().determineServerBase(servletContext, theRequest);
resource.getProfile().setReference(new IdDt(def.getResourceProfile(serverBase)));
TreeSet<String> includes = new TreeSet<String>();

View File

@ -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<String, String[]>());
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<String, String[]> params = new HashMap<String, String[]>();
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<String, String[]> params = new HashMap<String, String[]>();
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<String, String[]>());
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<String, String[]>());
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<String, String[]>());
RestfulServer server = new RestfulServer();

View File

@ -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<Include> theIncludes) {
myBase = theServerBase;