Documentaiton on idempotence
This commit is contained in:
parent
cc7e8c424c
commit
4426080374
|
@ -16,7 +16,7 @@ import ca.uhn.fhir.rest.annotation.OperationParam;
|
|||
public class ServerOperations {
|
||||
|
||||
//START SNIPPET: patientTypeOperation
|
||||
@Operation(name="$everything")
|
||||
@Operation(name="$everything", idempotent=true)
|
||||
public Bundle patientTypeOperation(
|
||||
@OperationParam(name="start") DateDt theStart,
|
||||
@OperationParam(name="end") DateDt theEnd) {
|
||||
|
@ -28,7 +28,7 @@ public class ServerOperations {
|
|||
//END SNIPPET: patientTypeOperation
|
||||
|
||||
//START SNIPPET: patientInstanceOperation
|
||||
@Operation(name="$everything")
|
||||
@Operation(name="$everything", idempotent=true)
|
||||
public Bundle patientInstanceOperation(
|
||||
@IdParam IdDt thePatientId,
|
||||
@OperationParam(name="start") DateDt theStart,
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,172 @@
|
|||
package ca.uhn.fhir.rest.server.interceptor;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR - Core Library
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2015 University Health Network
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.model.base.resource.BaseOperationOutcome;
|
||||
import ca.uhn.fhir.model.base.resource.BaseOperationOutcome.BaseIssue;
|
||||
import ca.uhn.fhir.rest.method.Request;
|
||||
import ca.uhn.fhir.rest.method.RequestDetails;
|
||||
import ca.uhn.fhir.rest.server.Constants;
|
||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||
import ca.uhn.fhir.rest.server.RestfulServer.NarrativeModeEnum;
|
||||
import ca.uhn.fhir.rest.server.RestfulServerUtils;
|
||||
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
|
||||
public class ExceptionHandlingInterceptor extends InterceptorAdapter {
|
||||
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExceptionHandlingInterceptor.class);
|
||||
private Class<?>[] myReturnStackTracesForExceptionTypes;
|
||||
|
||||
/**
|
||||
* If any server methods throw an exception which extends any of the given exception types, the exception
|
||||
* stack trace will be returned to the user. This can be useful for helping to diagnose issues, but may
|
||||
* not be desirable for production situations.
|
||||
*
|
||||
* @param theExceptionTypes The exception types for which to return the stack trace to the user.
|
||||
* @return Returns an instance of this interceptor, to allow for easy method chaining.
|
||||
*/
|
||||
public ExceptionHandlingInterceptor setReturnStackTracesForExceptionTypes(Class<?>... theExceptionTypes) {
|
||||
myReturnStackTracesForExceptionTypes = theExceptionTypes;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
<<<<<<< HEAD
|
||||
public boolean handleException(RequestDetails theRequestDetails, Throwable theException, HttpServletRequest theRequest, HttpServletResponse theResponse) throws ServletException, IOException {
|
||||
=======
|
||||
public boolean handleException(RestfulServer theRestfulServer, RequestDetails theRequestDetails, Throwable theException, HttpServletRequest theRequest, HttpServletResponse theResponse) throws ServletException, IOException {
|
||||
ourLog.error("AA", theException);
|
||||
>>>>>>> 5956ab75fd9186ccc8b9836b60bc421b19d3d288
|
||||
BaseOperationOutcome oo = null;
|
||||
int statusCode = Constants.STATUS_HTTP_500_INTERNAL_ERROR;
|
||||
|
||||
FhirContext ctx = theRestfulServer.getFhirContext();
|
||||
|
||||
if (theException instanceof BaseServerResponseException) {
|
||||
oo = ((BaseServerResponseException) theException).getOperationOutcome();
|
||||
statusCode = ((BaseServerResponseException) theException).getStatusCode();
|
||||
}
|
||||
|
||||
/*
|
||||
* Generate an OperationOutcome to return, unless the exception throw by the resource provider had one
|
||||
*/
|
||||
if (oo == null) {
|
||||
try {
|
||||
oo = (BaseOperationOutcome) ctx.getResourceDefinition("OperationOutcome").getImplementingClass().newInstance();
|
||||
} catch (Exception e1) {
|
||||
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 (theException instanceof InternalErrorException) {
|
||||
ourLog.error("Failure during REST processing", theException);
|
||||
populateDetails(theException, issue);
|
||||
} else if (theException instanceof BaseServerResponseException) {
|
||||
ourLog.warn("Failure during REST processing: {}", theException);
|
||||
BaseServerResponseException baseServerResponseException = (BaseServerResponseException) theException;
|
||||
statusCode = baseServerResponseException.getStatusCode();
|
||||
populateDetails(theException, issue);
|
||||
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: " + theException.toString(), theException);
|
||||
populateDetails(theException, issue);
|
||||
statusCode = Constants.STATUS_HTTP_500_INTERNAL_ERROR;
|
||||
}
|
||||
} else {
|
||||
ourLog.error("Unknown error during processing", theException);
|
||||
}
|
||||
|
||||
// Add headers associated with the specific error code
|
||||
if (theException instanceof BaseServerResponseException) {
|
||||
Map<String, String[]> additional = ((BaseServerResponseException) theException).getAssociatedHeaders();
|
||||
if (additional != null) {
|
||||
for (Entry<String, String[]> next : additional.entrySet()) {
|
||||
if (isNotBlank(next.getKey()) && next.getValue() != null) {
|
||||
String nextKey = next.getKey();
|
||||
for (String nextValue : next.getValue()) {
|
||||
theResponse.addHeader(nextKey, nextValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean requestIsBrowser = RestfulServer.requestIsBrowser(theRequest);
|
||||
String fhirServerBase = theRestfulServer.getServerBaseForRequest(theRequest);
|
||||
|
||||
RestfulServerUtils.streamResponseAsResource(theRestfulServer, theResponse, oo, RestfulServerUtils.determineResponseEncodingNoDefault(theRequest), true, requestIsBrowser,
|
||||
NarrativeModeEnum.NORMAL, statusCode, false, fhirServerBase);
|
||||
|
||||
<<<<<<< HEAD
|
||||
// theResponse.setStatus(statusCode);
|
||||
// theRequestDetails.getServer().addHeadersToResponse(theResponse);
|
||||
// theResponse.setContentType("text/plain");
|
||||
// theResponse.setCharacterEncoding("UTF-8");
|
||||
// theResponse.getWriter().append(theException.getMessage());
|
||||
// theResponse.getWriter().close();
|
||||
=======
|
||||
theResponse.setStatus(statusCode);
|
||||
theRestfulServer.addHeadersToResponse(theResponse);
|
||||
theResponse.setContentType("text/plain");
|
||||
theResponse.setCharacterEncoding("UTF-8");
|
||||
theResponse.getWriter().append(theException.getMessage());
|
||||
theResponse.getWriter().close();
|
||||
>>>>>>> 5956ab75fd9186ccc8b9836b60bc421b19d3d288
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void populateDetails(Throwable theException, BaseIssue issue) {
|
||||
if (myReturnStackTracesForExceptionTypes != null) {
|
||||
for (Class<?> next : myReturnStackTracesForExceptionTypes) {
|
||||
if (next.isAssignableFrom(theException.getClass())) {
|
||||
issue.getDetailsElement().setValue(theException.getMessage() + "\n\n" + ExceptionUtils.getStackTrace(theException));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
issue.getDetailsElement().setValue(theException.getMessage());
|
||||
}
|
||||
|
||||
}
|
|
@ -26,7 +26,6 @@ import javax.servlet.ServletException;
|
|||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||
import org.hl7.fhir.instance.model.IBaseResource;
|
||||
|
||||
import ca.uhn.fhir.model.api.Bundle;
|
||||
|
@ -58,7 +57,7 @@ public interface IServerInterceptor {
|
|||
* The incoming request
|
||||
* @param theResponse
|
||||
* The response. Note that interceptors may choose to provide a response (i.e. by calling {@link HttpServletResponse#getWriter()}) but in that case it is important to return
|
||||
* <code>false</code>
|
||||
* <code>false</code> to indicate that the server itself should not also provide a response.
|
||||
* @return Return <code>true</code> if processing should continue normally. This is generally the right thing to do. If your interceptor is providing a response rather than letting HAPI handle the
|
||||
* response normally, you must return <code>false</code>. In this case, no further processing will occur and no further interceptors will be called.
|
||||
*/
|
||||
|
@ -77,7 +76,7 @@ public interface IServerInterceptor {
|
|||
* The incoming request
|
||||
* @param theResponse
|
||||
* The response. Note that interceptors may choose to provide a response (i.e. by calling {@link HttpServletResponse#getWriter()}) but in that case it is important to return
|
||||
* <code>false</code>
|
||||
* <code>false</code> to indicate that the server itself should not also provide a response.
|
||||
* @return Return <code>true</code> if processing should continue normally. This is generally the right thing to do. If your interceptor is providing a response rather than letting HAPI handle the
|
||||
* response normally, you must return <code>false</code>. In this case, no further processing will occur and no further interceptors will be called.
|
||||
* @throws AuthenticationException
|
||||
|
@ -98,7 +97,7 @@ public interface IServerInterceptor {
|
|||
* The incoming request
|
||||
* @param theServletResponse
|
||||
* The response. Note that interceptors may choose to provide a response (i.e. by calling {@link HttpServletResponse#getWriter()}) but in that case it is important to return
|
||||
* <code>false</code>
|
||||
* <code>false</code> to indicate that the server itself should not also provide a response.
|
||||
* @return Return <code>true</code> if processing should continue normally. This is generally the right thing to do. If your interceptor is providing a response rather than letting HAPI handle the
|
||||
* response normally, you must return <code>false</code>. In this case, no further processing will occur and no further interceptors will be called.
|
||||
* @throws AuthenticationException
|
||||
|
@ -119,7 +118,7 @@ public interface IServerInterceptor {
|
|||
* The incoming request
|
||||
* @param theServletResponse
|
||||
* The response. Note that interceptors may choose to provide a response (i.e. by calling {@link HttpServletResponse#getWriter()}) but in that case it is important to return
|
||||
* <code>false</code>
|
||||
* <code>false</code> to indicate that the server itself should not also provide a response.
|
||||
* @return Return <code>true</code> if processing should continue normally. This is generally the right thing to do. If your interceptor is providing a response rather than letting HAPI handle the
|
||||
* response normally, you must return <code>false</code>. In this case, no further processing will occur and no further interceptors will be called.
|
||||
* @throws AuthenticationException
|
||||
|
@ -141,7 +140,7 @@ public interface IServerInterceptor {
|
|||
* The incoming request
|
||||
* @param theServletResponse
|
||||
* The response. Note that interceptors may choose to provide a response (i.e. by calling {@link HttpServletResponse#getWriter()}) but in that case it is important to return
|
||||
* <code>false</code>
|
||||
* <code>false</code> to indicate that the server itself should not also provide a response.
|
||||
* @return Return <code>true</code> if processing should continue normally. This is generally the right thing to do. If your interceptor is providing a response rather than letting HAPI handle the
|
||||
* response normally, you must return <code>false</code>. In this case, no further processing will occur and no further interceptors will be called.
|
||||
* @throws AuthenticationException
|
||||
|
@ -161,7 +160,7 @@ public interface IServerInterceptor {
|
|||
* The incoming request
|
||||
* @param theServletResponse
|
||||
* The response. Note that interceptors may choose to provide a response (i.e. by calling {@link HttpServletResponse#getWriter()}) but in that case it is important to return
|
||||
* <code>false</code>
|
||||
* <code>false</code> to indicate that the server itself should not also provide a response.
|
||||
* @return Return <code>true</code> if processing should continue normally. This is generally the right thing to do. If your interceptor is providing a response rather than letting HAPI handle the
|
||||
* response normally, you must return <code>false</code>. In this case, no further processing will occur and no further interceptors will be called.
|
||||
* @throws AuthenticationException
|
||||
|
@ -180,16 +179,16 @@ public interface IServerInterceptor {
|
|||
* </p>
|
||||
*
|
||||
*
|
||||
* @param server
|
||||
* @param theRequestDetails
|
||||
* 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 javax.servlet.http.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.
|
||||
* 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 javax.servlet.http.HttpServletRequest servlet request}. Note that the
|
||||
* bean properties are not all guaranteed to be populated, depending on how early
|
||||
* during processing the exception occurred.
|
||||
* @param theServletRequest
|
||||
* The incoming request
|
||||
* @param theServletResponse
|
||||
* The response. Note that interceptors may choose to provide a response (i.e. by calling {@link javax.servlet.http.HttpServletResponse#getWriter()}) but in that case it is important to return
|
||||
* <code>false</code>
|
||||
* <code>false</code> to indicate that the server itself should not also provide a response.
|
||||
* @return Return <code>true</code> if processing should continue normally. This is generally the right thing to do. If your interceptor is providing a response rather than letting HAPI handle the
|
||||
* response normally, you must return <code>false</code>. In this case, no further processing will occur and no further interceptors will be called.
|
||||
* @throws ServletException
|
||||
|
@ -197,7 +196,7 @@ public interface IServerInterceptor {
|
|||
* @throws IOException
|
||||
* If this exception is thrown, it will be re-thrown up to the container for handling.
|
||||
*/
|
||||
public boolean handleException(RestfulServer server, RequestDetails theRequestDetails, Throwable theException, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws ServletException,
|
||||
public boolean handleException(RequestDetails theRequestDetails, Throwable theException, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws ServletException,
|
||||
IOException;
|
||||
|
||||
}
|
||||
|
|
|
@ -71,7 +71,7 @@ public class InterceptorAdapter implements IServerInterceptor {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean handleException(RestfulServer server, RequestDetails theRequestDetails, Throwable theException, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws ServletException,
|
||||
public boolean handleException(RequestDetails theRequestDetails, Throwable theException, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws ServletException,
|
||||
IOException {
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -1574,6 +1574,31 @@
|
|||
|
||||
</subsection>
|
||||
|
||||
<subsection name="Idempotent Operations / Handling HTTP Get">
|
||||
|
||||
<p>
|
||||
The FHIR specification notes that if an operation is
|
||||
<a href="http://en.wikipedia.org/wiki/Idempotence">idempotent</a>
|
||||
(which means roughly that it does not modity any data or state
|
||||
on the server) then it may be invoked with an HTTP <code>GET</code>
|
||||
instead of an HTTP <code>POST</code>.
|
||||
</p>
|
||||
<p>
|
||||
If you are implementing an operation which is idempotent,
|
||||
you should mark your operation with
|
||||
<code>idempotent=true</code>,
|
||||
as shown in some of the examples above. The default value
|
||||
for this flag is <code>false</code>, meaning that operations
|
||||
will not support <code>HTTP GET</code> by default.
|
||||
</p>
|
||||
<p>
|
||||
Note that the HTTP GET form is only supported if the operation
|
||||
has only primitive parameters (no complex parameters or resource parameters).
|
||||
If a client makes a request containing a complex parameter, the
|
||||
server will respond with an <code>HTTP 405 Method Not Supported</code>.
|
||||
</p>
|
||||
</subsection>
|
||||
|
||||
</section>
|
||||
|
||||
</body>
|
||||
|
|
Loading…
Reference in New Issue