diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProvider.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProvider.java index 8bdd28c80ae..12e9af1504b 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProvider.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProvider.java @@ -10,7 +10,7 @@ package ca.uhn.fhir.jaxrs.server; * 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 + * 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, @@ -22,8 +22,6 @@ package ca.uhn.fhir.jaxrs.server; import java.io.IOException; import java.net.URL; -import java.util.Collections; -import java.util.List; import javax.interceptor.Interceptors; import javax.ws.rs.Consumes; @@ -41,7 +39,6 @@ import org.hl7.fhir.instance.model.api.IBaseResource; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jaxrs.server.interceptor.JaxRsExceptionInterceptor; -import ca.uhn.fhir.jaxrs.server.interceptor.JaxRsResponseException; import ca.uhn.fhir.jaxrs.server.util.JaxRsMethodBindings; import ca.uhn.fhir.jaxrs.server.util.JaxRsRequest; import ca.uhn.fhir.jaxrs.server.util.JaxRsRequest.Builder; @@ -60,47 +57,32 @@ import ca.uhn.fhir.rest.server.IRestfulServer; * @author Peter Van Houte | peter.vanhoute@agfa.com | Agfa Healthcare */ @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN }) -@Consumes({ MediaType.APPLICATION_FORM_URLENCODED, MediaType.APPLICATION_JSON, Constants.CT_FHIR_JSON, - Constants.CT_FHIR_XML }) +@Consumes({ MediaType.APPLICATION_FORM_URLENCODED, MediaType.APPLICATION_JSON, Constants.CT_FHIR_JSON, Constants.CT_FHIR_XML }) @Interceptors(JaxRsExceptionInterceptor.class) public abstract class AbstractJaxRsResourceProvider extends AbstractJaxRsProvider - implements IRestfulServer, IResourceProvider { +implements IRestfulServer, IResourceProvider { - /** the method bindings for this class */ - private final JaxRsMethodBindings theBindings; + /** the method bindings for this class */ + private final JaxRsMethodBindings theBindings; - /** - * The default constructor. The method bindings are retrieved from the class - * being constructed. - */ - protected AbstractJaxRsResourceProvider() { - super(); - theBindings = JaxRsMethodBindings.getMethodBindings(this, getClass()); - } - - /** + /** + * The default constructor. The method bindings are retrieved from the class + * being constructed. + */ + protected AbstractJaxRsResourceProvider() { + super(); + theBindings = JaxRsMethodBindings.getMethodBindings(this, getClass()); + } + + /** * Provides the ability to specify the {@link FhirContext}. * @param ctx the {@link FhirContext} instance. */ - protected AbstractJaxRsResourceProvider(FhirContext ctx) { + protected AbstractJaxRsResourceProvider(final FhirContext ctx) { super(ctx); theBindings = JaxRsMethodBindings.getMethodBindings(this, getClass()); } - - - /** - * This constructor takes in an explicit interface class. This subclass - * should be identical to the class being constructed but is given - * explicitly in order to avoid issues with proxy classes in a jee - * environment. - * - * @param theProviderClass the interface of the class - */ - protected AbstractJaxRsResourceProvider(Class theProviderClass) { - super(); - theBindings = JaxRsMethodBindings.getMethodBindings(this, theProviderClass); - } /** * This constructor takes in an explicit interface class. This subclass @@ -108,65 +90,82 @@ public abstract class AbstractJaxRsResourceProvider ext * explicitly in order to avoid issues with proxy classes in a jee * environment. * + * @param theProviderClass the interface of the class + */ + protected AbstractJaxRsResourceProvider(final Class theProviderClass) { + super(); + theBindings = JaxRsMethodBindings.getMethodBindings(this, theProviderClass); + } + + /** + * This constructor takes in an explicit interface class. This subclass + * should be identical to the class being constructed but is given + * explicitly in order to avoid issues with proxy classes in a jee + * environment. + * * @param ctx the {@link FhirContext} instance. * @param theProviderClass the interface of the class */ - protected AbstractJaxRsResourceProvider(FhirContext ctx, Class theProviderClass) { + protected AbstractJaxRsResourceProvider(final FhirContext ctx, final Class theProviderClass) { super(ctx); theBindings = JaxRsMethodBindings.getMethodBindings(this, theProviderClass); } - /** - * The base for request for a resource provider has the following form:
- * {@link AbstractJaxRsResourceProvider#getBaseForServer() - * getBaseForServer()} + "/" + - * {@link AbstractJaxRsResourceProvider#getResourceType() getResourceType()} - * .{@link java.lang.Class#getSimpleName() getSimpleName()} - */ - @Override - public String getBaseForRequest() { - try { - return new URL(getUriInfo().getBaseUri().toURL(), getResourceType().getSimpleName()).toExternalForm(); - } catch (Exception e) { - // cannot happen - return null; - } - } + /** + * The base for request for a resource provider has the following form:
+ * {@link AbstractJaxRsResourceProvider#getBaseForServer() + * getBaseForServer()} + "/" + + * {@link AbstractJaxRsResourceProvider#getResourceType() getResourceType()} + * .{@link java.lang.Class#getSimpleName() getSimpleName()} + */ + @Override + public String getBaseForRequest() { + try { + return new URL(getUriInfo().getBaseUri().toURL(), getResourceType().getSimpleName()).toExternalForm(); + } + catch (final Exception e) { + // cannot happen + return null; + } + } - /** - * Create a new resource with a server assigned id - * - * @param resource the body of the post method containing resource being created in a xml/json form - * @return the response - * @see https://www.hl7. org/fhir/http.html#create - */ - @POST - public Response create(final String resource) throws IOException { - return execute(getResourceRequest(RequestTypeEnum.POST, RestOperationTypeEnum.CREATE).resource(resource)); - } + /** + * Create a new resource with a server assigned id + * + * @param resource the body of the post method containing resource being created in a xml/json form + * @return the response + * @see https://www.hl7. org/fhir/http.html#create + */ + @POST + public Response create(final String resource) + throws IOException { + return execute(getResourceRequest(RequestTypeEnum.POST, RestOperationTypeEnum.CREATE).resource(resource)); + } - /** - * Search the resource type based on some filter criteria - * - * @return the response - * @see https://www.hl7.org/fhir/http.html#search - */ - @POST - @Path("/_search") - public Response searchWithPost() throws IOException { - return execute(getResourceRequest(RequestTypeEnum.POST, RestOperationTypeEnum.SEARCH_TYPE)); - } + /** + * Search the resource type based on some filter criteria + * + * @return the response + * @see https://www.hl7.org/fhir/http.html#search + */ + @POST + @Path("/_search") + public Response searchWithPost() + throws IOException { + return execute(getResourceRequest(RequestTypeEnum.POST, RestOperationTypeEnum.SEARCH_TYPE)); + } - /** - * Search the resource type based on some filter criteria - * - * @return the response - * @see https://www.hl7.org/fhir/http.html#search - */ - @GET - public Response search() throws IOException { - return execute(getResourceRequest(RequestTypeEnum.GET, RestOperationTypeEnum.SEARCH_TYPE)); - } + /** + * Search the resource type based on some filter criteria + * + * @return the response + * @see https://www.hl7.org/fhir/http.html#search + */ + @GET + public Response search() + throws IOException { + return execute(getResourceRequest(RequestTypeEnum.GET, RestOperationTypeEnum.SEARCH_TYPE)); + } /** * Update an existing resource based on the given condition @@ -175,23 +174,25 @@ public abstract class AbstractJaxRsResourceProvider ext * @see https://www.hl7.org/fhir/http.html#update */ @PUT - public Response conditionalUpdate(final String resource) throws IOException { + public Response conditionalUpdate(final String resource) + throws IOException { return execute(getResourceRequest(RequestTypeEnum.PUT, RestOperationTypeEnum.UPDATE).resource(resource)); } - - /** - * Update an existing resource by its id (or create it if it is new) - * - * @param id the id of the resource - * @param resource the body contents for the put method - * @return the response - * @see https://www.hl7.org/fhir/http.html#update - */ - @PUT - @Path("/{id}") - public Response update(@PathParam("id") final String id, final String resource) throws IOException { - return execute(getResourceRequest(RequestTypeEnum.PUT, RestOperationTypeEnum.UPDATE).id(id).resource(resource)); - } + + /** + * Update an existing resource by its id (or create it if it is new) + * + * @param id the id of the resource + * @param resource the body contents for the put method + * @return the response + * @see https://www.hl7.org/fhir/http.html#update + */ + @PUT + @Path("/{id}") + public Response update(@PathParam("id") final String id, final String resource) + throws IOException { + return execute(getResourceRequest(RequestTypeEnum.PUT, RestOperationTypeEnum.UPDATE).id(id).resource(resource)); + } /** * Delete a resource based on the given condition @@ -200,157 +201,163 @@ public abstract class AbstractJaxRsResourceProvider ext * @see https://www.hl7.org/fhir/http.html#delete */ @DELETE - public Response delete() throws IOException { + public Response delete() + throws IOException { return execute(getResourceRequest(RequestTypeEnum.DELETE, RestOperationTypeEnum.DELETE)); } - - /** - * Delete a resource - * - * @param id the id of the resource to delete - * @return the response - * @see https://www.hl7.org/fhir/http.html#delete - */ - @DELETE - @Path("/{id}") - public Response delete(@PathParam("id") final String id) throws IOException { - return execute(getResourceRequest(RequestTypeEnum.DELETE, RestOperationTypeEnum.DELETE).id(id)); - } - /** - * Read the current state of the resource - * - * @param id the id of the resource to read - * @return the response - * @see https://www.hl7.org/fhir/http.html#read - */ - @GET - @Path("/{id}") - public Response find(@PathParam("id") final String id) throws IOException { - return execute(getResourceRequest(RequestTypeEnum.GET, RestOperationTypeEnum.READ).id(id)); - } + /** + * Delete a resource + * + * @param id the id of the resource to delete + * @return the response + * @see https://www.hl7.org/fhir/http.html#delete + */ + @DELETE + @Path("/{id}") + public Response delete(@PathParam("id") final String id) + throws IOException { + return execute(getResourceRequest(RequestTypeEnum.DELETE, RestOperationTypeEnum.DELETE).id(id)); + } - /** - * Execute a custom operation - * - * @param resource the resource to create - * @param requestType the type of request - * @param id the id of the resource on which to perform the operation - * @param operationName the name of the operation to execute - * @param operationType the rest operation type - * @return the response - * @see https://www.hl7.org/fhir/operations.html - */ - protected Response customOperation(final String resource, RequestTypeEnum requestType, String id, - String operationName, RestOperationTypeEnum operationType) throws IOException { - Builder request = getResourceRequest(requestType, operationType).resource(resource).id(id); - return execute(request, operationName); - } + /** + * Read the current state of the resource + * + * @param id the id of the resource to read + * @return the response + * @see https://www.hl7.org/fhir/http.html#read + */ + @GET + @Path("/{id}") + public Response find(@PathParam("id") final String id) + throws IOException { + return execute(getResourceRequest(RequestTypeEnum.GET, RestOperationTypeEnum.READ).id(id)); + } - /** - * Retrieve the update history for a particular resource - * - * @param id the id of the resource - * @param version the version of the resource - * @return the response - * @see https://www.hl7.org/fhir/http.html#history - */ - @GET - @Path("/{id}/_history/{version}") - public Response findHistory(@PathParam("id") final String id, @PathParam("version") final String version) - throws IOException { - Builder theRequest = getResourceRequest(RequestTypeEnum.GET, RestOperationTypeEnum.VREAD).id(id) - .version(version); - return execute(theRequest); - } + /** + * Execute a custom operation + * + * @param resource the resource to create + * @param requestType the type of request + * @param id the id of the resource on which to perform the operation + * @param operationName the name of the operation to execute + * @param operationType the rest operation type + * @return the response + * @see https://www.hl7.org/fhir/operations.html + */ + protected Response customOperation(final String resource, final RequestTypeEnum requestType, final String id, + final String operationName, final RestOperationTypeEnum operationType) + throws IOException { + final Builder request = getResourceRequest(requestType, operationType).resource(resource).id(id); + return execute(request, operationName); + } - /** - * Compartment Based Access - * - * @param id the resource to which the compartment belongs - * @param compartment the compartment - * @return the repsonse - * @see https://www.hl7.org/fhir/http.html#search - * @see https://www.hl7.org/fhir/compartments.html#compartment - */ - @GET - @Path("/{id}/{compartment}") - public Response findCompartment(@PathParam("id") final String id, - @PathParam("compartment") final String compartment) throws IOException { - Builder theRequest = getResourceRequest(RequestTypeEnum.GET, RestOperationTypeEnum.SEARCH_TYPE).id(id) - .compartment(compartment); - return execute(theRequest, compartment); - } + /** + * Retrieve the update history for a particular resource + * + * @param id the id of the resource + * @param version the version of the resource + * @return the response + * @see https://www.hl7.org/fhir/http.html#history + */ + @GET + @Path("/{id}/_history/{version}") + public Response findHistory(@PathParam("id") final String id, @PathParam("version") final String version) + throws IOException { + final Builder theRequest = getResourceRequest(RequestTypeEnum.GET, RestOperationTypeEnum.VREAD).id(id).version(version); + return execute(theRequest); + } - /** - * Execute the method described by the requestBuilder and methodKey - * - * @param theRequestBuilder the requestBuilder that contains the information about the request - * @param methodKey the key determining the method to be executed - * @return the response - */ - private Response execute(Builder theRequestBuilder, String methodKey) throws IOException { - JaxRsRequest theRequest = theRequestBuilder.build(); - BaseMethodBinding method = getBinding(theRequest.getRestOperationType(), methodKey); - try { - return (Response) method.invokeServer(this, theRequest); - } catch (JaxRsResponseException theException) { - return new JaxRsExceptionInterceptor().convertExceptionIntoResponse(theRequest, theException); - } - } - - /** - * Execute the method described by the requestBuilder - * - * @param theRequestBuilder the requestBuilder that contains the information about the request - * @return the response - */ - private Response execute(Builder theRequestBuilder) throws IOException { - return execute(theRequestBuilder, JaxRsMethodBindings.DEFAULT_METHOD_KEY); - } - - /** - * Return the method binding for the given rest operation - * - * @param restOperation the rest operation to retrieve - * @param theBindingKey the key determining the method to be executed (needed for e.g. custom operation) - * @return - */ - protected BaseMethodBinding getBinding(RestOperationTypeEnum restOperation, String theBindingKey) { - return getBindings().getBinding(restOperation, theBindingKey); - } + /** + * Compartment Based Access + * + * @param id the resource to which the compartment belongs + * @param compartment the compartment + * @return the repsonse + * @see https://www.hl7.org/fhir/http.html#search + * @see https://www.hl7.org/fhir/compartments.html#compartment + */ + @GET + @Path("/{id}/{compartment}") + public Response findCompartment(@PathParam("id") final String id, @PathParam("compartment") final String compartment) + throws IOException { + final Builder theRequest = getResourceRequest(RequestTypeEnum.GET, RestOperationTypeEnum.SEARCH_TYPE).id(id).compartment( + compartment); + return execute(theRequest, compartment); + } - /** - * Default: no paging provider - */ - @Override - public IPagingProvider getPagingProvider() { - return null; - } + /** + * Execute the method described by the requestBuilder and methodKey + * + * @param theRequestBuilder the requestBuilder that contains the information about the request + * @param methodKey the key determining the method to be executed + * @return the response + */ + private Response execute(final Builder theRequestBuilder, final String methodKey) + throws IOException { + final JaxRsRequest theRequest = theRequestBuilder.build(); + final BaseMethodBinding method = getBinding(theRequest.getRestOperationType(), methodKey); + try { + return (Response) method.invokeServer(this, theRequest); + } + catch (final Throwable theException) { + return handleException(theRequest, theException); + } + } - /** - * Default: BundleInclusionRule.BASED_ON_INCLUDES - */ - @Override - public BundleInclusionRule getBundleInclusionRule() { - return BundleInclusionRule.BASED_ON_INCLUDES; - } + /** + * Execute the method described by the requestBuilder + * + * @param theRequestBuilder the requestBuilder that contains the information about the request + * @return the response + */ + private Response execute(final Builder theRequestBuilder) + throws IOException { + return execute(theRequestBuilder, JaxRsMethodBindings.DEFAULT_METHOD_KEY); + } - /** - * The resource type should return conform to the generic resource included - * in the topic - */ - @Override - public abstract Class getResourceType(); + /** + * Return the method binding for the given rest operation + * + * @param restOperation the rest operation to retrieve + * @param theBindingKey the key determining the method to be executed (needed for e.g. custom operation) + * @return + */ + protected BaseMethodBinding getBinding(final RestOperationTypeEnum restOperation, final String theBindingKey) { + return getBindings().getBinding(restOperation, theBindingKey); + } - /** - * Return the bindings defined in this resource provider - * - * @return the jax-rs method bindings - */ - public JaxRsMethodBindings getBindings() { - return theBindings; - } + /** + * Default: no paging provider + */ + @Override + public IPagingProvider getPagingProvider() { + return null; + } + + /** + * Default: BundleInclusionRule.BASED_ON_INCLUDES + */ + @Override + public BundleInclusionRule getBundleInclusionRule() { + return BundleInclusionRule.BASED_ON_INCLUDES; + } + + /** + * The resource type should return conform to the generic resource included + * in the topic + */ + @Override + public abstract Class getResourceType(); + + /** + * Return the bindings defined in this resource provider + * + * @return the jax-rs method bindings + */ + public JaxRsMethodBindings getBindings() { + return theBindings; + } /** * Return the request builder based on the resource name for the server diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/interceptor/JaxRsExceptionInterceptor.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/interceptor/JaxRsExceptionInterceptor.java index 5076ac2abfe..f2e124c5b82 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/interceptor/JaxRsExceptionInterceptor.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/interceptor/JaxRsExceptionInterceptor.java @@ -10,7 +10,7 @@ package ca.uhn.fhir.jaxrs.server.interceptor; * 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 + * 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, @@ -35,42 +35,44 @@ import ca.uhn.fhir.rest.server.interceptor.ExceptionHandlingInterceptor; /** * An interceptor that catches the jax-rs exceptions - * + * * @author Peter Van Houte | peter.vanhoute@agfa.com | Agfa Healthcare */ public class JaxRsExceptionInterceptor { /** the existing exception handler which is able to convert exception into responses*/ - private ExceptionHandlingInterceptor exceptionHandler; - + private final ExceptionHandlingInterceptor exceptionHandler; + /** * The default constructor */ public JaxRsExceptionInterceptor() { - this.exceptionHandler = new ExceptionHandlingInterceptor(); - } - + this.exceptionHandler = new ExceptionHandlingInterceptor(); + } + /** * A utility constructor for unit testing * @param exceptionHandler the handler for the exception conversion */ - JaxRsExceptionInterceptor(ExceptionHandlingInterceptor exceptionHandler) { - this.exceptionHandler = exceptionHandler; - } - - /** - * This interceptor will catch all exception and convert them using the exceptionhandler - * @param ctx the invocation context - * @return the result - * @throws JaxRsResponseException an exception that can be handled by a jee container - */ - @AroundInvoke - public Object intercept(final InvocationContext ctx) throws JaxRsResponseException { + JaxRsExceptionInterceptor(final ExceptionHandlingInterceptor exceptionHandler) { + this.exceptionHandler = exceptionHandler; + } + + /** + * This interceptor will catch all exception and convert them using the exceptionhandler + * @param ctx the invocation context + * @return the result + * @throws JaxRsResponseException an exception that can be handled by a jee container + */ + @AroundInvoke + public Object intercept(final InvocationContext ctx) + throws JaxRsResponseException { try { return ctx.proceed(); - } catch(final Exception theException) { - AbstractJaxRsProvider theServer = (AbstractJaxRsProvider) ctx.getTarget(); - throw convertException(theServer, theException); + } + catch (final Exception theException) { + final AbstractJaxRsProvider theServer = (AbstractJaxRsProvider) ctx.getTarget(); + throw convertException(theServer, theException); } } @@ -80,41 +82,48 @@ public class JaxRsExceptionInterceptor { * @param theException the exception to convert * @return JaxRsResponseException */ - public JaxRsResponseException convertException(final AbstractJaxRsProvider theServer, final Throwable theException) { + public JaxRsResponseException convertException(final AbstractJaxRsProvider theServer, final Throwable theException) { if (theServer.withStackTrace()) { exceptionHandler.setReturnStackTracesForExceptionTypes(Throwable.class); } - JaxRsRequest requestDetails = theServer.getRequest(null, null).build(); - BaseServerResponseException convertedException = preprocessException(theException, requestDetails); - return new JaxRsResponseException(convertedException); - } - - /** - * This method converts an exception into a response - * @param theRequest the request - * @param theException the thrown exception - * @return the response describing the error - * @throws IOException - */ - public Response convertExceptionIntoResponse(JaxRsRequest theRequest, JaxRsResponseException theException) - throws IOException { - return handleExceptionWithoutServletError(theRequest, theException); - } + final JaxRsRequest requestDetails = theServer.getRequest(null, null).build(); + final BaseServerResponseException convertedException = preprocessException(theException, requestDetails); + return new JaxRsResponseException(convertedException); + } - private BaseServerResponseException preprocessException(final Throwable theException, JaxRsRequest requestDetails) { - try { - return exceptionHandler.preProcessOutgoingException(requestDetails, theException, null); - } catch(ServletException e) { - return new InternalErrorException(e); - } - } + /** + * This method converts an exception into a response + * @param theRequest the request + * @param theException the thrown exception + * @return the response describing the error + * @throws IOException + */ + public Response convertExceptionIntoResponse(final JaxRsRequest theRequest, final JaxRsResponseException theException) + throws IOException { + return handleExceptionWithoutServletError(theRequest, theException); + } - private Response handleExceptionWithoutServletError(JaxRsRequest theRequest, BaseServerResponseException theException) throws IOException { - try { - return (Response) exceptionHandler.handleException(theRequest, theException); - } catch (ServletException e) { - BaseServerResponseException newException = preprocessException(new InternalErrorException(e), theRequest); - return handleExceptionWithoutServletError(theRequest, newException); - } - } + private BaseServerResponseException preprocessException(final Throwable theException, final JaxRsRequest requestDetails) { + try { + Throwable theExceptionToConvert = theException; + if (!(theException instanceof BaseServerResponseException) && (theException.getCause() instanceof BaseServerResponseException)) { + theExceptionToConvert = theException.getCause(); + } + return exceptionHandler.preProcessOutgoingException(requestDetails, theExceptionToConvert, null); + } + catch (final ServletException e) { + return new InternalErrorException(e); + } + } + + private Response handleExceptionWithoutServletError(final JaxRsRequest theRequest, final BaseServerResponseException theException) + throws IOException { + try { + return (Response) exceptionHandler.handleException(theRequest, theException); + } + catch (final ServletException e) { + final BaseServerResponseException newException = preprocessException(new InternalErrorException(e), theRequest); + return handleExceptionWithoutServletError(theRequest, newException); + } + } }