diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsBundleProvider.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsBundleProvider.java new file mode 100644 index 00000000000..4bd18bd50d8 --- /dev/null +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsBundleProvider.java @@ -0,0 +1,196 @@ +package ca.uhn.fhir.jaxrs.server; + +/* + * #%L + * HAPI FHIR JAX-RS Server + * %% + * Copyright (C) 2014 - 2016 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import java.io.IOException; +import java.util.Collections; +import java.util.List; + +import javax.interceptor.Interceptors; +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jaxrs.server.interceptor.JaxRsExceptionInterceptor; +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; +import ca.uhn.fhir.rest.api.RequestTypeEnum; +import ca.uhn.fhir.rest.api.RestOperationTypeEnum; +import ca.uhn.fhir.rest.method.BaseMethodBinding; +import ca.uhn.fhir.rest.server.BundleInclusionRule; +import ca.uhn.fhir.rest.server.Constants; +import ca.uhn.fhir.rest.server.IBundleProvider; +import ca.uhn.fhir.rest.server.IPagingProvider; +import ca.uhn.fhir.rest.server.IRestfulServer; +import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; + +/** + * This server is the abstract superclass for all bundle providers. It exposes + * a large amount of the fhir api functionality using JAXRS + * + * @author Peter Van Houte | peter.vanhoute@agfa.com | Agfa Healthcare + */ +@SuppressWarnings("javadoc") +@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 }) +@Interceptors(JaxRsExceptionInterceptor.class) +public abstract class AbstractJaxRsBundleProvider extends AbstractJaxRsProvider implements IRestfulServer, IBundleProvider { + + /** the method bindings for this class */ + private final JaxRsMethodBindings theBindings; + + /** + * The default constructor. The method bindings are retrieved from the class + * being constructed. + */ + protected AbstractJaxRsBundleProvider() { + super(); + theBindings = JaxRsMethodBindings.getMethodBindings(this, getClass()); + } + + /** + * Provides the ability to specify the {@link FhirContext}. + * @param ctx the {@link FhirContext} instance. + */ + protected AbstractJaxRsBundleProvider(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 AbstractJaxRsBundleProvider(final Class theProviderClass) { + theBindings = JaxRsMethodBindings.getMethodBindings(this, theProviderClass); + } + + /** + * Create all resources in one transaction + * + * @param resource the body of the post method containing the bundle of the resources 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(getRequest(RequestTypeEnum.POST, RestOperationTypeEnum.TRANSACTION).resource(resource)); + } + + /** + * 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(getRequest(RequestTypeEnum.GET, RestOperationTypeEnum.SEARCH_TYPE)); + } + + /** + * 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); + } + } + + /** + * 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); + } + + /** + * 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); + } + + /** + * Default: an empty list of interceptors + * + * @see ca.uhn.fhir.rest.server.IRestfulServer#getInterceptors() + */ + @Override + public List getInterceptors() { + return Collections.emptyList(); + } + + /** + * Default: no paging provider + */ + @Override + public IPagingProvider getPagingProvider() { + return null; + } + + /** + * Default: BundleInclusionRule.BASED_ON_INCLUDES + */ + @Override + public BundleInclusionRule getBundleInclusionRule() { + return BundleInclusionRule.BASED_ON_INCLUDES; + } + + /** + * Return the bindings defined in this resource provider + * + * @return the jax-rs method bindings + */ + public JaxRsMethodBindings getBindings() { + return theBindings; + } + +} diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsProvider.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsProvider.java index d5a52771419..d1faa8eaae5 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsProvider.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsProvider.java @@ -1,7 +1,6 @@ package ca.uhn.fhir.jaxrs.server; -import java.util.Collections; - +import java.io.IOException; /* * #%L * HAPI FHIR JAX-RS Server @@ -12,7 +11,7 @@ import java.util.Collections; * 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, @@ -21,7 +20,7 @@ import java.util.Collections; * limitations under the License. * #L% */ - +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -30,11 +29,18 @@ import java.util.Map.Entry; import javax.ws.rs.core.Context; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; + 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.JaxRsRequest; import ca.uhn.fhir.jaxrs.server.util.JaxRsRequest.Builder; +import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.rest.api.RequestTypeEnum; import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.server.AddProfileTagEnum; @@ -43,18 +49,25 @@ import ca.uhn.fhir.rest.server.EncodingEnum; import ca.uhn.fhir.rest.server.HardcodedServerAddressStrategy; import ca.uhn.fhir.rest.server.IRestfulServerDefaults; import ca.uhn.fhir.rest.server.IServerAddressStrategy; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; +import ca.uhn.fhir.util.OperationOutcomeUtil; /** * This is the abstract superclass for all jaxrs providers. It contains some defaults implementing * the IRestfulServerDefaults interface and exposes the uri and headers. - * + * * @author Peter Van Houte | peter.vanhoute@agfa.com | Agfa Healthcare */ public abstract class AbstractJaxRsProvider implements IRestfulServerDefaults { private final FhirContext CTX; + /** The default exception interceptor */ + private static final JaxRsExceptionInterceptor DEFAULT_EXCEPTION_HANDLER = new JaxRsExceptionInterceptor(); + private static final String PROCESSING = "processing"; + private static final String ERROR = "error"; + /** the uri info */ @Context private UriInfo theUriInfo; @@ -63,10 +76,10 @@ public abstract class AbstractJaxRsProvider implements IRestfulServerDefaults { private HttpHeaders theHeaders; @Override - public FhirContext getFhirContext() { + public FhirContext getFhirContext() { return CTX; } - + /** * Default is DSTU2. Use {@link AbstractJaxRsProvider#AbstractJaxRsProvider(FhirContext)} to specify a DSTU3 context. */ @@ -75,146 +88,190 @@ public abstract class AbstractJaxRsProvider implements IRestfulServerDefaults { } /** - * + * * @param ctx the {@link FhirContext} to support. */ - protected AbstractJaxRsProvider(FhirContext ctx) { + protected AbstractJaxRsProvider(final FhirContext ctx) { CTX = ctx; } - + /** * This method returns the query parameters * @return the query parameters */ public Map getParameters() { - MultivaluedMap queryParameters = getUriInfo().getQueryParameters(); - HashMap params = new HashMap(); - for (Entry> paramEntry : queryParameters.entrySet()) { + final MultivaluedMap queryParameters = getUriInfo().getQueryParameters(); + final HashMap params = new HashMap(); + for (final Entry> paramEntry : queryParameters.entrySet()) { params.put(paramEntry.getKey(), paramEntry.getValue().toArray(new String[paramEntry.getValue().size()])); } return params; } - + /** - * This method returns the default server address strategy. The default strategy return the - * base uri for the request {@link AbstractJaxRsProvider#getBaseForRequest() getBaseForRequest()} + * This method returns the default server address strategy. The default strategy return the + * base uri for the request {@link AbstractJaxRsProvider#getBaseForRequest() getBaseForRequest()} * @return */ public IServerAddressStrategy getServerAddressStrategy() { - HardcodedServerAddressStrategy addressStrategy = new HardcodedServerAddressStrategy(); + final HardcodedServerAddressStrategy addressStrategy = new HardcodedServerAddressStrategy(); addressStrategy.setValue(getBaseForRequest()); - return addressStrategy; + return addressStrategy; } /** * This method returns the server base, independent of the request or resource. * @see javax.ws.rs.core.UriInfo#getBaseUri() * @return the ascii string for the server base - */ + */ public String getBaseForServer() { return getUriInfo().getBaseUri().toASCIIString(); } - + /** * This method returns the server base, including the resource path. - * {@link javax.ws.rs.core.UriInfo#getBaseUri() UriInfo#getBaseUri()} + * {@link javax.ws.rs.core.UriInfo#getBaseUri() UriInfo#getBaseUri()} * @return the ascii string for the base resource provider path - */ + */ public String getBaseForRequest() { return getBaseForServer(); - } - - /** - * Default: an empty list of interceptors (Interceptors are not yet supported - * in the JAX-RS server). Please get in touch if you'd like to help! - * - * @see ca.uhn.fhir.rest.server.IRestfulServer#getInterceptors() - */ - @Override - public List getInterceptors() { - return Collections.emptyList(); - } + } /** - * Get the uriInfo - * @return the uri info - */ - public UriInfo getUriInfo() { - return this.theUriInfo; - } - - /** - * Set the Uri Info - * @param uriInfo the uri info - */ - public void setUriInfo(UriInfo uriInfo) { - this.theUriInfo = uriInfo; - } - - /** - * Get the headers - * @return the headers - */ - public HttpHeaders getHeaders() { - return this.theHeaders; - } - - /** - * Set the headers - * @param headers the headers to set - */ - public void setHeaders(HttpHeaders headers) { - this.theHeaders = headers; - } - - /** - * Return the requestbuilder for the server - * @param requestType the type of the request - * @param restOperation the rest operation type - * @return the requestbuilder - */ - public Builder getRequest(RequestTypeEnum requestType, RestOperationTypeEnum restOperation) { - return new JaxRsRequest.Builder(this, requestType, restOperation, theUriInfo.getRequestUri().toString()); - } + * Default: an empty list of interceptors (Interceptors are not yet supported + * in the JAX-RS server). Please get in touch if you'd like to help! + * + * @see ca.uhn.fhir.rest.server.IRestfulServer#getInterceptors() + */ + @Override + public List getInterceptors() { + return Collections.emptyList(); + } + + /** + * Get the uriInfo + * @return the uri info + */ + public UriInfo getUriInfo() { + return this.theUriInfo; + } + + /** + * Set the Uri Info + * @param uriInfo the uri info + */ + public void setUriInfo(final UriInfo uriInfo) { + this.theUriInfo = uriInfo; + } + + /** + * Get the headers + * @return the headers + */ + public HttpHeaders getHeaders() { + return this.theHeaders; + } + + /** + * Set the headers + * @param headers the headers to set + */ + public void setHeaders(final HttpHeaders headers) { + this.theHeaders = headers; + } + + /** + * Return the requestbuilder for the server + * @param requestType the type of the request + * @param restOperation the rest operation type + * @param theResourceName the resource name + * @return the requestbuilder + */ + public Builder getRequest(final RequestTypeEnum requestType, final RestOperationTypeEnum restOperation, final String theResourceName) { + return new JaxRsRequest.Builder(this, requestType, restOperation, theUriInfo.getRequestUri().toString(), theResourceName); + } + + /** + * Return the requestbuilder for the server + * @param requestType the type of the request + * @param restOperation the rest operation type + * @return the requestbuilder + */ + public Builder getRequest(final RequestTypeEnum requestType, final RestOperationTypeEnum restOperation) { + return getRequest(requestType, restOperation, null); + } /** * DEFAULT = EncodingEnum.JSON */ @Override - public EncodingEnum getDefaultResponseEncoding() { + public EncodingEnum getDefaultResponseEncoding() { return EncodingEnum.JSON; } - + /** * DEFAULT = true */ @Override - public boolean isDefaultPrettyPrint() { - return true; - } + public boolean isDefaultPrettyPrint() { + return true; + } /** * DEFAULT = ETagSupportEnum.DISABLED */ - @Override - public ETagSupportEnum getETagSupport() { - return ETagSupportEnum.DISABLED; - } + @Override + public ETagSupportEnum getETagSupport() { + return ETagSupportEnum.DISABLED; + } /** * DEFAULT = AddProfileTagEnum.NEVER */ - @Override - public AddProfileTagEnum getAddProfileTag() { - return AddProfileTagEnum.NEVER; - } + @Override + public AddProfileTagEnum getAddProfileTag() { + return AddProfileTagEnum.NEVER; + } - /** + /** * DEFAULT = false */ - @Override - public boolean isUseBrowserFriendlyContentTypes() { - return true; - } + @Override + public boolean isUseBrowserFriendlyContentTypes() { + return true; + } + /** + * DEFAULT = false + */ + public boolean withStackTrace() { + return false; + } + + /** + * Convert an exception to a response + * @param theRequest the incoming request + * @param theException the exception to convert + * @return response + * @throws IOException + */ + public Response handleException(final JaxRsRequest theRequest, final Throwable theException) + throws IOException { + if (theException instanceof JaxRsResponseException) { + return DEFAULT_EXCEPTION_HANDLER.convertExceptionIntoResponse(theRequest, (JaxRsResponseException) theException); + } else if (theException instanceof DataFormatException) { + return DEFAULT_EXCEPTION_HANDLER.convertExceptionIntoResponse(theRequest, new JaxRsResponseException( + new InvalidRequestException(theException.getMessage(), createOutcome((DataFormatException) theException)))); + } else { + return DEFAULT_EXCEPTION_HANDLER.convertExceptionIntoResponse(theRequest, + DEFAULT_EXCEPTION_HANDLER.convertException(this, theException)); + } + } + + private IBaseOperationOutcome createOutcome(final DataFormatException theException) { + final IBaseOperationOutcome oo = OperationOutcomeUtil.newInstance(getFhirContext()); + final String detailsValue = theException.getMessage() + "\n\n" + ExceptionUtils.getStackTrace(theException); + OperationOutcomeUtil.addIssue(getFhirContext(), oo, ERROR, detailsValue, null, PROCESSING); + return oo; + } } 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 204b9e39e33..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, @@ -39,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; @@ -58,272 +57,315 @@ 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 { - /** the method bindings for this class */ - private final JaxRsMethodBindings theBindings; +implements IRestfulServer, IResourceProvider { - /** - * The default constructor. The method bindings are retrieved from the class - * being constructed. - */ - protected AbstractJaxRsResourceProvider() { - super(); - theBindings = JaxRsMethodBindings.getMethodBindings(this, getClass()); - } + /** the method bindings for this class */ + private final JaxRsMethodBindings theBindings; - /** - * Provides the ability to specify the {@link FhirContext}. - * @param ctx the {@link FhirContext} instance. - */ - protected AbstractJaxRsResourceProvider(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); - } + /** + * The default constructor. The method bindings are retrieved from the class + * being constructed. + */ + protected AbstractJaxRsResourceProvider() { + super(); + 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 ctx the {@link FhirContext} instance. - * @param theProviderClass the interface of the class - */ - protected AbstractJaxRsResourceProvider(FhirContext ctx, 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; - } - } + /** + * Provides the ability to specify the {@link FhirContext}. + * @param ctx the {@link FhirContext} instance. + */ + protected AbstractJaxRsResourceProvider(final FhirContext ctx) { + super(ctx); + theBindings = JaxRsMethodBindings.getMethodBindings(this, getClass()); + } - /** - * 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(getRequest(RequestTypeEnum.POST, RestOperationTypeEnum.CREATE).resource(resource)); - } + /** + * 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(final Class theProviderClass) { + super(); + theBindings = JaxRsMethodBindings.getMethodBindings(this, theProviderClass); + } - /** - * 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(getRequest(RequestTypeEnum.POST, RestOperationTypeEnum.SEARCH_TYPE)); - } + /** + * 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(final FhirContext ctx, final Class theProviderClass) { + super(ctx); + theBindings = JaxRsMethodBindings.getMethodBindings(this, theProviderClass); + } - /** - * 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(getRequest(RequestTypeEnum.GET, RestOperationTypeEnum.SEARCH_TYPE)); - } + /** + * 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; + } + } - /** - * 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(getRequest(RequestTypeEnum.PUT, RestOperationTypeEnum.UPDATE).id(id).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)); + } - /** - * 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(getRequest(RequestTypeEnum.DELETE, RestOperationTypeEnum.DELETE).id(id)); - } + /** + * 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)); + } - /** - * 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(getRequest(RequestTypeEnum.GET, RestOperationTypeEnum.READ).id(id)); - } + /** + * 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)); + } - /** - * 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 = getRequest(requestType, operationType).resource(resource).id(id); - return execute(request, operationName); - } + /** + * Update an existing resource based on the given condition + * @param resource the body contents for the put method + * @return the response + * @see https://www.hl7.org/fhir/http.html#update + */ + @PUT + public Response conditionalUpdate(final String resource) + throws IOException { + return execute(getResourceRequest(RequestTypeEnum.PUT, RestOperationTypeEnum.UPDATE).resource(resource)); + } - /** - * 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 = getRequest(RequestTypeEnum.GET, RestOperationTypeEnum.VREAD).id(id) - .version(version); - return execute(theRequest); - } + /** + * 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)); + } - /** - * 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 = getRequest(RequestTypeEnum.GET, RestOperationTypeEnum.SEARCH_TYPE).id(id) - .compartment(compartment); - return execute(theRequest, compartment); - } + /** + * Delete a resource based on the given condition + * + * @return the response + * @see https://www.hl7.org/fhir/http.html#delete + */ + @DELETE + public Response delete() + throws IOException { + return execute(getResourceRequest(RequestTypeEnum.DELETE, RestOperationTypeEnum.DELETE)); + } - /** - * 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); - } + /** + * 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)); + } - /** - * Default: no paging provider - */ - @Override - public IPagingProvider getPagingProvider() { - return null; - } + /** + * 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)); + } - /** - * Default: BundleInclusionRule.BASED_ON_INCLUDES - */ - @Override - public BundleInclusionRule getBundleInclusionRule() { - return BundleInclusionRule.BASED_ON_INCLUDES; - } + /** + * 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); + } - /** - * The resource type should return conform to the generic resource included - * in the topic - */ - @Override - public abstract Class getResourceType(); + /** + * 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); + } - /** - * Return the bindings defined in this resource provider - * - * @return the jax-rs method bindings - */ - public JaxRsMethodBindings getBindings() { - return theBindings; - } + /** + * 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); + } + /** + * 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); + } + } + + /** + * 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); + } + + /** + * 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); + } + + /** + * 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 + * @param requestType the type of the request + * @param restOperation the rest operation type + * @return the requestbuilder + */ + private Builder getResourceRequest(final RequestTypeEnum requestType, final RestOperationTypeEnum restOperation) { + return getRequest(requestType, restOperation, getResourceType().getSimpleName()); + } } 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 28f297ab920..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,77 +35,95 @@ 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); } } - private JaxRsResponseException convertException(final AbstractJaxRsProvider theServer, final Exception theException) { - 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); - } + /** + * This method convert an exception to a JaxRsResponseException + * @param theServer the provider + * @param theException the exception to convert + * @return JaxRsResponseException + */ + public JaxRsResponseException convertException(final AbstractJaxRsProvider theServer, final Throwable theException) { + if (theServer.withStackTrace()) { + exceptionHandler.setReturnStackTracesForExceptionTypes(Throwable.class); + } + final JaxRsRequest requestDetails = theServer.getRequest(null, null).build(); + final BaseServerResponseException convertedException = preprocessException(theException, requestDetails); + return new JaxRsResponseException(convertedException); + } - private BaseServerResponseException preprocessException(final Exception 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); + } + } } diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsRequest.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsRequest.java index 46e0d3ecce7..57a914e6f9b 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsRequest.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsRequest.java @@ -64,6 +64,7 @@ public class JaxRsRequest extends RequestDetails { private String myVersion; private String myCompartment; private String myRequestUrl; + private final String myResourceName; /** * Utility Constructor @@ -73,11 +74,12 @@ public class JaxRsRequest extends RequestDetails { * @param theRequestUrl */ public Builder(AbstractJaxRsProvider theServer, RequestTypeEnum theRequestType, - RestOperationTypeEnum theRestOperation, String theRequestUrl) { + RestOperationTypeEnum theRestOperation, String theRequestUrl, String theResourceName) { this.myServer = theServer; this.myRequestType = theRequestType; this.myRestOperation = theRestOperation; this.myRequestUrl = theRequestUrl; + this.myResourceName = theResourceName; } /** @@ -125,44 +127,45 @@ public class JaxRsRequest extends RequestDetails { * @return the jax-rs request */ public JaxRsRequest build() { - JaxRsRequest result = new JaxRsRequest(myServer, myResource, myRequestType, myRestOperation); - if ((StringUtils.isNotBlank(myVersion) || StringUtils.isNotBlank(myCompartment)) - && StringUtils.isBlank(myId)) { - throw new InvalidRequestException("Don't know how to handle request path: " - + myServer.getUriInfo().getRequestUri().toASCIIString()); - } - - FhirVersionEnum fhirContextVersion = myServer.getFhirContext().getVersion().getVersion(); + JaxRsRequest result = new JaxRsRequest(myServer, myResource, myRequestType, myRestOperation); + if ((StringUtils.isNotBlank(myVersion) || StringUtils.isNotBlank(myCompartment)) + && StringUtils.isBlank(myId)) { + throw new InvalidRequestException("Don't know how to handle request path: " + + myServer.getUriInfo().getRequestUri().toASCIIString()); + } + + FhirVersionEnum fhirContextVersion = myServer.getFhirContext().getVersion().getVersion(); - if (StringUtils.isNotBlank(myVersion)) { - if (FhirVersionEnum.DSTU3.equals(fhirContextVersion)) { - result.setId( - new IdType(myServer.getBaseForRequest(), UrlUtil.unescape(myId), UrlUtil.unescape(myVersion))); - } else if (FhirVersionEnum.DSTU2.equals(fhirContextVersion)) { - result.setId( - new IdDt(myServer.getBaseForRequest(), UrlUtil.unescape(myId), UrlUtil.unescape(myVersion))); - } - } else if (StringUtils.isNotBlank(myId)) { - if (FhirVersionEnum.DSTU3.equals(fhirContextVersion)) { - result.setId(new IdType(myServer.getBaseForRequest(), UrlUtil.unescape(myId))); - } else if (FhirVersionEnum.DSTU2.equals(fhirContextVersion)) { - result.setId(new IdDt(myServer.getBaseForRequest(), UrlUtil.unescape(myId))); - } - } + if (StringUtils.isNotBlank(myVersion)) { + if (FhirVersionEnum.DSTU3.equals(fhirContextVersion)) { + result.setId( + new IdType(myServer.getBaseForRequest(), UrlUtil.unescape(myId), UrlUtil.unescape(myVersion))); + } else if (FhirVersionEnum.DSTU2.equals(fhirContextVersion)) { + result.setId( + new IdDt(myServer.getBaseForRequest(), UrlUtil.unescape(myId), UrlUtil.unescape(myVersion))); + } + } else if (StringUtils.isNotBlank(myId)) { + if (FhirVersionEnum.DSTU3.equals(fhirContextVersion)) { + result.setId(new IdType(myServer.getBaseForRequest(), UrlUtil.unescape(myId))); + } else if (FhirVersionEnum.DSTU2.equals(fhirContextVersion)) { + result.setId(new IdDt(myServer.getBaseForRequest(), UrlUtil.unescape(myId))); + } + } - if (myRestOperation == RestOperationTypeEnum.UPDATE) { - String contentLocation = result.getHeader(Constants.HEADER_CONTENT_LOCATION); - if (contentLocation != null) { - if (FhirVersionEnum.DSTU3.equals(fhirContextVersion)) { - result.setId(new IdType(contentLocation)); - } else if (FhirVersionEnum.DSTU2.equals(fhirContextVersion)) { - result.setId(new IdDt(contentLocation)); - } - } - } - - result.setCompartmentName(myCompartment); - result.setCompleteUrl(myRequestUrl); + if (myRestOperation == RestOperationTypeEnum.UPDATE) { + String contentLocation = result.getHeader(Constants.HEADER_CONTENT_LOCATION); + if (contentLocation != null) { + if (FhirVersionEnum.DSTU3.equals(fhirContextVersion)) { + result.setId(new IdType(contentLocation)); + } else if (FhirVersionEnum.DSTU2.equals(fhirContextVersion)) { + result.setId(new IdDt(contentLocation)); + } + } + } + + result.setCompartmentName(myCompartment); + result.setCompleteUrl(myRequestUrl); + result.setResourceName(myResourceName); return result; } diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsProviderMock.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsProviderMock.java new file mode 100644 index 00000000000..ff8e1586e1f --- /dev/null +++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsProviderMock.java @@ -0,0 +1,6 @@ +package ca.uhn.fhir.jaxrs.server; + +@SuppressWarnings("javadoc") +public class AbstractJaxRsProviderMock extends AbstractJaxRsProvider { + +} diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsProviderTest.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsProviderTest.java new file mode 100644 index 00000000000..5188d9f73a6 --- /dev/null +++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsProviderTest.java @@ -0,0 +1,86 @@ +package ca.uhn.fhir.jaxrs.server; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; + +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import ca.uhn.fhir.jaxrs.server.interceptor.JaxRsResponseException; +import ca.uhn.fhir.jaxrs.server.util.JaxRsRequest; +import ca.uhn.fhir.jaxrs.server.util.JaxRsResponse; +import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.parser.DataFormatException; +import ca.uhn.fhir.rest.server.Constants; +import ca.uhn.fhir.rest.server.IRestfulResponse; +import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; + +@SuppressWarnings("javadoc") +@RunWith(MockitoJUnitRunner.class) +public class AbstractJaxRsProviderTest { + + private AbstractJaxRsProviderMock provider; + @Mock + private JaxRsRequest theRequest; + + @Before + public void setUp() { + provider = new AbstractJaxRsProviderMock(); + final IRestfulResponse response = new JaxRsResponse(theRequest); + doReturn(provider).when(theRequest).getServer(); + doReturn(response).when(theRequest).getResponse(); + } + + @Test + public void testWithStackTrace() { + assertFalse(provider.withStackTrace()); + } + + @Test + public void testHandleExceptionJaxRsResponseException() throws IOException { + final ResourceNotFoundException base = new ResourceNotFoundException(new IdDt(1L)); + final JaxRsResponseException theException = new JaxRsResponseException(base); + final Response result = provider.handleException(theRequest, theException); + assertNotNull(result); + assertEquals(base.getStatusCode(), result.getStatus()); + } + + @Test + public void testHandleExceptionDataFormatException() throws IOException { + final DataFormatException theException = new DataFormatException(); + final Response result = provider.handleException(theRequest, theException); + assertNotNull(result); + assertEquals(Constants.STATUS_HTTP_400_BAD_REQUEST, result.getStatus()); + } + + @Test + public void testHandleExceptionRuntimeException() throws IOException, URISyntaxException { + final RuntimeException theException = new RuntimeException(); + final UriInfo mockUriInfo = mock(UriInfo.class); + final MultivaluedMap mockMap = mock(MultivaluedMap.class); + when(mockUriInfo.getBaseUri()).thenReturn(new URI("http://www.test.com")); + when(mockUriInfo.getRequestUri()).thenReturn(new URI("http://www.test.com/test")); + when(mockUriInfo.getQueryParameters()).thenReturn(mockMap); + + provider.setUriInfo(mockUriInfo); + final Response result = provider.handleException(theRequest, theException); + assertNotNull(result); + assertEquals(Constants.STATUS_HTTP_500_INTERNAL_ERROR, result.getStatus()); + } +} diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProviderDstu3Test.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProviderDstu3Test.java index 5eeb72ad589..75c740c1861 100644 --- a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProviderDstu3Test.java +++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProviderDstu3Test.java @@ -4,10 +4,12 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; + import static org.mockito.Matchers.any; import static org.mockito.Matchers.argThat; import static org.mockito.Matchers.eq; import static org.mockito.Matchers.isNull; + import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -78,6 +80,7 @@ public class AbstractJaxRsResourceProviderDstu3Test { private static Server jettyServer; private TestJaxRsMockPatientRestProviderDstu3 mock; private ArgumentCaptor idCaptor; + private ArgumentCaptor conditionalCaptor; private ArgumentCaptor patientCaptor; private void compareResultId(int id, IBaseResource resource) { @@ -132,6 +135,7 @@ public class AbstractJaxRsResourceProviderDstu3Test { this.mock = TestJaxRsMockPatientRestProviderDstu3.mock; idCaptor = ArgumentCaptor.forClass(IdType.class); patientCaptor = ArgumentCaptor.forClass(Patient.class); + conditionalCaptor = ArgumentCaptor.forClass(String.class); reset(mock); } @@ -179,10 +183,17 @@ public class AbstractJaxRsResourceProviderDstu3Test { @Test public void testDeletePatient() { - when(mock.delete(idCaptor.capture())).thenReturn(new MethodOutcome()); + when(mock.delete(idCaptor.capture(), conditionalCaptor.capture())).thenReturn(new MethodOutcome()); final BaseOperationOutcome results = client.delete().resourceById("Patient", "1").execute(); assertEquals("1", idCaptor.getValue().getIdPart()); } + + @Test + public void testConditionalDelete() throws Exception { + when(mock.delete(idCaptor.capture(), conditionalCaptor.capture())).thenReturn(new MethodOutcome()); + client.delete().resourceConditionalByType("Patient").where(Patient.IDENTIFIER.exactly().identifier("2")).execute(); + assertEquals("Patient?identifier=2&_format=json", conditionalCaptor.getValue()); + } /** Extended Operations */ @Test @@ -357,17 +368,27 @@ public class AbstractJaxRsResourceProviderDstu3Test { @Test public void testUpdateById() throws Exception { - when(mock.update(idCaptor.capture(), patientCaptor.capture())).thenReturn(new MethodOutcome()); + when(mock.update(idCaptor.capture(), patientCaptor.capture(), conditionalCaptor.capture())).thenReturn(new MethodOutcome()); client.update("1", createPatient(1)); assertEquals("1", idCaptor.getValue().getIdPart()); compareResultId(1, patientCaptor.getValue()); } + @Test + public void testConditionalUpdate() throws Exception { + when(mock.update(idCaptor.capture(), patientCaptor.capture(), conditionalCaptor.capture())).thenReturn(new MethodOutcome()); + client.update().resource(createPatient(1)).conditional().where(Patient.IDENTIFIER.exactly().identifier("2")).execute(); + + compareResultId(1, patientCaptor.getValue()); + assertEquals("Patient?identifier=2&_format=json", conditionalCaptor.getValue()); + compareResultId(1, patientCaptor.getValue()); + } + @SuppressWarnings("unchecked") @Ignore @Test public void testResourceNotFound() throws Exception { - when(mock.update(idCaptor.capture(), patientCaptor.capture())).thenThrow(ResourceNotFoundException.class); + when(mock.update(idCaptor.capture(), patientCaptor.capture(), conditionalCaptor.capture())).thenThrow(ResourceNotFoundException.class); try { client.update("1", createPatient(2)); fail(); diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProviderTest.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProviderTest.java index 3a26b8dd6ec..1aca9ae237b 100644 --- a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProviderTest.java +++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProviderTest.java @@ -80,6 +80,7 @@ public class AbstractJaxRsResourceProviderTest { private static Server jettyServer; private TestJaxRsMockPatientRestProvider mock; private ArgumentCaptor idCaptor; + private ArgumentCaptor conditionalCaptor; private ArgumentCaptor patientCaptor; private void compareResultId(int id, IResource resource) { @@ -133,6 +134,7 @@ public class AbstractJaxRsResourceProviderTest { this.mock = TestJaxRsMockPatientRestProvider.mock; idCaptor = ArgumentCaptor.forClass(IdDt.class); patientCaptor = ArgumentCaptor.forClass(Patient.class); + conditionalCaptor = ArgumentCaptor.forClass(String.class); reset(mock); } @@ -180,11 +182,18 @@ public class AbstractJaxRsResourceProviderTest { @Test public void testDeletePatient() { - when(mock.delete(idCaptor.capture())).thenReturn(new MethodOutcome()); + when(mock.delete(idCaptor.capture(), conditionalCaptor.capture())).thenReturn(new MethodOutcome()); final BaseOperationOutcome results = client.delete().resourceById("Patient", "1").execute(); assertEquals("1", idCaptor.getValue().getIdPart()); } + @Test + public void testConditionalDelete() throws Exception { + when(mock.delete(idCaptor.capture(), conditionalCaptor.capture())).thenReturn(new MethodOutcome()); + client.delete().resourceConditionalByType("Patient").where(Patient.IDENTIFIER.exactly().identifier("2")).execute(); + assertEquals("Patient?identifier=2&_format=json", conditionalCaptor.getValue()); + } + /** Extended Operations */ @Test public void testExtendedOperations() { @@ -344,17 +353,28 @@ public class AbstractJaxRsResourceProviderTest { @Test public void testUpdateById() throws Exception { - when(mock.update(idCaptor.capture(), patientCaptor.capture())).thenReturn(new MethodOutcome()); + when(mock.update(idCaptor.capture(), patientCaptor.capture(), conditionalCaptor.capture())).thenReturn(new MethodOutcome()); client.update("1", createPatient(1)); assertEquals("1", idCaptor.getValue().getIdPart()); compareResultId(1, patientCaptor.getValue()); } - + + + @Test + public void testConditionalUpdate() throws Exception { + when(mock.update(idCaptor.capture(), patientCaptor.capture(), conditionalCaptor.capture())).thenReturn(new MethodOutcome()); + client.update().resource(createPatient(1)).conditional().where(Patient.IDENTIFIER.exactly().identifier("2")).execute(); + + assertEquals("1", patientCaptor.getValue().getId().getIdPart()); + assertEquals("Patient?identifier=2&_format=json", conditionalCaptor.getValue()); + compareResultId(1, patientCaptor.getValue()); + } + @SuppressWarnings("unchecked") @Ignore @Test public void testResourceNotFound() throws Exception { - when(mock.update(idCaptor.capture(), patientCaptor.capture())).thenThrow(ResourceNotFoundException.class); + when(mock.update(idCaptor.capture(), patientCaptor.capture(), conditionalCaptor.capture())).thenThrow(ResourceNotFoundException.class); try { client.update("1", createPatient(2)); fail(); diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsMockPatientRestProvider.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsMockPatientRestProvider.java index 223bb928568..8181857cf1b 100644 --- a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsMockPatientRestProvider.java +++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsMockPatientRestProvider.java @@ -76,8 +76,8 @@ public class TestJaxRsMockPatientRestProvider extends AbstractJaxRsResourceProvi } @Update - public MethodOutcome update(@IdParam final IdDt theId, @ResourceParam final Patient patient) throws Exception { - return mock.update(theId, patient); + public MethodOutcome update(@IdParam final IdDt theId, @ResourceParam final Patient patient,@ConditionalUrlParam final String theConditional) throws Exception { + return mock.update(theId, patient, theConditional); } @Read @@ -97,8 +97,8 @@ public class TestJaxRsMockPatientRestProvider extends AbstractJaxRsResourceProvi } @Delete - public MethodOutcome delete(@IdParam final IdDt theId) { - return mock.delete(theId); + public MethodOutcome delete(@IdParam final IdDt theId, @ConditionalUrlParam final String theConditional) { + return mock.delete(theId, theConditional); } @Search(compartmentName = "Condition") diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsMockPatientRestProviderDstu3.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsMockPatientRestProviderDstu3.java index 1cf3caed218..2e0e1e07031 100644 --- a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsMockPatientRestProviderDstu3.java +++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsMockPatientRestProviderDstu3.java @@ -78,8 +78,8 @@ public class TestJaxRsMockPatientRestProviderDstu3 extends AbstractJaxRsResource } @Update - public MethodOutcome update(@IdParam final IdType theId, @ResourceParam final Patient patient) throws Exception { - return mock.update(theId, patient); + public MethodOutcome update(@IdParam final IdType theId, @ResourceParam final Patient patient,@ConditionalUrlParam final String theConditional) throws Exception { + return mock.update(theId, patient, theConditional); } @Read @@ -99,8 +99,8 @@ public class TestJaxRsMockPatientRestProviderDstu3 extends AbstractJaxRsResource } @Delete - public MethodOutcome delete(@IdParam final IdType theId) { - return mock.delete(theId); + public MethodOutcome delete(@IdParam final IdType theId, @ConditionalUrlParam final String theConditional) { + return mock.delete(theId, theConditional); } @Search(compartmentName = "Condition")