Implement handleException on the server interceptor framework, as well
as some site and documentation enhancements
This commit is contained in:
parent
8f704030ed
commit
d22a35788f
|
@ -0,0 +1,29 @@
|
||||||
|
package example;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.rest.server.interceptor.InterceptorAdapter;
|
||||||
|
|
||||||
|
//START SNIPPET: interceptor
|
||||||
|
public class RequestCounterInterceptor extends InterceptorAdapter
|
||||||
|
{
|
||||||
|
|
||||||
|
private int myRequestCount;
|
||||||
|
|
||||||
|
public int getRequestCount() {
|
||||||
|
return myRequestCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override the incomingRequestPreProcessed method, which is called
|
||||||
|
* for each incoming request before any processing is done
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean incomingRequestPreProcessed(HttpServletRequest theRequest, HttpServletResponse theResponse) {
|
||||||
|
myRequestCount++;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
//END SNIPPET: interceptor
|
|
@ -0,0 +1,42 @@
|
||||||
|
package example;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.rest.method.RequestDetails;
|
||||||
|
import ca.uhn.fhir.rest.server.Constants;
|
||||||
|
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
|
||||||
|
import ca.uhn.fhir.rest.server.interceptor.InterceptorAdapter;
|
||||||
|
|
||||||
|
//START SNIPPET: interceptor
|
||||||
|
public class RequestExceptionInterceptor extends InterceptorAdapter
|
||||||
|
{
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean handleException(RequestDetails theRequestDetails, Throwable theException, HttpServletRequest theServletRequest,
|
||||||
|
HttpServletResponse theServletResponse) throws ServletException, IOException {
|
||||||
|
|
||||||
|
// If the exception is a built-in type, it defines the correct status
|
||||||
|
// code to return. Otherwise default to 500.
|
||||||
|
if (theException instanceof BaseServerResponseException) {
|
||||||
|
theServletResponse.setStatus(((BaseServerResponseException) theException).getStatusCode());
|
||||||
|
} else {
|
||||||
|
theServletResponse.setStatus(Constants.STATUS_HTTP_500_INTERNAL_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provide a response ourself
|
||||||
|
theServletResponse.setContentType("text/plain");
|
||||||
|
theServletResponse.getWriter().append("Failed to process!");
|
||||||
|
theServletResponse.getWriter().close();
|
||||||
|
|
||||||
|
// Since we handled this response in the interceptor, we must return false
|
||||||
|
// to stop processing immediately
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
//END SNIPPET: interceptor
|
|
@ -3,6 +3,9 @@ package example;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.model.api.ExtensionDt;
|
import ca.uhn.fhir.model.api.ExtensionDt;
|
||||||
import ca.uhn.fhir.model.dstu.composite.HumanNameDt;
|
import ca.uhn.fhir.model.dstu.composite.HumanNameDt;
|
||||||
|
@ -11,6 +14,7 @@ import ca.uhn.fhir.model.dstu.valueset.IdentifierUseEnum;
|
||||||
import ca.uhn.fhir.model.primitive.DateTimeDt;
|
import ca.uhn.fhir.model.primitive.DateTimeDt;
|
||||||
import ca.uhn.fhir.model.primitive.StringDt;
|
import ca.uhn.fhir.model.primitive.StringDt;
|
||||||
import ca.uhn.fhir.parser.DataFormatException;
|
import ca.uhn.fhir.parser.DataFormatException;
|
||||||
|
import ca.uhn.fhir.rest.server.interceptor.InterceptorAdapter;
|
||||||
|
|
||||||
public class ServerInterceptors {
|
public class ServerInterceptors {
|
||||||
|
|
||||||
|
|
|
@ -19,9 +19,11 @@
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-project-info-reports-plugin</artifactId>
|
<artifactId>maven-project-info-reports-plugin</artifactId>
|
||||||
<version>2.7</version>
|
<version>2.7</version>
|
||||||
|
<inherited>false</inherited>
|
||||||
<reportSets>
|
<reportSets>
|
||||||
<reportSet>
|
<reportSet>
|
||||||
<reports>
|
<reports>
|
||||||
|
<report>scm</report>
|
||||||
</reports>
|
</reports>
|
||||||
</reportSet>
|
</reportSet>
|
||||||
</reportSets>
|
</reportSets>
|
||||||
|
@ -39,6 +41,7 @@
|
||||||
<configuration>
|
<configuration>
|
||||||
<links>
|
<links>
|
||||||
<link>http://docs.oracle.com/javaee/7/api</link>
|
<link>http://docs.oracle.com/javaee/7/api</link>
|
||||||
|
<link>http://jamesagnew.github.io/hapi-fhir/apidocs</link>
|
||||||
</links>
|
</links>
|
||||||
</configuration>
|
</configuration>
|
||||||
</reportSet>
|
</reportSet>
|
||||||
|
|
|
@ -15,13 +15,6 @@
|
||||||
|
|
||||||
<name>HAPI FHIR - Core Library</name>
|
<name>HAPI FHIR - Core Library</name>
|
||||||
|
|
||||||
<distributionManagement>
|
|
||||||
<site>
|
|
||||||
<id>git.server</id>
|
|
||||||
<url>scm:git:git@github.com:jamesagnew/hapi-fhir.git</url>
|
|
||||||
</site>
|
|
||||||
</distributionManagement>
|
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
|
||||||
<!-- JSON -->
|
<!-- JSON -->
|
||||||
|
@ -214,6 +207,8 @@
|
||||||
<groupId>org.codehaus.mojo</groupId>
|
<groupId>org.codehaus.mojo</groupId>
|
||||||
<artifactId>findbugs-maven-plugin</artifactId>
|
<artifactId>findbugs-maven-plugin</artifactId>
|
||||||
<version>3.0.0</version>
|
<version>3.0.0</version>
|
||||||
|
<configuration>
|
||||||
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
|
|
@ -207,10 +207,11 @@ public class ParameterUtil {
|
||||||
case ',':
|
case ',':
|
||||||
case '|':
|
case '|':
|
||||||
b.append('\\');
|
b.append('\\');
|
||||||
// fall through
|
break;
|
||||||
default:
|
default:
|
||||||
b.append(next);
|
break;
|
||||||
}
|
}
|
||||||
|
b.append(next);
|
||||||
}
|
}
|
||||||
|
|
||||||
return b.toString();
|
return b.toString();
|
||||||
|
|
|
@ -1,32 +0,0 @@
|
||||||
package ca.uhn.fhir.rest.server;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* #%L
|
|
||||||
* HAPI FHIR - Core Library
|
|
||||||
* %%
|
|
||||||
* Copyright (C) 2014 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 javax.servlet.http.HttpServletRequest;
|
|
||||||
|
|
||||||
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated Use {@link ca.uhn.fhir.rest.server.security.ISecurityManager} instead
|
|
||||||
*/
|
|
||||||
public interface ISecurityManager {
|
|
||||||
public void authenticate(HttpServletRequest request) throws AuthenticationException;
|
|
||||||
}
|
|
|
@ -92,13 +92,12 @@ public class RestfulServer extends HttpServlet {
|
||||||
private AddProfileTagEnum myAddProfileTag;
|
private AddProfileTagEnum myAddProfileTag;
|
||||||
private FhirContext myFhirContext;
|
private FhirContext myFhirContext;
|
||||||
private String myImplementationDescription;
|
private String myImplementationDescription;
|
||||||
private List<IServerInterceptor> myInterceptors = new ArrayList<IServerInterceptor>();
|
private final List<IServerInterceptor> myInterceptors = new ArrayList<IServerInterceptor>();
|
||||||
private ResourceBinding myNullResourceBinding = new ResourceBinding();
|
private ResourceBinding myNullResourceBinding = new ResourceBinding();
|
||||||
private IPagingProvider myPagingProvider;
|
private IPagingProvider myPagingProvider;
|
||||||
private Collection<Object> myPlainProviders;
|
private Collection<Object> myPlainProviders;
|
||||||
private Map<String, ResourceBinding> myResourceNameToProvider = new HashMap<String, ResourceBinding>();
|
private Map<String, ResourceBinding> myResourceNameToProvider = new HashMap<String, ResourceBinding>();
|
||||||
private Collection<IResourceProvider> myResourceProviders;
|
private Collection<IResourceProvider> myResourceProviders;
|
||||||
private ISecurityManager mySecurityManager;
|
|
||||||
private IServerAddressStrategy myServerAddressStrategy = new IncomingRequestAddressStrategy();
|
private IServerAddressStrategy myServerAddressStrategy = new IncomingRequestAddressStrategy();
|
||||||
private BaseMethodBinding<?> myServerConformanceMethod;
|
private BaseMethodBinding<?> myServerConformanceMethod;
|
||||||
private Object myServerConformanceProvider;
|
private Object myServerConformanceProvider;
|
||||||
|
@ -123,8 +122,7 @@ public class RestfulServer extends HttpServlet {
|
||||||
/**
|
/**
|
||||||
* This method is called prior to sending a response to incoming requests. It is used to add custom headers.
|
* This method is called prior to sending a response to incoming requests. It is used to add custom headers.
|
||||||
* <p>
|
* <p>
|
||||||
* Use caution if overriding this method: it is recommended to call <code>super.addHeadersToResponse</code> to avoid
|
* Use caution if overriding this method: it is recommended to call <code>super.addHeadersToResponse</code> to avoid inadvertantly disabling functionality.
|
||||||
* inadvertantly disabling functionality.
|
|
||||||
* </p>
|
* </p>
|
||||||
*/
|
*/
|
||||||
public void addHeadersToResponse(HttpServletResponse theHttpResponse) {
|
public void addHeadersToResponse(HttpServletResponse theHttpResponse) {
|
||||||
|
@ -236,14 +234,13 @@ public class RestfulServer extends HttpServlet {
|
||||||
Package pack = annotation.annotationType().getPackage();
|
Package pack = annotation.annotationType().getPackage();
|
||||||
if (pack.equals(IdParam.class.getPackage())) {
|
if (pack.equals(IdParam.class.getPackage())) {
|
||||||
if (!allowableParams.contains(annotation.annotationType())) {
|
if (!allowableParams.contains(annotation.annotationType())) {
|
||||||
throw new ConfigurationException("Method[" + m.toString() + "] is not allowed to have a parameter annotated with "+ annotation);
|
throw new ConfigurationException("Method[" + m.toString() + "] is not allowed to have a parameter annotated with " + annotation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
resourceBinding.addMethod(foundMethodBinding);
|
resourceBinding.addMethod(foundMethodBinding);
|
||||||
ourLog.debug(" * Method: {}#{} is a handler", theProvider.getClass(), m.getName());
|
ourLog.debug(" * Method: {}#{} is a handler", theProvider.getClass(), m.getName());
|
||||||
}
|
}
|
||||||
|
@ -293,8 +290,8 @@ public class RestfulServer extends HttpServlet {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the {@link FhirContext} associated with this server. For efficient processing, resource providers and plain
|
* 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
|
||||||
* providers should generally use this context if one is needed, as opposed to creating their own.
|
* creating their own.
|
||||||
*/
|
*/
|
||||||
public FhirContext getFhirContext() {
|
public FhirContext getFhirContext() {
|
||||||
return myFhirContext;
|
return myFhirContext;
|
||||||
|
@ -336,26 +333,16 @@ public class RestfulServer extends HttpServlet {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides the security manager, or <code>null</code> if none
|
* Get the server address strategy, which is used to determine what base URL to provide clients to refer to this server. Defaults to an instance of {@link IncomingRequestAddressStrategy}
|
||||||
*/
|
|
||||||
public ISecurityManager getSecurityManager() {
|
|
||||||
return mySecurityManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the server address strategy, which is used to determine what base URL to provide clients to refer to this
|
|
||||||
* server. Defaults to an instance of {@link IncomingRequestAddressStrategy}
|
|
||||||
*/
|
*/
|
||||||
public IServerAddressStrategy getServerAddressStrategy() {
|
public IServerAddressStrategy getServerAddressStrategy() {
|
||||||
return myServerAddressStrategy;
|
return myServerAddressStrategy;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the server conformance provider, which is the provider that is used to generate the server's conformance
|
* Returns the server conformance provider, which is the provider that is used to generate the server's conformance (metadata) statement.
|
||||||
* (metadata) statement.
|
|
||||||
* <p>
|
* <p>
|
||||||
* By default, the {@link ServerConformanceProvider} is used, but this can be changed, or set to <code>null</code>
|
* By default, the {@link ServerConformanceProvider} is used, but this can be changed, or set to <code>null</code> if you do not wish to export a conformance statement.
|
||||||
* if you do not wish to export a conformance statement.
|
|
||||||
* </p>
|
* </p>
|
||||||
*/
|
*/
|
||||||
public Object getServerConformanceProvider() {
|
public Object getServerConformanceProvider() {
|
||||||
|
@ -363,8 +350,7 @@ public class RestfulServer extends HttpServlet {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the server's name, as exported in conformance profiles exported by the server. This is informational only,
|
* Gets the server's name, as exported in conformance profiles exported by the server. This is informational only, but can be helpful to set with something appropriate.
|
||||||
* but can be helpful to set with something appropriate.
|
|
||||||
*
|
*
|
||||||
* @see RestfulServer#setServerName(String)
|
* @see RestfulServer#setServerName(String)
|
||||||
*/
|
*/
|
||||||
|
@ -377,8 +363,7 @@ public class RestfulServer extends HttpServlet {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the server's version, as exported in conformance profiles exported by the server. This is informational
|
* Gets the server's version, as exported in conformance profiles exported by the server. This is informational only, but can be helpful to set with something appropriate.
|
||||||
* only, but can be helpful to set with something appropriate.
|
|
||||||
*/
|
*/
|
||||||
public String getServerVersion() {
|
public String getServerVersion() {
|
||||||
return myServerVersion;
|
return myServerVersion;
|
||||||
|
@ -417,12 +402,14 @@ public class RestfulServer extends HttpServlet {
|
||||||
NarrativeModeEnum narrativeMode = determineNarrativeMode(theRequest);
|
NarrativeModeEnum narrativeMode = determineNarrativeMode(theRequest);
|
||||||
boolean respondGzip = theRequest.isRespondGzip();
|
boolean respondGzip = theRequest.isRespondGzip();
|
||||||
|
|
||||||
Bundle bundle = createBundleFromBundleProvider(this, theResponse, resultList, responseEncoding, theRequest.getFhirServerBase(), theRequest.getCompleteUrl(), prettyPrint, requestIsBrowser, narrativeMode, start, count, thePagingAction);
|
Bundle bundle = createBundleFromBundleProvider(this, theResponse, resultList, responseEncoding, theRequest.getFhirServerBase(), theRequest.getCompleteUrl(), prettyPrint, requestIsBrowser,
|
||||||
|
narrativeMode, start, count, thePagingAction);
|
||||||
|
|
||||||
for (int i = getInterceptors().size() - 1; i >= 0; i--) {
|
for (int i = getInterceptors().size() - 1; i >= 0; i--) {
|
||||||
IServerInterceptor next = getInterceptors().get(i);
|
IServerInterceptor next = getInterceptors().get(i);
|
||||||
boolean continueProcessing = next.outgoingResponse(theRequest, bundle, theRequest.getServletRequest(), theRequest.getServletResponse());
|
boolean continueProcessing = next.outgoingResponse(theRequest, bundle, theRequest.getServletRequest(), theRequest.getServletResponse());
|
||||||
if (!continueProcessing) {
|
if (!continueProcessing) {
|
||||||
|
ourLog.debug("Interceptor {} returned false, not continuing processing");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -435,30 +422,28 @@ public class RestfulServer extends HttpServlet {
|
||||||
for (IServerInterceptor next : myInterceptors) {
|
for (IServerInterceptor next : myInterceptors) {
|
||||||
boolean continueProcessing = next.incomingRequestPreProcessed(theRequest, theResponse);
|
boolean continueProcessing = next.incomingRequestPreProcessed(theRequest, theResponse);
|
||||||
if (!continueProcessing) {
|
if (!continueProcessing) {
|
||||||
|
ourLog.debug("Interceptor {} returned false, not continuing processing");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String fhirServerBase = null;
|
String fhirServerBase = null;
|
||||||
boolean requestIsBrowser = requestIsBrowser(theRequest);
|
boolean requestIsBrowser = requestIsBrowser(theRequest);
|
||||||
|
RequestDetails requestDetails=null;
|
||||||
try {
|
try {
|
||||||
|
|
||||||
if (null != mySecurityManager) {
|
|
||||||
mySecurityManager.authenticate(theRequest);
|
|
||||||
}
|
|
||||||
|
|
||||||
String resourceName = null;
|
String resourceName = null;
|
||||||
String requestFullPath = StringUtils.defaultString(theRequest.getRequestURI());
|
String requestFullPath = StringUtils.defaultString(theRequest.getRequestURI());
|
||||||
String servletPath = StringUtils.defaultString(theRequest.getServletPath());
|
String servletPath = StringUtils.defaultString(theRequest.getServletPath());
|
||||||
StringBuffer requestUrl = theRequest.getRequestURL();
|
StringBuffer requestUrl = theRequest.getRequestURL();
|
||||||
String servletContextPath = "";
|
String servletContextPath = "";
|
||||||
|
|
||||||
// if (getServletContext().getMajorVersion() >= 3) {
|
// if (getServletContext().getMajorVersion() >= 3) {
|
||||||
// // getServletContext is only supported in version 3+ of servlet-api
|
// // getServletContext is only supported in version 3+ of servlet-api
|
||||||
if (getServletContext() != null) {
|
if (getServletContext() != null) {
|
||||||
servletContextPath = StringUtils.defaultString(getServletContext().getContextPath());
|
servletContextPath = StringUtils.defaultString(getServletContext().getContextPath());
|
||||||
}
|
}
|
||||||
// }
|
// }
|
||||||
|
|
||||||
if (ourLog.isTraceEnabled()) {
|
if (ourLog.isTraceEnabled()) {
|
||||||
ourLog.trace("Request FullPath: {}", requestFullPath);
|
ourLog.trace("Request FullPath: {}", requestFullPath);
|
||||||
|
@ -609,7 +594,7 @@ public class RestfulServer extends HttpServlet {
|
||||||
throw new InvalidRequestException(b.toString());
|
throw new InvalidRequestException(b.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
RequestDetails requestDetails = r;
|
requestDetails = r;
|
||||||
requestDetails.setResourceOperationType(resourceMethod.getResourceOperationType());
|
requestDetails.setResourceOperationType(resourceMethod.getResourceOperationType());
|
||||||
requestDetails.setSystemOperationType(resourceMethod.getSystemOperationType());
|
requestDetails.setSystemOperationType(resourceMethod.getSystemOperationType());
|
||||||
requestDetails.setOtherOperationType(resourceMethod.getOtherOperationType());
|
requestDetails.setOtherOperationType(resourceMethod.getOtherOperationType());
|
||||||
|
@ -617,6 +602,7 @@ public class RestfulServer extends HttpServlet {
|
||||||
for (IServerInterceptor next : myInterceptors) {
|
for (IServerInterceptor next : myInterceptors) {
|
||||||
boolean continueProcessing = next.incomingRequestPostProcessed(requestDetails, theRequest, theResponse);
|
boolean continueProcessing = next.incomingRequestPostProcessed(requestDetails, theRequest, theResponse);
|
||||||
if (!continueProcessing) {
|
if (!continueProcessing) {
|
||||||
|
ourLog.debug("Interceptor {} returned false, not continuing processing");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -624,6 +610,15 @@ public class RestfulServer extends HttpServlet {
|
||||||
resourceMethod.invokeServer(this, r);
|
resourceMethod.invokeServer(this, r);
|
||||||
|
|
||||||
} catch (AuthenticationException e) {
|
} catch (AuthenticationException e) {
|
||||||
|
|
||||||
|
for (int i = getInterceptors().size() - 1; i >= 0; i--) {
|
||||||
|
IServerInterceptor next = getInterceptors().get(i);
|
||||||
|
if (!next.handleException(requestDetails, e, theRequest, theResponse)) {
|
||||||
|
ourLog.debug("Interceptor {} returned false, not continuing processing");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (requestIsBrowser) {
|
if (requestIsBrowser) {
|
||||||
// if request is coming from a browser, prompt the user to enter login credentials
|
// if request is coming from a browser, prompt the user to enter login credentials
|
||||||
theResponse.setHeader("WWW-Authenticate", "BASIC realm=\"FHIR\"");
|
theResponse.setHeader("WWW-Authenticate", "BASIC realm=\"FHIR\"");
|
||||||
|
@ -636,14 +631,30 @@ public class RestfulServer extends HttpServlet {
|
||||||
|
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We have caught an exception while handling an incoming server request.
|
||||||
|
* Start by notifying the interceptors..
|
||||||
|
*/
|
||||||
|
for (int i = getInterceptors().size() - 1; i >= 0; i--) {
|
||||||
|
IServerInterceptor next = getInterceptors().get(i);
|
||||||
|
if (!next.handleException(requestDetails, e, theRequest, theResponse)) {
|
||||||
|
ourLog.debug("Interceptor {} returned false, not continuing processing");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
BaseOperationOutcome oo = null;
|
BaseOperationOutcome oo = null;
|
||||||
int statusCode = 500;
|
int statusCode = Constants.STATUS_HTTP_500_INTERNAL_ERROR;
|
||||||
|
|
||||||
if (e instanceof BaseServerResponseException) {
|
if (e instanceof BaseServerResponseException) {
|
||||||
oo = ((BaseServerResponseException) e).getOperationOutcome();
|
oo = ((BaseServerResponseException) e).getOperationOutcome();
|
||||||
statusCode = ((BaseServerResponseException) e).getStatusCode();
|
statusCode = ((BaseServerResponseException) e).getStatusCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Generate an OperationOutcome to return, unless the exception throw by
|
||||||
|
* the resource provider had one
|
||||||
|
*/
|
||||||
if (oo == null) {
|
if (oo == null) {
|
||||||
try {
|
try {
|
||||||
oo = (BaseOperationOutcome) myFhirContext.getResourceDefinition("OperationOutcome").getImplementingClass().newInstance();
|
oo = (BaseOperationOutcome) myFhirContext.getResourceDefinition("OperationOutcome").getImplementingClass().newInstance();
|
||||||
|
@ -657,13 +668,23 @@ public class RestfulServer extends HttpServlet {
|
||||||
if (e instanceof InternalErrorException) {
|
if (e instanceof InternalErrorException) {
|
||||||
ourLog.error("Failure during REST processing", e);
|
ourLog.error("Failure during REST processing", e);
|
||||||
issue.getDetailsElement().setValue(e.toString() + "\n\n" + ExceptionUtils.getStackTrace(e));
|
issue.getDetailsElement().setValue(e.toString() + "\n\n" + ExceptionUtils.getStackTrace(e));
|
||||||
|
statusCode = ((InternalErrorException) e).getStatusCode();
|
||||||
} else if (e instanceof BaseServerResponseException) {
|
} else if (e instanceof BaseServerResponseException) {
|
||||||
ourLog.warn("Failure during REST processing: {}", e.toString());
|
ourLog.warn("Failure during REST processing: {}", e.toString());
|
||||||
statusCode = ((BaseServerResponseException) e).getStatusCode();
|
BaseServerResponseException baseServerResponseException = (BaseServerResponseException) e;
|
||||||
|
statusCode = baseServerResponseException.getStatusCode();
|
||||||
issue.getDetailsElement().setValue(e.getMessage());
|
issue.getDetailsElement().setValue(e.getMessage());
|
||||||
|
if (baseServerResponseException.getAdditionalMessages() != null) {
|
||||||
|
for (String next : baseServerResponseException.getAdditionalMessages()) {
|
||||||
|
BaseIssue issue2 = oo.addIssue();
|
||||||
|
issue2.getSeverityElement().setValue("error");
|
||||||
|
issue2.setDetails(next);
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
ourLog.error("Failure during REST processing", e);
|
ourLog.error("Failure during REST processing: " + e.toString(), e);
|
||||||
issue.getDetailsElement().setValue(e.toString() + "\n\n" + ExceptionUtils.getStackTrace(e));
|
issue.getDetailsElement().setValue(e.toString() + "\n\n" + ExceptionUtils.getStackTrace(e));
|
||||||
|
statusCode = Constants.STATUS_HTTP_500_INTERNAL_ERROR;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -681,9 +702,8 @@ public class RestfulServer extends HttpServlet {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the server. Note that this method is final to avoid accidentally introducing bugs in implementations,
|
* Initializes the server. Note that this method is final to avoid accidentally introducing bugs in implementations, but subclasses may put initialization code in {@link #initialize()}, which is
|
||||||
* but subclasses may put initialization code in {@link #initialize()}, which is called immediately before beginning
|
* called immediately before beginning initialization of the restful server's internal init.
|
||||||
* initialization of the restful server's internal init.
|
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public final void init() throws ServletException {
|
public final void init() throws ServletException {
|
||||||
|
@ -691,11 +711,6 @@ public class RestfulServer extends HttpServlet {
|
||||||
try {
|
try {
|
||||||
ourLog.info("Initializing HAPI FHIR restful server");
|
ourLog.info("Initializing HAPI FHIR restful server");
|
||||||
|
|
||||||
mySecurityManager = getSecurityManager();
|
|
||||||
if (null == mySecurityManager) {
|
|
||||||
ourLog.trace("No security manager has been provided");
|
|
||||||
}
|
|
||||||
|
|
||||||
ProvidedResourceScanner providedResourceScanner = new ProvidedResourceScanner(getFhirContext());
|
ProvidedResourceScanner providedResourceScanner = new ProvidedResourceScanner(getFhirContext());
|
||||||
providedResourceScanner.scanForProvidedResources(this);
|
providedResourceScanner.scanForProvidedResources(this);
|
||||||
|
|
||||||
|
@ -741,8 +756,7 @@ public class RestfulServer extends HttpServlet {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method may be overridden by subclasses to do perform initialization that needs to be performed prior to the
|
* This method may be overridden by subclasses to do perform initialization that needs to be performed prior to the server being used.
|
||||||
* server being used.
|
|
||||||
*/
|
*/
|
||||||
protected void initialize() throws ServletException {
|
protected void initialize() throws ServletException {
|
||||||
// nothing by default
|
// nothing by default
|
||||||
|
@ -763,9 +777,8 @@ public class RestfulServer extends HttpServlet {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the profile tagging behaviour for the server. When set to a value other than {@link AddProfileTagEnum#NEVER}
|
* Sets the profile tagging behaviour for the server. When set to a value other than {@link AddProfileTagEnum#NEVER} (which is the default), the server will automatically add a profile tag based
|
||||||
* (which is the default), the server will automatically add a profile tag based on the class of the resource(s)
|
* on the class of the resource(s) being returned.
|
||||||
* being returned.
|
|
||||||
*
|
*
|
||||||
* @param theAddProfileTag
|
* @param theAddProfileTag
|
||||||
* The behaviour enum (must not be null)
|
* The behaviour enum (must not be null)
|
||||||
|
@ -784,7 +797,6 @@ public class RestfulServer extends HttpServlet {
|
||||||
myImplementationDescription = theImplementationDescription;
|
myImplementationDescription = theImplementationDescription;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets (or clears) the list of interceptors
|
* Sets (or clears) the list of interceptors
|
||||||
*
|
*
|
||||||
|
@ -860,15 +872,7 @@ public class RestfulServer extends HttpServlet {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the security manager, or <code>null</code> if none
|
* Provide a server address strategy, which is used to determine what base URL to provide clients to refer to this server. Defaults to an instance of {@link IncomingRequestAddressStrategy}
|
||||||
*/
|
|
||||||
public void setSecurityManager(ISecurityManager theSecurityManager) {
|
|
||||||
mySecurityManager = theSecurityManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provide a server address strategy, which is used to determine what base URL to provide clients to refer to this
|
|
||||||
* server. Defaults to an instance of {@link IncomingRequestAddressStrategy}
|
|
||||||
*/
|
*/
|
||||||
public void setServerAddressStrategy(IServerAddressStrategy theServerAddressStrategy) {
|
public void setServerAddressStrategy(IServerAddressStrategy theServerAddressStrategy) {
|
||||||
Validate.notNull(theServerAddressStrategy, "Server address strategy can not be null");
|
Validate.notNull(theServerAddressStrategy, "Server address strategy can not be null");
|
||||||
|
@ -876,17 +880,14 @@ public class RestfulServer extends HttpServlet {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the server conformance provider, which is the provider that is used to generate the server's conformance
|
* Returns the server conformance provider, which is the provider that is used to generate the server's conformance (metadata) statement.
|
||||||
* (metadata) statement.
|
|
||||||
* <p>
|
* <p>
|
||||||
* By default, the {@link ServerConformanceProvider} is used, but this can be changed, or set to <code>null</code>
|
* By default, the {@link ServerConformanceProvider} is used, but this can be changed, or set to <code>null</code> if you do not wish to export a conformance statement.
|
||||||
* if you do not wish to export a conformance statement.
|
|
||||||
* </p>
|
* </p>
|
||||||
* Note that this method can only be called before the server is initialized.
|
* Note that this method can only be called before the server is initialized.
|
||||||
*
|
*
|
||||||
* @throws IllegalStateException
|
* @throws IllegalStateException
|
||||||
* Note that this method can only be called prior to {@link #init() initialization} and will throw an
|
* Note that this method can only be called prior to {@link #init() initialization} and will throw an {@link IllegalStateException} if called after that.
|
||||||
* {@link IllegalStateException} if called after that.
|
|
||||||
*/
|
*/
|
||||||
public void setServerConformanceProvider(Object theServerConformanceProvider) {
|
public void setServerConformanceProvider(Object theServerConformanceProvider) {
|
||||||
if (myStarted) {
|
if (myStarted) {
|
||||||
|
@ -896,24 +897,22 @@ public class RestfulServer extends HttpServlet {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the server's name, as exported in conformance profiles exported by the server. This is informational only,
|
* Sets the server's name, as exported in conformance profiles exported by the server. This is informational only, but can be helpful to set with something appropriate.
|
||||||
* but can be helpful to set with something appropriate.
|
|
||||||
*/
|
*/
|
||||||
public void setServerName(String theServerName) {
|
public void setServerName(String theServerName) {
|
||||||
myServerName = theServerName;
|
myServerName = theServerName;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the server's version, as exported in conformance profiles exported by the server. This is informational
|
* Gets the server's version, as exported in conformance profiles exported by the server. This is informational only, but can be helpful to set with something appropriate.
|
||||||
* only, but can be helpful to set with something appropriate.
|
|
||||||
*/
|
*/
|
||||||
public void setServerVersion(String theServerVersion) {
|
public void setServerVersion(String theServerVersion) {
|
||||||
myServerVersion = theServerVersion;
|
myServerVersion = theServerVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If set to <code>true</code> (default is false), the server will use browser friendly content-types (instead of
|
* If set to <code>true</code> (default is false), the server will use browser friendly content-types (instead of standard FHIR ones) when it detects that the request is coming from a browser
|
||||||
* standard FHIR ones) when it detects that the request is coming from a browser instead of a FHIR
|
* instead of a FHIR
|
||||||
*/
|
*/
|
||||||
public void setUseBrowserFriendlyContentTypes(boolean theUseBrowserFriendlyContentTypes) {
|
public void setUseBrowserFriendlyContentTypes(boolean theUseBrowserFriendlyContentTypes) {
|
||||||
myUseBrowserFriendlyContentTypes = theUseBrowserFriendlyContentTypes;
|
myUseBrowserFriendlyContentTypes = theUseBrowserFriendlyContentTypes;
|
||||||
|
@ -939,8 +938,8 @@ public class RestfulServer extends HttpServlet {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Bundle createBundleFromBundleProvider(RestfulServer theServer, HttpServletResponse theHttpResponse, IBundleProvider theResult, EncodingEnum theResponseEncoding, String theServerBase, String theCompleteUrl, boolean thePrettyPrint, boolean theRequestIsBrowser,
|
public static Bundle createBundleFromBundleProvider(RestfulServer theServer, HttpServletResponse theHttpResponse, IBundleProvider theResult, EncodingEnum theResponseEncoding,
|
||||||
NarrativeModeEnum theNarrativeMode, int theOffset, Integer theLimit, String theSearchId) {
|
String theServerBase, String theCompleteUrl, boolean thePrettyPrint, boolean theRequestIsBrowser, NarrativeModeEnum theNarrativeMode, int theOffset, Integer theLimit, String theSearchId) {
|
||||||
theHttpResponse.setStatus(200);
|
theHttpResponse.setStatus(200);
|
||||||
|
|
||||||
if (theRequestIsBrowser && theServer.isUseBrowserFriendlyContentTypes()) {
|
if (theRequestIsBrowser && theServer.isUseBrowserFriendlyContentTypes()) {
|
||||||
|
@ -1045,7 +1044,7 @@ public class RestfulServer extends HttpServlet {
|
||||||
|
|
||||||
Set<String> containedIds = new HashSet<String>();
|
Set<String> containedIds = new HashSet<String>();
|
||||||
for (IResource nextContained : next.getContained().getContainedResources()) {
|
for (IResource nextContained : next.getContained().getContainedResources()) {
|
||||||
if (nextContained.getId().isEmpty()==false) {
|
if (nextContained.getId().isEmpty() == false) {
|
||||||
containedIds.add(nextContained.getId().getValue());
|
containedIds.add(nextContained.getId().getValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1188,9 +1187,7 @@ public class RestfulServer extends HttpServlet {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine whether a response should be given in JSON or XML format based on the
|
* 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.
|
||||||
* incoming HttpServletRequest's <code>"_format"</code> parameter and <code>"Accept:"</code>
|
|
||||||
* HTTP header.
|
|
||||||
*/
|
*/
|
||||||
public static EncodingEnum determineResponseEncoding(HttpServletRequest theReq) {
|
public static EncodingEnum determineResponseEncoding(HttpServletRequest theReq) {
|
||||||
String[] format = theReq.getParameterValues(Constants.PARAM_FORMAT);
|
String[] format = theReq.getParameterValues(Constants.PARAM_FORMAT);
|
||||||
|
@ -1283,8 +1280,8 @@ public class RestfulServer extends HttpServlet {
|
||||||
return prettyPrint;
|
return prettyPrint;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void streamResponseAsBundle(RestfulServer theServer, HttpServletResponse theHttpResponse, Bundle bundle, EncodingEnum theResponseEncoding, String theServerBase, boolean thePrettyPrint, NarrativeModeEnum theNarrativeMode, boolean theRespondGzip)
|
public static void streamResponseAsBundle(RestfulServer theServer, HttpServletResponse theHttpResponse, Bundle bundle, EncodingEnum theResponseEncoding, String theServerBase,
|
||||||
throws IOException {
|
boolean thePrettyPrint, NarrativeModeEnum theNarrativeMode, boolean theRespondGzip) throws IOException {
|
||||||
assert !theServerBase.endsWith("/");
|
assert !theServerBase.endsWith("/");
|
||||||
|
|
||||||
Writer writer = getWriter(theHttpResponse, theRespondGzip);
|
Writer writer = getWriter(theHttpResponse, theRespondGzip);
|
||||||
|
@ -1302,14 +1299,14 @@ public class RestfulServer extends HttpServlet {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void streamResponseAsResource(RestfulServer theServer, HttpServletResponse theHttpResponse, IResource theResource, EncodingEnum theResponseEncoding, boolean thePrettyPrint, boolean theRequestIsBrowser, NarrativeModeEnum theNarrativeMode, boolean theRespondGzip,
|
public static void streamResponseAsResource(RestfulServer theServer, HttpServletResponse theHttpResponse, IResource theResource, EncodingEnum theResponseEncoding, boolean thePrettyPrint,
|
||||||
String theServerBase) throws IOException {
|
boolean theRequestIsBrowser, NarrativeModeEnum theNarrativeMode, boolean theRespondGzip, String theServerBase) throws IOException {
|
||||||
int stausCode = 200;
|
int stausCode = 200;
|
||||||
streamResponseAsResource(theServer, theHttpResponse, theResource, theResponseEncoding, thePrettyPrint, theRequestIsBrowser, theNarrativeMode, stausCode, theRespondGzip, theServerBase);
|
streamResponseAsResource(theServer, theHttpResponse, theResource, theResponseEncoding, thePrettyPrint, theRequestIsBrowser, theNarrativeMode, stausCode, theRespondGzip, theServerBase);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void streamResponseAsResource(RestfulServer theServer, HttpServletResponse theHttpResponse, IResource theResource, EncodingEnum theResponseEncoding, boolean thePrettyPrint, boolean theRequestIsBrowser, NarrativeModeEnum theNarrativeMode, int stausCode,
|
private static void streamResponseAsResource(RestfulServer theServer, HttpServletResponse theHttpResponse, IResource theResource, EncodingEnum theResponseEncoding, boolean thePrettyPrint,
|
||||||
boolean theRespondGzip, String theServerBase) throws IOException {
|
boolean theRequestIsBrowser, NarrativeModeEnum theNarrativeMode, int stausCode, boolean theRespondGzip, String theServerBase) throws IOException {
|
||||||
theHttpResponse.setStatus(stausCode);
|
theHttpResponse.setStatus(stausCode);
|
||||||
|
|
||||||
if (theResource.getId() != null && theResource.getId().hasIdPart() && isNotBlank(theServerBase)) {
|
if (theResource.getId() != null && theResource.getId().hasIdPart() && isNotBlank(theServerBase)) {
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
package ca.uhn.fhir.rest.server.exceptions;
|
package ca.uhn.fhir.rest.server.exceptions;
|
||||||
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import ca.uhn.fhir.model.base.resource.BaseOperationOutcome;
|
import ca.uhn.fhir.model.base.resource.BaseOperationOutcome;
|
||||||
|
@ -27,9 +29,8 @@ import ca.uhn.fhir.model.base.resource.BaseOperationOutcome;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base class for RESTful client and server exceptions. RESTful client methods will only throw exceptions which are
|
* Base class for RESTful client and server exceptions. RESTful client methods will only throw exceptions which are subclasses of this exception type, and RESTful server methods should also only call
|
||||||
* subclasses of this exception type, and RESTful server methods should also only call subclasses of this exception
|
* subclasses of this exception type.
|
||||||
* type.
|
|
||||||
*/
|
*/
|
||||||
public abstract class BaseServerResponseException extends RuntimeException {
|
public abstract class BaseServerResponseException extends RuntimeException {
|
||||||
|
|
||||||
|
@ -49,6 +50,7 @@ public abstract class BaseServerResponseException extends RuntimeException {
|
||||||
registerExceptionType(NotImplementedOperationException.STATUS_CODE, NotImplementedOperationException.class);
|
registerExceptionType(NotImplementedOperationException.STATUS_CODE, NotImplementedOperationException.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<String> myAdditionalMessages = null;
|
||||||
private BaseOperationOutcome myBaseOperationOutcome;
|
private BaseOperationOutcome myBaseOperationOutcome;
|
||||||
private String myResponseBody;
|
private String myResponseBody;
|
||||||
private String myResponseMimeType;
|
private String myResponseMimeType;
|
||||||
|
@ -68,6 +70,23 @@ public abstract class BaseServerResponseException extends RuntimeException {
|
||||||
myBaseOperationOutcome = null;
|
myBaseOperationOutcome = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @param theStatusCode
|
||||||
|
* The HTTP status code corresponding to this problem
|
||||||
|
* @param theMessage
|
||||||
|
* The message
|
||||||
|
*/
|
||||||
|
public BaseServerResponseException(int theStatusCode, String... theMessages) {
|
||||||
|
super(theMessages != null && theMessages.length > 0 ? theMessages[0] : null);
|
||||||
|
myStatusCode = theStatusCode;
|
||||||
|
myBaseOperationOutcome = null;
|
||||||
|
if (theMessages != null && theMessages.length > 1) {
|
||||||
|
myAdditionalMessages = Arrays.asList(Arrays.copyOfRange(theMessages, 1, theMessages.length, String[].class));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
*
|
*
|
||||||
|
@ -76,8 +95,7 @@ public abstract class BaseServerResponseException extends RuntimeException {
|
||||||
* @param theMessage
|
* @param theMessage
|
||||||
* The message
|
* The message
|
||||||
* @param theBaseOperationOutcome
|
* @param theBaseOperationOutcome
|
||||||
* An BaseOperationOutcome resource to return to the calling client (in a server) or the BaseOperationOutcome
|
* An BaseOperationOutcome resource to return to the calling client (in a server) or the BaseOperationOutcome that was returned from the server (in a client)
|
||||||
* that was returned from the server (in a client)
|
|
||||||
*/
|
*/
|
||||||
public BaseServerResponseException(int theStatusCode, String theMessage, BaseOperationOutcome theBaseOperationOutcome) {
|
public BaseServerResponseException(int theStatusCode, String theMessage, BaseOperationOutcome theBaseOperationOutcome) {
|
||||||
super(theMessage);
|
super(theMessage);
|
||||||
|
@ -111,8 +129,7 @@ public abstract class BaseServerResponseException extends RuntimeException {
|
||||||
* @param theCause
|
* @param theCause
|
||||||
* The underlying cause exception
|
* The underlying cause exception
|
||||||
* @param theBaseOperationOutcome
|
* @param theBaseOperationOutcome
|
||||||
* An BaseOperationOutcome resource to return to the calling client (in a server) or the BaseOperationOutcome
|
* An BaseOperationOutcome resource to return to the calling client (in a server) or the BaseOperationOutcome that was returned from the server (in a client)
|
||||||
* that was returned from the server (in a client)
|
|
||||||
*/
|
*/
|
||||||
public BaseServerResponseException(int theStatusCode, String theMessage, Throwable theCause, BaseOperationOutcome theBaseOperationOutcome) {
|
public BaseServerResponseException(int theStatusCode, String theMessage, Throwable theCause, BaseOperationOutcome theBaseOperationOutcome) {
|
||||||
super(theMessage, theCause);
|
super(theMessage, theCause);
|
||||||
|
@ -142,8 +159,7 @@ public abstract class BaseServerResponseException extends RuntimeException {
|
||||||
* @param theCause
|
* @param theCause
|
||||||
* The underlying cause exception
|
* The underlying cause exception
|
||||||
* @param theBaseOperationOutcome
|
* @param theBaseOperationOutcome
|
||||||
* An BaseOperationOutcome resource to return to the calling client (in a server) or the BaseOperationOutcome
|
* An BaseOperationOutcome resource to return to the calling client (in a server) or the BaseOperationOutcome that was returned from the server (in a client)
|
||||||
* that was returned from the server (in a client)
|
|
||||||
*/
|
*/
|
||||||
public BaseServerResponseException(int theStatusCode, Throwable theCause, BaseOperationOutcome theBaseOperationOutcome) {
|
public BaseServerResponseException(int theStatusCode, Throwable theCause, BaseOperationOutcome theBaseOperationOutcome) {
|
||||||
super(theCause.toString(), theCause);
|
super(theCause.toString(), theCause);
|
||||||
|
@ -151,6 +167,10 @@ public abstract class BaseServerResponseException extends RuntimeException {
|
||||||
myBaseOperationOutcome = theBaseOperationOutcome;
|
myBaseOperationOutcome = theBaseOperationOutcome;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<String> getAdditionalMessages() {
|
||||||
|
return myAdditionalMessages;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the {@link BaseOperationOutcome} resource if any which was supplied in the response, or <code>null</code>
|
* Returns the {@link BaseOperationOutcome} resource if any which was supplied in the response, or <code>null</code>
|
||||||
*/
|
*/
|
||||||
|
@ -159,8 +179,7 @@ public abstract class BaseServerResponseException extends RuntimeException {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* In a RESTful client, this method will be populated with the body of the HTTP respone if one was provided by the
|
* In a RESTful client, this method will be populated with the body of the HTTP respone if one was provided by the server, or <code>null</code> otherwise.
|
||||||
* server, or <code>null</code> otherwise.
|
|
||||||
* <p>
|
* <p>
|
||||||
* In a restful server, this method is currently ignored.
|
* In a restful server, this method is currently ignored.
|
||||||
* </p>
|
* </p>
|
||||||
|
@ -170,8 +189,7 @@ public abstract class BaseServerResponseException extends RuntimeException {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* In a RESTful client, this method will be populated with the HTTP status code that was returned with the HTTP
|
* In a RESTful client, this method will be populated with the HTTP status code that was returned with the HTTP response.
|
||||||
* response.
|
|
||||||
* <p>
|
* <p>
|
||||||
* In a restful server, this method is currently ignored.
|
* In a restful server, this method is currently ignored.
|
||||||
* </p>
|
* </p>
|
||||||
|
@ -188,11 +206,11 @@ public abstract class BaseServerResponseException extends RuntimeException {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the BaseOperationOutcome resource associated with this exception. In server
|
* Sets the BaseOperationOutcome resource associated with this exception. In server implementations, this is the OperartionOutcome resource to include with the HTTP response. In client
|
||||||
* implementations, this is the OperartionOutcome resource to include with the HTTP response. In
|
* implementations you should not call this method.
|
||||||
* client implementations you should not call this method.
|
|
||||||
*
|
*
|
||||||
* @param theBaseOperationOutcome The BaseOperationOutcome resource
|
* @param theBaseOperationOutcome
|
||||||
|
* The BaseOperationOutcome resource
|
||||||
*/
|
*/
|
||||||
public void setOperationOutcome(BaseOperationOutcome theBaseOperationOutcome) {
|
public void setOperationOutcome(BaseOperationOutcome theBaseOperationOutcome) {
|
||||||
myBaseOperationOutcome = theBaseOperationOutcome;
|
myBaseOperationOutcome = theBaseOperationOutcome;
|
||||||
|
|
|
@ -77,7 +77,7 @@ public class UnprocessableEntityException extends BaseServerResponseException {
|
||||||
* Constructor which accepts an array of Strings describing the issue. This strings will be translated into an {@link BaseOperationOutcome} resource which will be supplied in the response.
|
* Constructor which accepts an array of Strings describing the issue. This strings will be translated into an {@link BaseOperationOutcome} resource which will be supplied in the response.
|
||||||
*/
|
*/
|
||||||
public UnprocessableEntityException(String... theMessage) {
|
public UnprocessableEntityException(String... theMessage) {
|
||||||
super(STATUS_CODE, theMessage[0]); // TODO: this used to generate an OperationOutcome - why?
|
super(STATUS_CODE, theMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,7 +64,8 @@ public interface IServerInterceptor {
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param theRequestDetails
|
* @param theRequestDetails
|
||||||
* A bean containing details about the request that is about to be processed, including
|
* 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 theRequest
|
* @param theRequest
|
||||||
* The incoming request
|
* The incoming request
|
||||||
* @param theResponse
|
* @param theResponse
|
||||||
|
@ -82,7 +83,8 @@ public interface IServerInterceptor {
|
||||||
* This method is called after the server implementation method has been called, but before any attempt to stream the response back to the client
|
* 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
|
* @param theRequestDetails
|
||||||
* A bean containing details about the request that is about to be processed, including
|
* 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
|
* @param theResponseObject
|
||||||
* The actual object which is being streamed to the client as a response
|
* The actual object which is being streamed to the client as a response
|
||||||
* @param theRequest
|
* @param theRequest
|
||||||
|
@ -124,7 +126,8 @@ public interface IServerInterceptor {
|
||||||
* This method is called after the server implementation method has been called, but before any attempt to stream the response back to the client
|
* 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
|
* @param theRequestDetails
|
||||||
* A bean containing details about the request that is about to be processed, including
|
* 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
|
* @param theResponseObject
|
||||||
* The actual object which is being streamed to the client as a response
|
* The actual object which is being streamed to the client as a response
|
||||||
* @param theRequest
|
* @param theRequest
|
||||||
|
@ -145,7 +148,8 @@ public interface IServerInterceptor {
|
||||||
* This method is called after the server implementation method has been called, but before any attempt to stream the response back to the client
|
* 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
|
* @param theRequestDetails
|
||||||
* A bean containing details about the request that is about to be processed, including
|
* 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
|
* @param theResponseObject
|
||||||
* The actual object which is being streamed to the client as a response
|
* The actual object which is being streamed to the client as a response
|
||||||
* @param theRequest
|
* @param theRequest
|
||||||
|
@ -171,7 +175,9 @@ public interface IServerInterceptor {
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param theRequestDetails
|
* @param theRequestDetails
|
||||||
* A bean containing details about the request that is about to be processed, including
|
* Contains either <code>null</code>, or 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}. This parameter may be
|
||||||
|
* null if the request processing did not successfully parse the incoming request, but will generally not be null.
|
||||||
* @param theResponseObject
|
* @param theResponseObject
|
||||||
* The actual object which is being streamed to the client as a response
|
* The actual object which is being streamed to the client as a response
|
||||||
* @param theRequest
|
* @param theRequest
|
||||||
|
|
|
@ -357,6 +357,8 @@ public class XmlUtil {
|
||||||
case '"':
|
case '"':
|
||||||
case '&':
|
case '&':
|
||||||
hasEscapable = true;
|
hasEscapable = true;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,7 @@ import ca.uhn.fhir.rest.annotation.Search;
|
||||||
import ca.uhn.fhir.rest.param.StringParam;
|
import ca.uhn.fhir.rest.param.StringParam;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||||
|
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||||
import ca.uhn.fhir.util.PortUtil;
|
import ca.uhn.fhir.util.PortUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -54,6 +55,21 @@ public class ExceptionTest {
|
||||||
ourExceptionType=null;
|
ourExceptionType=null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testThrowUnprocessableEntityWithMultipleMessages() throws Exception {
|
||||||
|
{
|
||||||
|
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?throwUnprocessableEntityWithMultipleMessages=aaa");
|
||||||
|
HttpResponse status = ourClient.execute(httpGet);
|
||||||
|
String responseContent = IOUtils.toString(status.getEntity().getContent());
|
||||||
|
IOUtils.closeQuietly(status.getEntity().getContent());
|
||||||
|
ourLog.info(responseContent);
|
||||||
|
assertEquals(422, status.getStatusLine().getStatusCode());
|
||||||
|
OperationOutcome oo = (OperationOutcome) servlet.getFhirContext().newXmlParser().parseResource(responseContent);
|
||||||
|
assertThat(oo.getIssueFirstRep().getDetails().getValue(), StringContains.containsString("message1"));
|
||||||
|
assertEquals(3, oo.getIssue().size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testInternalError() throws Exception {
|
public void testInternalError() throws Exception {
|
||||||
{
|
{
|
||||||
|
@ -162,10 +178,15 @@ public class ExceptionTest {
|
||||||
|
|
||||||
|
|
||||||
@Search
|
@Search
|
||||||
public List<Patient> findPatient(@RequiredParam(name = "throwInternalError") StringParam theParam) {
|
public List<Patient> throwInternalError(@RequiredParam(name = "throwInternalError") StringParam theParam) {
|
||||||
throw new InternalErrorException("Exception Text");
|
throw new InternalErrorException("Exception Text");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Search
|
||||||
|
public List<Patient> throwUnprocessableEntityWithMultipleMessages(@RequiredParam(name = "throwUnprocessableEntityWithMultipleMessages") StringParam theParam) {
|
||||||
|
throw new UnprocessableEntityException("message1", "message2", "message3");
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Class<? extends IResource> getResourceType() {
|
public Class<? extends IResource> getResourceType() {
|
||||||
return Patient.class;
|
return Patient.class;
|
||||||
|
|
|
@ -1378,11 +1378,6 @@ public class ResfulServerMethodTest {
|
||||||
return myResourceProviders;
|
return myResourceProviders;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public ISecurityManager getSecurityManager() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,165 @@
|
||||||
|
package ca.uhn.fhir.rest.server.interceptor;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
import org.apache.http.HttpResponse;
|
||||||
|
import org.apache.http.client.methods.HttpGet;
|
||||||
|
import org.apache.http.impl.client.CloseableHttpClient;
|
||||||
|
import org.apache.http.impl.client.HttpClientBuilder;
|
||||||
|
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
|
||||||
|
import org.eclipse.jetty.server.Server;
|
||||||
|
import org.eclipse.jetty.servlet.ServletHandler;
|
||||||
|
import org.eclipse.jetty.servlet.ServletHolder;
|
||||||
|
import org.hamcrest.core.StringContains;
|
||||||
|
import org.junit.AfterClass;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.mockito.invocation.InvocationOnMock;
|
||||||
|
import org.mockito.stubbing.Answer;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.model.dstu.composite.HumanNameDt;
|
||||||
|
import ca.uhn.fhir.model.dstu.composite.IdentifierDt;
|
||||||
|
import ca.uhn.fhir.model.dstu.resource.Patient;
|
||||||
|
import ca.uhn.fhir.model.dstu.valueset.IdentifierUseEnum;
|
||||||
|
import ca.uhn.fhir.model.primitive.IdDt;
|
||||||
|
import ca.uhn.fhir.model.primitive.UriDt;
|
||||||
|
import ca.uhn.fhir.rest.annotation.IdParam;
|
||||||
|
import ca.uhn.fhir.rest.annotation.Read;
|
||||||
|
import ca.uhn.fhir.rest.annotation.RequiredParam;
|
||||||
|
import ca.uhn.fhir.rest.annotation.Search;
|
||||||
|
import ca.uhn.fhir.rest.method.RequestDetails;
|
||||||
|
import ca.uhn.fhir.rest.server.IResourceProvider;
|
||||||
|
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||||
|
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||||
|
import ca.uhn.fhir.util.PortUtil;
|
||||||
|
|
||||||
|
public class ExceptionHandlingInterceptorTest {
|
||||||
|
|
||||||
|
private static CloseableHttpClient ourClient;
|
||||||
|
private static int ourPort;
|
||||||
|
private static Server ourServer;
|
||||||
|
private static RestfulServer servlet;
|
||||||
|
private IServerInterceptor myInterceptor;
|
||||||
|
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExceptionHandlingInterceptorTest.class);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testThrowUnprocessableEntityException() throws Exception {
|
||||||
|
|
||||||
|
when(myInterceptor.incomingRequestPreProcessed(any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
|
||||||
|
when(myInterceptor.incomingRequestPostProcessed(any(RequestDetails.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
|
||||||
|
when(myInterceptor.handleException(any(RequestDetails.class), any(Throwable.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
|
||||||
|
|
||||||
|
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_query=throwUnprocessableEntityException");
|
||||||
|
HttpResponse status = ourClient.execute(httpGet);
|
||||||
|
ourLog.info(IOUtils.toString(status.getEntity().getContent()));
|
||||||
|
assertEquals(422, status.getStatusLine().getStatusCode());
|
||||||
|
IOUtils.closeQuietly(status.getEntity().getContent());
|
||||||
|
|
||||||
|
ArgumentCaptor<Throwable> captor = ArgumentCaptor.forClass(Throwable.class);
|
||||||
|
verify(myInterceptor, times(1)).handleException(any(RequestDetails.class), captor.capture(), any(HttpServletRequest.class), any(HttpServletResponse.class));
|
||||||
|
|
||||||
|
assertEquals(UnprocessableEntityException.class, captor.getValue().getClass());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testThrowUnprocessableEntityExceptionAndOverrideResponse() throws Exception {
|
||||||
|
|
||||||
|
when(myInterceptor.incomingRequestPreProcessed(any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
|
||||||
|
when(myInterceptor.incomingRequestPostProcessed(any(RequestDetails.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
|
||||||
|
|
||||||
|
when(myInterceptor.handleException(any(RequestDetails.class), any(Throwable.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenAnswer(new Answer<Boolean>() {
|
||||||
|
@Override
|
||||||
|
public Boolean answer(InvocationOnMock theInvocation) throws Throwable {
|
||||||
|
HttpServletResponse resp = (HttpServletResponse) theInvocation.getArguments()[3];
|
||||||
|
resp.setStatus(405);
|
||||||
|
resp.setContentType("text/plain");
|
||||||
|
resp.getWriter().write("HELP IM A BUG");
|
||||||
|
resp.getWriter().close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_query=throwUnprocessableEntityException");
|
||||||
|
HttpResponse status = ourClient.execute(httpGet);
|
||||||
|
String responseContent = IOUtils.toString(status.getEntity().getContent());
|
||||||
|
ourLog.info(responseContent);
|
||||||
|
assertEquals(405, status.getStatusLine().getStatusCode());
|
||||||
|
IOUtils.closeQuietly(status.getEntity().getContent());
|
||||||
|
|
||||||
|
assertEquals("HELP IM A BUG", responseContent);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterClass
|
||||||
|
public static void afterClass() throws Exception {
|
||||||
|
ourServer.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void before() {
|
||||||
|
myInterceptor = mock(IServerInterceptor.class);
|
||||||
|
servlet.setInterceptors(Collections.singletonList(myInterceptor));
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public static void beforeClass() throws Exception {
|
||||||
|
ourPort = PortUtil.findFreePort();
|
||||||
|
ourServer = new Server(ourPort);
|
||||||
|
|
||||||
|
DummyPatientResourceProvider patientProvider = new DummyPatientResourceProvider();
|
||||||
|
|
||||||
|
ServletHandler proxyHandler = new ServletHandler();
|
||||||
|
servlet = new RestfulServer();
|
||||||
|
servlet.setResourceProviders(patientProvider);
|
||||||
|
ServletHolder servletHolder = new ServletHolder(servlet);
|
||||||
|
proxyHandler.addServletWithMapping(servletHolder, "/*");
|
||||||
|
ourServer.setHandler(proxyHandler);
|
||||||
|
ourServer.start();
|
||||||
|
|
||||||
|
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
|
||||||
|
HttpClientBuilder builder = HttpClientBuilder.create();
|
||||||
|
builder.setConnectionManager(connectionManager);
|
||||||
|
ourClient = builder.build();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by dsotnikov on 2/25/2014.
|
||||||
|
*/
|
||||||
|
public static class DummyPatientResourceProvider implements IResourceProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the resource by its identifier
|
||||||
|
*
|
||||||
|
* @param theId
|
||||||
|
* The resource identity
|
||||||
|
* @return The resource
|
||||||
|
*/
|
||||||
|
@Search(queryName = "throwUnprocessableEntityException")
|
||||||
|
public List<Patient> throwUnprocessableEntityException() {
|
||||||
|
throw new UnprocessableEntityException("Unprocessable!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<Patient> getResourceType() {
|
||||||
|
return Patient.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -221,7 +221,7 @@ public class TinderStructuresMojo extends AbstractMojo {
|
||||||
String dtOutputDir = "target/generated-sources/tinder/ca/uhn/fhir/model/dev/composite";
|
String dtOutputDir = "target/generated-sources/tinder/ca/uhn/fhir/model/dev/composite";
|
||||||
|
|
||||||
ResourceGeneratorUsingSpreadsheet rp = new ResourceGeneratorUsingSpreadsheet("dev", ".");
|
ResourceGeneratorUsingSpreadsheet rp = new ResourceGeneratorUsingSpreadsheet("dev", ".");
|
||||||
rp.setBaseResourceNames(Arrays.asList("referralrequest", "patient","practitioner","encounter",
|
rp.setBaseResourceNames(Arrays.asList("conformance", "referralrequest", "patient","practitioner","encounter",
|
||||||
"organization","location","relatedperson","appointment","slot","order","availability","device", "valueset"));
|
"organization","location","relatedperson","appointment","slot","order","availability","device", "valueset"));
|
||||||
rp.parse();
|
rp.parse();
|
||||||
rp.bindValueSets(vsp);
|
rp.bindValueSets(vsp);
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
curl https://api.github.com/repos/jamesagnew/hapi-fhir/releases
|
28
pom.xml
28
pom.xml
|
@ -28,6 +28,13 @@
|
||||||
<url>https://github.com/jamesagnew/hapi-fhir/issues/</url>
|
<url>https://github.com/jamesagnew/hapi-fhir/issues/</url>
|
||||||
</issueManagement>
|
</issueManagement>
|
||||||
|
|
||||||
|
<distributionManagement>
|
||||||
|
<site>
|
||||||
|
<id>git.server</id>
|
||||||
|
<url>scm:git:git@github.com:jamesagnew/hapi-fhir.git</url>
|
||||||
|
</site>
|
||||||
|
</distributionManagement>
|
||||||
|
|
||||||
<scm>
|
<scm>
|
||||||
<connection>scm:git:git@github.com:jamesagnew/hapi-fhir.git</connection>
|
<connection>scm:git:git@github.com:jamesagnew/hapi-fhir.git</connection>
|
||||||
<url>scm:git:git@github.com:jamesagnew/hapi-fhir.git</url>
|
<url>scm:git:git@github.com:jamesagnew/hapi-fhir.git</url>
|
||||||
|
@ -502,12 +509,33 @@
|
||||||
</reportSet>
|
</reportSet>
|
||||||
</reportSets>
|
</reportSets>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.codehaus.mojo</groupId>
|
||||||
|
<artifactId>findbugs-maven-plugin</artifactId>
|
||||||
|
<version>3.0.0</version>
|
||||||
|
<configuration>
|
||||||
|
<classFilesDirectory>./hapi-fhir-base/target/classes</classFilesDirectory>
|
||||||
|
</configuration>
|
||||||
|
<reportSets>
|
||||||
|
<reportSet>
|
||||||
|
<reports>
|
||||||
|
<report>findbugs</report>
|
||||||
|
</reports>
|
||||||
|
</reportSet>
|
||||||
|
</reportSets>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
<!-- <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-linkcheck-plugin</artifactId>
|
<!-- <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-linkcheck-plugin</artifactId>
|
||||||
<version>1.1</version> </plugin> -->
|
<version>1.1</version> </plugin> -->
|
||||||
</plugins>
|
</plugins>
|
||||||
</reporting>
|
</reporting>
|
||||||
|
|
||||||
<profiles>
|
<profiles>
|
||||||
|
<profile>
|
||||||
|
<id>ROOT</id>
|
||||||
|
<modules>
|
||||||
|
</modules>
|
||||||
|
</profile>
|
||||||
<profile>
|
<profile>
|
||||||
<id>SIGN_ARTIFACTS</id>
|
<id>SIGN_ARTIFACTS</id>
|
||||||
<activation>
|
<activation>
|
||||||
|
|
|
@ -7,11 +7,19 @@
|
||||||
</properties>
|
</properties>
|
||||||
<body>
|
<body>
|
||||||
<release version="0.8" date="TBD">
|
<release version="0.8" date="TBD">
|
||||||
<action type="add" issue="38">
|
<action>
|
||||||
|
<![CDATA[<b>API CHANGE:</b>]]> The "FHIR structures" for DSTU1 (the classes which model the
|
||||||
|
resources and composite datatypes) have been moved out of the core JAR into their
|
||||||
|
own JAR, in order to allow support for DEV resources, and DSTU2 resources when thast
|
||||||
|
version is finalized. See
|
||||||
|
<![CDATA[<a href="./doc_upgrading.html">upgrading</a>]]>
|
||||||
|
for more information.
|
||||||
|
</action>
|
||||||
|
<action type="add" issue="38" dev="wdebeau1">
|
||||||
Profile generation on the server was not working due to IdDt being
|
Profile generation on the server was not working due to IdDt being
|
||||||
incorrectly used. Thanks to Bill de Beaubien for the pull request!
|
incorrectly used. Thanks to Bill de Beaubien for the pull request!
|
||||||
</action>
|
</action>
|
||||||
<action type="add" issue="42">
|
<action type="add" issue="42" dev="wdebeau1">
|
||||||
Profiles did not generate correctly if a resource definition class had a
|
Profiles did not generate correctly if a resource definition class had a
|
||||||
defined extension which was of a composite type. Thanks to Bill de Beaubien for the pull request!
|
defined extension which was of a composite type. Thanks to Bill de Beaubien for the pull request!
|
||||||
</action>
|
</action>
|
||||||
|
@ -19,7 +27,7 @@
|
||||||
Remove unnessecary IOException from narrative generator API. Thanks to
|
Remove unnessecary IOException from narrative generator API. Thanks to
|
||||||
Petro Mykhailysyn for the pull request!
|
Petro Mykhailysyn for the pull request!
|
||||||
</action>
|
</action>
|
||||||
<action type="add" issue="48">
|
<action type="add" issue="48" dev="wdebeau1">
|
||||||
Introduced a new
|
Introduced a new
|
||||||
<![CDATA[<code>@ProvidesResources</code>]]> annotation which can be added to
|
<![CDATA[<code>@ProvidesResources</code>]]> annotation which can be added to
|
||||||
resource provider and servers to allow them to declare additional resource
|
resource provider and servers to allow them to declare additional resource
|
||||||
|
@ -28,6 +36,16 @@
|
||||||
returns a default Patient, but sometimes uses a custom subclass).
|
returns a default Patient, but sometimes uses a custom subclass).
|
||||||
Thanks to Bill de Beaubien for the pull request!
|
Thanks to Bill de Beaubien for the pull request!
|
||||||
</action>
|
</action>
|
||||||
|
<action>
|
||||||
|
Add a new method <![CDATA[handleException]]> to the server interceptor
|
||||||
|
framework which allows interceptors to be notified of any exceptions and
|
||||||
|
runtime errors within server methods. Interceptors may optionally also
|
||||||
|
override the default error handling behaviour of the RestfulServer.
|
||||||
|
</action>
|
||||||
|
<action dev="wdebeau1">
|
||||||
|
Add constants to BaseResource for the "_id" search parameter which all resources
|
||||||
|
should support.
|
||||||
|
</action>
|
||||||
</release>
|
</release>
|
||||||
<release version="0.7" date="2014-Oct-23">
|
<release version="0.7" date="2014-Oct-23">
|
||||||
<action type="add" issue="30">
|
<action type="add" issue="30">
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
<mxGraphModel dx="940" dy="431" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" fold="1" page="1" pageScale="1" pageWidth="826" pageHeight="1169" style="default-style2" math="0"><root><mxCell id="0"/><mxCell id="1" parent="0"/><mxCell id="2" value="RESTful
Server" style="shape=umlLifeline;perimeter=lifelinePerimeter;size=46;strokeColor=#0000FF;strokeWidth=2;fillColor=#99FFFF" parent="1" vertex="1"><mxGeometry x="150" y="20" width="100" height="280" as="geometry"/></mxCell><mxCell id="3" value="Interceptor" style="shape=umlLifeline;perimeter=lifelinePerimeter;size=46;strokeColor=#0000FF;strokeWidth=2;fillColor=#99FFFF" parent="1" vertex="1"><mxGeometry x="280" y="20" width="100" height="280" as="geometry"/></mxCell><mxCell id="4" value="Resource/Plain
Provider
Method" style="shape=umlLifeline;perimeter=lifelinePerimeter;size=46;strokeColor=#0000FF;strokeWidth=2;fillColor=#99FFFF" parent="1" vertex="1"><mxGeometry x="400" y="20" width="100" height="280" as="geometry"/></mxCell><mxCell id="7" value="" style="ellipse;shape=startState;fillColor=#000000;strokeColor=#ff0000;" parent="1" vertex="1"><mxGeometry x="70" y="80" width="30" height="30" as="geometry"/></mxCell><mxCell id="8" value="Incoming Request" style="edgeStyle=elbowEdgeStyle;elbow=horizontal;verticalAlign=bottom;endArrow=open;endSize=8;strokeColor=#ff0000;entryX=0.5;entryY=0.3;entryPerimeter=0" parent="1" source="7" edge="1"><mxGeometry x="70" y="80" as="geometry"><mxPoint x="200" y="95" as="targetPoint"/></mxGeometry></mxCell><mxCell id="21" value="Request is handled" style="rounded=1;whiteSpace=wrap" parent="1" vertex="1"><mxGeometry x="390" y="150" width="120" height="30" as="geometry"/></mxCell><mxCell id="22" value="" style="edgeStyle=none;align=left;entryX=0.5;entryY=0.632;entryPerimeter=0;exitX=0.5;exitY=0.632;exitPerimeter=0" parent="1" edge="1"><mxGeometry width="100" height="100" relative="1" as="geometry"><mxPoint x="200" y="140.15999999999997" as="sourcePoint"/><mxPoint x="450" y="140.15999999999997" as="targetPoint"/></mxGeometry></mxCell><mxCell id="24" value="" parent="1" vertex="1"><mxGeometry x="320" y="233" width="20" height="27" as="geometry"/></mxCell><mxCell id="25" value="handleException" style="edgeStyle=elbowEdgeStyle;elbow=vertical;verticalAlign=bottom;endArrow=block;exitX=0.5;exitY=0.478;exitPerimeter=0;align=left" parent="1" target="24" edge="1"><mxGeometry x="221.75" y="233.25" as="geometry"><mxPoint x="200" y="233.39999999999998" as="sourcePoint"/></mxGeometry></mxCell><mxCell id="26" value="return true;" style="edgeStyle=elbowEdgeStyle;elbow=vertical;verticalAlign=bottom;dashed=1;endArrow=open;endSize=8;" parent="1" source="24" edge="1"><mxGeometry x="221.75" y="233.25" as="geometry"><mxPoint x="200" y="260" as="targetPoint"/></mxGeometry></mxCell><mxCell id="27" value="" style="ellipse;shape=endState;fillColor=#000000;strokeColor=#ff0000;align=left" parent="1" vertex="1"><mxGeometry x="70" y="270" width="30" height="30" as="geometry"/></mxCell><mxCell id="28" value="Response" style="edgeStyle=orthogonalEdgeStyle;endArrow=block;dashed=0;dashPattern=1 4;align=left;entryX=1;entryY=0.5;exitX=0.5;exitY=0.954;exitPerimeter=0;endFill=1;strokeWidth=1;strokeColor=#FF0000" parent="1" target="27" edge="1"><mxGeometry x="0.500299820107935" y="-13" width="100" height="100" relative="1" as="geometry"><mxPoint x="200" y="285.05999999999995" as="sourcePoint"/><mxPoint x="650" y="213" as="targetPoint"/><mxPoint x="-8" y="-12" as="offset"/></mxGeometry></mxCell><mxCell id="29" value="" style="ellipse;shape=cloud;whiteSpace=wrap;html=1;" vertex="1" parent="1"><mxGeometry x="175" y="100" width="50" height="30" as="geometry"/></mxCell><mxCell id="30" value="throw exception" style="edgeStyle=elbowEdgeStyle;elbow=vertical;verticalAlign=bottom;dashed=1;endArrow=open;endSize=8;" edge="1" parent="1"><mxGeometry x="351.75" y="173.25" as="geometry"><mxPoint x="200" y="200" as="targetPoint"/><mxPoint x="450" y="200" as="sourcePoint"/></mxGeometry></mxCell></root></mxGraphModel>
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,4 @@
|
||||||
|
In this directory:
|
||||||
|
|
||||||
|
* XCF files are opened in The GIMP
|
||||||
|
* XML files are opened in draw.io
|
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 5.3 KiB |
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 7.7 KiB |
|
@ -70,6 +70,32 @@
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
<br clear="all"/>
|
||||||
|
<subsection name="Exception Handling">
|
||||||
|
|
||||||
|
<img src="svg/restful-server-interceptors-exception.svg" alt="Interceptors" align="right"/>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
In the event of an exception being thrown within the server, the interceptor
|
||||||
|
method
|
||||||
|
<code><a href="./apidocs/ca/uhn/fhir/rest/server/interceptor/IServerInterceptor.html#handleException(ca.uhn.fhir.rest.method.RequestDetails,%20java.lang.Throwable,%20javax.servlet.http.HttpServletRequest,%20javax.servlet.http.HttpServletResponse)">handleException</a></code>
|
||||||
|
will be called. This applies both to HAPI-FHIR defined exceptions thrown within resource provider methods
|
||||||
|
you have created as well as unexpected exceptions such as NullPointerException thrown
|
||||||
|
at any point in the handling chain.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
In general, you will want to return <code>true</code> from the <code>handleException</code>
|
||||||
|
method, which means that processing continues normally (RestfulServer will return an
|
||||||
|
HTTP 4xx or 5xx response automatically depending on the specific exception which was thrown).
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
However, you may override the server's built-in exception handling by returning
|
||||||
|
<code>false</code>. In this case, you must provide your own response by
|
||||||
|
interacting with the <code>HttpServletResponse</code> object which is
|
||||||
|
passed in.
|
||||||
|
</p>
|
||||||
|
</subsection>
|
||||||
|
|
||||||
<br clear="all"/>
|
<br clear="all"/>
|
||||||
<subsection name="Registering Interceptors">
|
<subsection name="Registering Interceptors">
|
||||||
|
|
||||||
|
@ -129,6 +155,29 @@
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section name="Creating Interceptors">
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Creating your own interceptors is easy. HAPI-FHIR provides a class called
|
||||||
|
<code>InterceptorAdapter</code> which you can extend and then override any
|
||||||
|
methods you wish. The following example shows a simple request counter.
|
||||||
|
</p>
|
||||||
|
<macro name="snippet">
|
||||||
|
<param name="id" value="interceptor" />
|
||||||
|
<param name="file" value="examples/src/main/java/example/RequestCounterInterceptor.java" />
|
||||||
|
</macro>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The following example shows an exception handling interceptor which
|
||||||
|
overrides the built-in exception handling by providing a custom response.
|
||||||
|
</p>
|
||||||
|
<macro name="snippet">
|
||||||
|
<param name="id" value="interceptor" />
|
||||||
|
<param name="file" value="examples/src/main/java/example/RequestExceptionInterceptor.java" />
|
||||||
|
</macro>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</document>
|
</document>
|
||||||
|
|
|
@ -28,21 +28,21 @@
|
||||||
</p>
|
</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
The <code>hapi-fhir-base-[version].jar</code> file containing the core library.
|
<i>Required:</i> The <code>hapi-fhir-base-[version].jar</code> file containing the core library.
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
The <code>hapi-fhir-structures-dstu-[version].jar</code> file containing the FHIR model classes
|
<i>Required:</i> The <code>hapi-fhir-structures-dstu-[version].jar</code> file containing the FHIR model classes
|
||||||
for DSTU1 (all contents of this JAR were previously found in hapi-fhir-base).
|
for DSTU1 (all contents of this JAR were previously found in hapi-fhir-base).
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<i>Optionally: </i>You may also choose to include the
|
<i>Optional:</i> You may also choose to include the
|
||||||
<code>hapi-fhir-structures-dev-[version].jar</code>. This JAR contains structures for the
|
<code>hapi-fhir-structures-dev-[version].jar</code>. This JAR contains structures for the
|
||||||
latest DEV version of FHIR. You may create a client/server which supports either DSTU1 or DEV
|
latest DEV version of FHIR. You may create a client/server which supports either DSTU1 or DEV
|
||||||
resources, or both depending on your needs. Note that using DEV resources may introduce
|
resources, or both depending on your needs. Note that using DEV resources may introduce
|
||||||
incompatibilities with other frameworks however.<br/><br/>
|
incompatibilities with other frameworks however. If you are including this JAR,
|
||||||
<b>You must also include hapi-fhir-structures-dstu-[version].jar</b> if you include
|
<b>you must also include hapi-fhir-structures-dstu-[version].jar</b>.
|
||||||
the dev structures JAR at this time. Hopefully at some point soon this requirement
|
Hopefully by the time 0.8 is final this requirement will be relaxed, but for now it
|
||||||
will be relaxed.
|
is mandatory.
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue