Implement handleException on the server interceptor framework, as well

as some site and documentation enhancements
This commit is contained in:
James Agnew 2014-11-07 14:26:59 -05:00
parent 8f704030ed
commit d22a35788f
26 changed files with 528 additions and 179 deletions

View File

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

View File

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

View File

@ -3,6 +3,9 @@ package example;
import java.io.IOException;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.ExtensionDt;
import ca.uhn.fhir.model.dstu.composite.HumanNameDt;
@ -11,12 +14,13 @@ import ca.uhn.fhir.model.dstu.valueset.IdentifierUseEnum;
import ca.uhn.fhir.model.primitive.DateTimeDt;
import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.rest.server.interceptor.InterceptorAdapter;
public class ServerInterceptors {
@SuppressWarnings("unused")
public static void main(String[] args) throws DataFormatException, IOException {
// START SNIPPET: resourceExtension
// Create an example patient

View File

@ -19,9 +19,11 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>2.7</version>
<inherited>false</inherited>
<reportSets>
<reportSet>
<reports>
<report>scm</report>
</reports>
</reportSet>
</reportSets>
@ -39,6 +41,7 @@
<configuration>
<links>
<link>http://docs.oracle.com/javaee/7/api</link>
<link>http://jamesagnew.github.io/hapi-fhir/apidocs</link>
</links>
</configuration>
</reportSet>

View File

@ -15,13 +15,6 @@
<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>
<!-- JSON -->
@ -214,6 +207,8 @@
<groupId>org.codehaus.mojo</groupId>
<artifactId>findbugs-maven-plugin</artifactId>
<version>3.0.0</version>
<configuration>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>

View File

@ -207,10 +207,11 @@ public class ParameterUtil {
case ',':
case '|':
b.append('\\');
// fall through
break;
default:
b.append(next);
break;
}
b.append(next);
}
return b.toString();

View File

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

View File

@ -92,13 +92,12 @@ public class RestfulServer extends HttpServlet {
private AddProfileTagEnum myAddProfileTag;
private FhirContext myFhirContext;
private String myImplementationDescription;
private List<IServerInterceptor> myInterceptors = new ArrayList<IServerInterceptor>();
private final List<IServerInterceptor> myInterceptors = new ArrayList<IServerInterceptor>();
private ResourceBinding myNullResourceBinding = new ResourceBinding();
private IPagingProvider myPagingProvider;
private Collection<Object> myPlainProviders;
private Map<String, ResourceBinding> myResourceNameToProvider = new HashMap<String, ResourceBinding>();
private Collection<IResourceProvider> myResourceProviders;
private ISecurityManager mySecurityManager;
private IServerAddressStrategy myServerAddressStrategy = new IncomingRequestAddressStrategy();
private BaseMethodBinding<?> myServerConformanceMethod;
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.
* <p>
* Use caution if overriding this method: it is recommended to call <code>super.addHeadersToResponse</code> to avoid
* inadvertantly disabling functionality.
* Use caution if overriding this method: it is recommended to call <code>super.addHeadersToResponse</code> to avoid inadvertantly disabling functionality.
* </p>
*/
public void addHeadersToResponse(HttpServletResponse theHttpResponse) {
@ -236,14 +234,13 @@ public class RestfulServer extends HttpServlet {
Package pack = annotation.annotationType().getPackage();
if (pack.equals(IdParam.class.getPackage())) {
if (!allowableParams.contains(annotation.annotationType())) {
throw new ConfigurationException("Method[" + m.toString() + "] is not allowed to have a parameter annotated with "+ annotation);
throw new ConfigurationException("Method[" + m.toString() + "] is not allowed to have a parameter annotated with " + annotation);
}
}
}
}
}
resourceBinding.addMethod(foundMethodBinding);
ourLog.debug(" * Method: {}#{} is a handler", theProvider.getClass(), m.getName());
}
@ -293,8 +290,8 @@ public class RestfulServer extends HttpServlet {
}
/**
* Gets the {@link FhirContext} associated with this server. For efficient processing, resource providers and plain
* providers should generally use this context if one is needed, as opposed to creating their own.
* Gets the {@link FhirContext} associated with this server. For efficient processing, resource providers and plain providers should generally use this context if one is needed, as opposed to
* creating their own.
*/
public FhirContext getFhirContext() {
return myFhirContext;
@ -336,26 +333,16 @@ public class RestfulServer extends HttpServlet {
}
/**
* Provides the security manager, or <code>null</code> if none
*/
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}
* Get the server address strategy, which is used to determine what base URL to provide clients to refer to this server. Defaults to an instance of {@link IncomingRequestAddressStrategy}
*/
public IServerAddressStrategy getServerAddressStrategy() {
return myServerAddressStrategy;
}
/**
* Returns the server conformance provider, which is the provider that is used to generate the server's conformance
* (metadata) statement.
* Returns the server conformance provider, which is the provider that is used to generate the server's conformance (metadata) statement.
* <p>
* By default, the {@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.
* 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.
* </p>
*/
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,
* but can be helpful to set with something appropriate.
* Gets the server's name, as exported in conformance profiles exported by the server. This is informational only, but can be helpful to set with something appropriate.
*
* @see RestfulServer#setServerName(String)
*/
@ -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
* only, but can be helpful to set with something appropriate.
* Gets the server's version, as exported in conformance profiles exported by the server. This is informational only, but can be helpful to set with something appropriate.
*/
public String getServerVersion() {
return myServerVersion;
@ -417,12 +402,14 @@ public class RestfulServer extends HttpServlet {
NarrativeModeEnum narrativeMode = determineNarrativeMode(theRequest);
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--) {
IServerInterceptor next = getInterceptors().get(i);
boolean continueProcessing = next.outgoingResponse(theRequest, bundle, theRequest.getServletRequest(), theRequest.getServletResponse());
if (!continueProcessing) {
ourLog.debug("Interceptor {} returned false, not continuing processing");
return;
}
}
@ -435,31 +422,29 @@ public class RestfulServer extends HttpServlet {
for (IServerInterceptor next : myInterceptors) {
boolean continueProcessing = next.incomingRequestPreProcessed(theRequest, theResponse);
if (!continueProcessing) {
ourLog.debug("Interceptor {} returned false, not continuing processing");
return;
}
}
String fhirServerBase = null;
boolean requestIsBrowser = requestIsBrowser(theRequest);
RequestDetails requestDetails=null;
try {
if (null != mySecurityManager) {
mySecurityManager.authenticate(theRequest);
}
String resourceName = null;
String requestFullPath = StringUtils.defaultString(theRequest.getRequestURI());
String servletPath = StringUtils.defaultString(theRequest.getServletPath());
StringBuffer requestUrl = theRequest.getRequestURL();
String servletContextPath = "";
// if (getServletContext().getMajorVersion() >= 3) {
// // getServletContext is only supported in version 3+ of servlet-api
if (getServletContext() != null) {
servletContextPath = StringUtils.defaultString(getServletContext().getContextPath());
}
// }
// if (getServletContext().getMajorVersion() >= 3) {
// // getServletContext is only supported in version 3+ of servlet-api
if (getServletContext() != null) {
servletContextPath = StringUtils.defaultString(getServletContext().getContextPath());
}
// }
if (ourLog.isTraceEnabled()) {
ourLog.trace("Request FullPath: {}", requestFullPath);
ourLog.trace("Servlet Path: {}", servletPath);
@ -609,7 +594,7 @@ public class RestfulServer extends HttpServlet {
throw new InvalidRequestException(b.toString());
}
RequestDetails requestDetails = r;
requestDetails = r;
requestDetails.setResourceOperationType(resourceMethod.getResourceOperationType());
requestDetails.setSystemOperationType(resourceMethod.getSystemOperationType());
requestDetails.setOtherOperationType(resourceMethod.getOtherOperationType());
@ -617,6 +602,7 @@ public class RestfulServer extends HttpServlet {
for (IServerInterceptor next : myInterceptors) {
boolean continueProcessing = next.incomingRequestPostProcessed(requestDetails, theRequest, theResponse);
if (!continueProcessing) {
ourLog.debug("Interceptor {} returned false, not continuing processing");
return;
}
}
@ -624,6 +610,15 @@ public class RestfulServer extends HttpServlet {
resourceMethod.invokeServer(this, r);
} 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 request is coming from a browser, prompt the user to enter login credentials
theResponse.setHeader("WWW-Authenticate", "BASIC realm=\"FHIR\"");
@ -636,14 +631,30 @@ public class RestfulServer extends HttpServlet {
} 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;
int statusCode = 500;
int statusCode = Constants.STATUS_HTTP_500_INTERNAL_ERROR;
if (e instanceof BaseServerResponseException) {
oo = ((BaseServerResponseException) e).getOperationOutcome();
statusCode = ((BaseServerResponseException) e).getStatusCode();
}
/*
* Generate an OperationOutcome to return, unless the exception throw by
* the resource provider had one
*/
if (oo == null) {
try {
oo = (BaseOperationOutcome) myFhirContext.getResourceDefinition("OperationOutcome").getImplementingClass().newInstance();
@ -651,19 +662,29 @@ public class RestfulServer extends HttpServlet {
ourLog.error("Failed to instantiate OperationOutcome resource instance", e1);
throw new ServletException("Failed to instantiate OperationOutcome resource instance", e1);
}
BaseIssue issue = oo.addIssue();
issue.getSeverityElement().setValue("error");
if (e instanceof InternalErrorException) {
ourLog.error("Failure during REST processing", e);
issue.getDetailsElement().setValue(e.toString() + "\n\n" + ExceptionUtils.getStackTrace(e));
statusCode = ((InternalErrorException) e).getStatusCode();
} else if (e instanceof BaseServerResponseException) {
ourLog.warn("Failure during REST processing: {}", e.toString());
statusCode = ((BaseServerResponseException) e).getStatusCode();
BaseServerResponseException baseServerResponseException = (BaseServerResponseException) e;
statusCode = baseServerResponseException.getStatusCode();
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 {
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));
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,
* but subclasses may put initialization code in {@link #initialize()}, which is called immediately before beginning
* initialization of the restful server's internal init.
* Initializes the server. Note that this method is final to avoid accidentally introducing bugs in implementations, but subclasses may put initialization code in {@link #initialize()}, which is
* called immediately before beginning initialization of the restful server's internal init.
*/
@Override
public final void init() throws ServletException {
@ -691,11 +711,6 @@ public class RestfulServer extends HttpServlet {
try {
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.scanForProvidedResources(this);
@ -735,14 +750,13 @@ public class RestfulServer extends HttpServlet {
ourLog.error("An error occurred while loading request handlers!", ex);
throw new ServletException("Failed to initialize FHIR Restful server", ex);
}
myStarted = true;
ourLog.info("A FHIR has been lit on this server");
}
/**
* This method may be overridden by subclasses to do perform initialization that needs to be performed prior to the
* server being used.
* This method may be overridden by subclasses to do perform initialization that needs to be performed prior to the server being used.
*/
protected void initialize() throws ServletException {
// nothing by default
@ -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}
* (which is the default), the server will automatically add a profile tag based on the class of the resource(s)
* being returned.
* Sets the profile tagging behaviour for the server. When set to a value other than {@link AddProfileTagEnum#NEVER} (which is the default), the server will automatically add a profile tag based
* on the class of the resource(s) being returned.
*
* @param theAddProfileTag
* The behaviour enum (must not be null)
@ -784,7 +797,6 @@ public class RestfulServer extends HttpServlet {
myImplementationDescription = theImplementationDescription;
}
/**
* 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
*/
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}
* Provide a server address strategy, which is used to determine what base URL to provide clients to refer to this server. Defaults to an instance of {@link IncomingRequestAddressStrategy}
*/
public void setServerAddressStrategy(IServerAddressStrategy theServerAddressStrategy) {
Validate.notNull(theServerAddressStrategy, "Server address strategy can not be null");
@ -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
* (metadata) statement.
* Returns the server conformance provider, which is the provider that is used to generate the server's conformance (metadata) statement.
* <p>
* By default, the {@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.
* 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.
* </p>
* Note that this method can only be called before the server is initialized.
*
* @throws IllegalStateException
* Note that this method can only be called prior to {@link #init() initialization} and will throw an
* {@link IllegalStateException} if called after that.
* Note that this method can only be called prior to {@link #init() initialization} and will throw an {@link IllegalStateException} if called after that.
*/
public void setServerConformanceProvider(Object theServerConformanceProvider) {
if (myStarted) {
@ -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,
* but can be helpful to set with something appropriate.
* Sets the server's name, as exported in conformance profiles exported by the server. This is informational only, but can be helpful to set with something appropriate.
*/
public void setServerName(String theServerName) {
myServerName = theServerName;
}
/**
* Gets the server's version, as exported in conformance profiles exported by the server. This is informational
* only, but can be helpful to set with something appropriate.
* Gets the server's version, as exported in conformance profiles exported by the server. This is informational only, but can be helpful to set with something appropriate.
*/
public void setServerVersion(String theServerVersion) {
myServerVersion = theServerVersion;
}
/**
* If set to <code>true</code> (default is false), the server will use browser friendly content-types (instead of
* standard FHIR ones) when it detects that the request is coming from a browser instead of a FHIR
* If set to <code>true</code> (default is false), the server will use browser friendly content-types (instead of standard FHIR ones) when it detects that the request is coming from a browser
* instead of a FHIR
*/
public void setUseBrowserFriendlyContentTypes(boolean theUseBrowserFriendlyContentTypes) {
myUseBrowserFriendlyContentTypes = theUseBrowserFriendlyContentTypes;
@ -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,
NarrativeModeEnum theNarrativeMode, int theOffset, Integer theLimit, String theSearchId) {
public static Bundle createBundleFromBundleProvider(RestfulServer theServer, HttpServletResponse theHttpResponse, IBundleProvider theResult, EncodingEnum theResponseEncoding,
String theServerBase, String theCompleteUrl, boolean thePrettyPrint, boolean theRequestIsBrowser, NarrativeModeEnum theNarrativeMode, int theOffset, Integer theLimit, String theSearchId) {
theHttpResponse.setStatus(200);
if (theRequestIsBrowser && theServer.isUseBrowserFriendlyContentTypes()) {
@ -962,7 +961,7 @@ public class RestfulServer extends HttpServlet {
numToReturn = theResult.size();
resourceList = theResult.getResources(0, numToReturn);
validateResourceListNotNull(resourceList);
} else {
IPagingProvider pagingProvider = theServer.getPagingProvider();
if (theLimit == null) {
@ -1045,11 +1044,11 @@ public class RestfulServer extends HttpServlet {
Set<String> containedIds = new HashSet<String>();
for (IResource nextContained : next.getContained().getContainedResources()) {
if (nextContained.getId().isEmpty()==false) {
if (nextContained.getId().isEmpty() == false) {
containedIds.add(nextContained.getId().getValue());
}
}
if (theContext.getNarrativeGenerator() != null) {
String title = theContext.getNarrativeGenerator().generateTitle(next);
ourLog.trace("Narrative generator created title: {}", title);
@ -1072,7 +1071,7 @@ public class RestfulServer extends HttpServlet {
// Don't add contained IDs as top level resources
continue;
}
IdDt id = nextRes.getId().toVersionless();
if (id.hasResourceType() == false) {
String resName = theContext.getResourceDefinition(nextRes).getName();
@ -1188,9 +1187,7 @@ public class RestfulServer extends HttpServlet {
}
/**
* 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.
* Determine whether a response should be given in JSON or XML format based on the incoming HttpServletRequest's <code>"_format"</code> parameter and <code>"Accept:"</code> HTTP header.
*/
public static EncodingEnum determineResponseEncoding(HttpServletRequest theReq) {
String[] format = theReq.getParameterValues(Constants.PARAM_FORMAT);
@ -1283,8 +1280,8 @@ public class RestfulServer extends HttpServlet {
return prettyPrint;
}
public static void streamResponseAsBundle(RestfulServer theServer, HttpServletResponse theHttpResponse, Bundle bundle, EncodingEnum theResponseEncoding, String theServerBase, boolean thePrettyPrint, NarrativeModeEnum theNarrativeMode, boolean theRespondGzip)
throws IOException {
public static void streamResponseAsBundle(RestfulServer theServer, HttpServletResponse theHttpResponse, Bundle bundle, EncodingEnum theResponseEncoding, String theServerBase,
boolean thePrettyPrint, NarrativeModeEnum theNarrativeMode, boolean theRespondGzip) throws IOException {
assert !theServerBase.endsWith("/");
Writer writer = getWriter(theHttpResponse, theRespondGzip);
@ -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,
String theServerBase) throws IOException {
public static void streamResponseAsResource(RestfulServer theServer, HttpServletResponse theHttpResponse, IResource theResource, EncodingEnum theResponseEncoding, boolean thePrettyPrint,
boolean theRequestIsBrowser, NarrativeModeEnum theNarrativeMode, boolean theRespondGzip, String theServerBase) throws IOException {
int stausCode = 200;
streamResponseAsResource(theServer, theHttpResponse, theResource, theResponseEncoding, thePrettyPrint, theRequestIsBrowser, theNarrativeMode, stausCode, theRespondGzip, theServerBase);
}
private static void streamResponseAsResource(RestfulServer theServer, HttpServletResponse theHttpResponse, IResource theResource, EncodingEnum theResponseEncoding, boolean thePrettyPrint, boolean theRequestIsBrowser, NarrativeModeEnum theNarrativeMode, int stausCode,
boolean theRespondGzip, String theServerBase) throws IOException {
private static void streamResponseAsResource(RestfulServer theServer, HttpServletResponse theHttpResponse, IResource theResource, EncodingEnum theResponseEncoding, boolean thePrettyPrint,
boolean theRequestIsBrowser, NarrativeModeEnum theNarrativeMode, int stausCode, boolean theRespondGzip, String theServerBase) throws IOException {
theHttpResponse.setStatus(stausCode);
if (theResource.getId() != null && theResource.getId().hasIdPart() && isNotBlank(theServerBase)) {

View File

@ -1,7 +1,9 @@
package ca.uhn.fhir.rest.server.exceptions;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
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
* subclasses of this exception type, and RESTful server methods should also only call subclasses of this exception
* type.
* 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.
*/
public abstract class BaseServerResponseException extends RuntimeException {
@ -49,11 +50,12 @@ public abstract class BaseServerResponseException extends RuntimeException {
registerExceptionType(NotImplementedOperationException.STATUS_CODE, NotImplementedOperationException.class);
}
private List<String> myAdditionalMessages = null;
private BaseOperationOutcome myBaseOperationOutcome;
private String myResponseBody;
private String myResponseMimeType;
private int myStatusCode;
/**
* Constructor
*
@ -67,7 +69,24 @@ public abstract class BaseServerResponseException extends RuntimeException {
myStatusCode = theStatusCode;
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
*
@ -76,8 +95,7 @@ public abstract class BaseServerResponseException extends RuntimeException {
* @param theMessage
* The message
* @param theBaseOperationOutcome
* An BaseOperationOutcome resource to return to the calling client (in a server) or the BaseOperationOutcome
* that was returned from the server (in a client)
* An BaseOperationOutcome resource to return to the calling client (in a server) or the BaseOperationOutcome that was returned from the server (in a client)
*/
public BaseServerResponseException(int theStatusCode, String theMessage, BaseOperationOutcome theBaseOperationOutcome) {
super(theMessage);
@ -111,8 +129,7 @@ public abstract class BaseServerResponseException extends RuntimeException {
* @param theCause
* The underlying cause exception
* @param theBaseOperationOutcome
* An BaseOperationOutcome resource to return to the calling client (in a server) or the BaseOperationOutcome
* that was returned from the server (in a client)
* An BaseOperationOutcome resource to return to the calling client (in a server) or the BaseOperationOutcome that was returned from the server (in a client)
*/
public BaseServerResponseException(int theStatusCode, String theMessage, Throwable theCause, BaseOperationOutcome theBaseOperationOutcome) {
super(theMessage, theCause);
@ -142,8 +159,7 @@ public abstract class BaseServerResponseException extends RuntimeException {
* @param theCause
* The underlying cause exception
* @param theBaseOperationOutcome
* An BaseOperationOutcome resource to return to the calling client (in a server) or the BaseOperationOutcome
* that was returned from the server (in a client)
* An BaseOperationOutcome resource to return to the calling client (in a server) or the BaseOperationOutcome that was returned from the server (in a client)
*/
public BaseServerResponseException(int theStatusCode, Throwable theCause, BaseOperationOutcome theBaseOperationOutcome) {
super(theCause.toString(), theCause);
@ -151,6 +167,10 @@ public abstract class BaseServerResponseException extends RuntimeException {
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>
*/
@ -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
* server, or <code>null</code> otherwise.
* 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.
* <p>
* In a restful server, this method is currently ignored.
* </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
* response.
* In a RESTful client, this method will be populated with the HTTP status code that was returned with the HTTP response.
* <p>
* In a restful server, this method is currently ignored.
* </p>
@ -188,11 +206,11 @@ public abstract class BaseServerResponseException extends RuntimeException {
}
/**
* 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 you should not call this method.
* 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 you should not call this method.
*
* @param theBaseOperationOutcome The BaseOperationOutcome resource
* @param theBaseOperationOutcome
* The BaseOperationOutcome resource
*/
public void setOperationOutcome(BaseOperationOutcome theBaseOperationOutcome) {
myBaseOperationOutcome = theBaseOperationOutcome;

View File

@ -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.
*/
public UnprocessableEntityException(String... theMessage) {
super(STATUS_CODE, theMessage[0]); // TODO: this used to generate an OperationOutcome - why?
super(STATUS_CODE, theMessage);
}
}

View File

@ -64,7 +64,8 @@ public interface IServerInterceptor {
* </p>
*
* @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
* The incoming request
* @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
*
* @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
* The actual object which is being streamed to the client as a response
* @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
*
* @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
* The actual object which is being streamed to the client as a response
* @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
*
* @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
* The actual object which is being streamed to the client as a response
* @param theRequest
@ -171,7 +175,9 @@ public interface IServerInterceptor {
* </p>
*
* @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
* The actual object which is being streamed to the client as a response
* @param theRequest

View File

@ -357,6 +357,8 @@ public class XmlUtil {
case '"':
case '&':
hasEscapable = true;
default:
break;
}
}

View File

@ -33,6 +33,7 @@ import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.PortUtil;
/**
@ -54,6 +55,21 @@ public class ExceptionTest {
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
public void testInternalError() throws Exception {
{
@ -162,10 +178,15 @@ public class ExceptionTest {
@Search
public List<Patient> findPatient(@RequiredParam(name = "throwInternalError") StringParam theParam) {
public List<Patient> throwInternalError(@RequiredParam(name = "throwInternalError") StringParam theParam) {
throw new InternalErrorException("Exception Text");
}
@Search
public List<Patient> throwUnprocessableEntityWithMultipleMessages(@RequiredParam(name = "throwUnprocessableEntityWithMultipleMessages") StringParam theParam) {
throw new UnprocessableEntityException("message1", "message2", "message3");
}
@Override
public Class<? extends IResource> getResourceType() {
return Patient.class;

View File

@ -1378,11 +1378,6 @@ public class ResfulServerMethodTest {
return myResourceProviders;
}
@Override
public ISecurityManager getSecurityManager() {
return null;
}
}
}

View File

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

View File

@ -221,7 +221,7 @@ public class TinderStructuresMojo extends AbstractMojo {
String dtOutputDir = "target/generated-sources/tinder/ca/uhn/fhir/model/dev/composite";
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"));
rp.parse();
rp.bindValueSets(vsp);

1
list_releases.sh Normal file
View File

@ -0,0 +1 @@
curl https://api.github.com/repos/jamesagnew/hapi-fhir/releases

28
pom.xml
View File

@ -28,6 +28,13 @@
<url>https://github.com/jamesagnew/hapi-fhir/issues/</url>
</issueManagement>
<distributionManagement>
<site>
<id>git.server</id>
<url>scm:git:git@github.com:jamesagnew/hapi-fhir.git</url>
</site>
</distributionManagement>
<scm>
<connection>scm:git:git@github.com:jamesagnew/hapi-fhir.git</connection>
<url>scm:git:git@github.com:jamesagnew/hapi-fhir.git</url>
@ -502,12 +509,33 @@
</reportSet>
</reportSets>
</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>
<version>1.1</version> </plugin> -->
</plugins>
</reporting>
<profiles>
<profile>
<id>ROOT</id>
<modules>
</modules>
</profile>
<profile>
<id>SIGN_ARTIFACTS</id>
<activation>

View File

@ -7,11 +7,19 @@
</properties>
<body>
<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
incorrectly used. Thanks to Bill de Beaubien for the pull request!
</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
defined extension which was of a composite type. Thanks to Bill de Beaubien for the pull request!
</action>
@ -19,7 +27,7 @@
Remove unnessecary IOException from narrative generator API. Thanks to
Petro Mykhailysyn for the pull request!
</action>
<action type="add" issue="48">
<action type="add" issue="48" dev="wdebeau1">
Introduced a new
<![CDATA[<code>@ProvidesResources</code>]]> annotation which can be added to
resource provider and servers to allow them to declare additional resource
@ -27,6 +35,16 @@
serve up multiple classes for the same resource type (e.g. a server that sometimes
returns a default Patient, but sometimes uses a custom subclass).
Thanks to Bill de Beaubien for the pull request!
</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 version="0.7" date="2014-Oct-23">

View File

@ -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&#xa;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&#xa;Provider&#xa;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

View File

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

View File

@ -69,6 +69,32 @@
of the operation being invoked (resource, bundle, etc.)
</li>
</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"/>
<subsection name="Registering Interceptors">
@ -129,6 +155,29 @@
</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>
</document>

View File

@ -28,21 +28,21 @@
</p>
<ul>
<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>
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).
</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
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
incompatibilities with other frameworks however.<br/><br/>
<b>You must also include hapi-fhir-structures-dstu-[version].jar</b> if you include
the dev structures JAR at this time. Hopefully at some point soon this requirement
will be relaxed.
incompatibilities with other frameworks however. If you are including this JAR,
<b>you must also include hapi-fhir-structures-dstu-[version].jar</b>.
Hopefully by the time 0.8 is final this requirement will be relaxed, but for now it
is mandatory.
</li>
</ul>