package ca.uhn.fhir.jaxrs.server; /* * #%L * HAPI FHIR JAX-RS Server * %% * Copyright (C) 2014 - 2017 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.net.URL; import javax.interceptor.Interceptors; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; 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.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.IPagingProvider; import ca.uhn.fhir.rest.server.IResourceProvider; import ca.uhn.fhir.rest.server.IRestfulServer; /** * This server is the abstract superclass for all resource providers. It exposes * a large amount of the fhir api functionality using JAXRS * @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, Constants.CT_FHIR_JSON_NEW, Constants.CT_FHIR_XML_NEW }) @Interceptors(JaxRsExceptionInterceptor.class) public abstract class AbstractJaxRsResourceProvider<R extends IBaseResource> extends AbstractJaxRsProvider implements IRestfulServer<JaxRsRequest>, IResourceProvider { /** 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()); } /** * 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()); } /** * 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<? extends AbstractJaxRsProvider> 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(final FhirContext ctx, final Class<? extends AbstractJaxRsProvider> theProviderClass) { super(ctx); theBindings = JaxRsMethodBindings.getMethodBindings(this, theProviderClass); } /** * The base for request for a resource provider has the following form:</br> * {@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 <a href="https://www.hl7.org/fhir/http.html#create">https://www.hl7. org/fhir/http.html#create</a> */ @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 <a href="https://www.hl7.org/fhir/http.html#search">https://www.hl7.org/fhir/http.html#search</a> */ @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 <a href="https://www.hl7.org/fhir/http.html#search">https://www.hl7.org/fhir/http.html#search</a> */ @GET public Response search() throws IOException { return execute(getResourceRequest(RequestTypeEnum.GET, RestOperationTypeEnum.SEARCH_TYPE)); } /** * Update an existing resource based on the given condition * @param resource the body contents for the put method * @return the response * @see <a href="https://www.hl7.org/fhir/http.html#update">https://www.hl7.org/fhir/http.html#update</a> */ @PUT 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 <a href="https://www.hl7.org/fhir/http.html#update">https://www.hl7.org/fhir/http.html#update</a> */ @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 * * @return the response * @see <a href="https://www.hl7.org/fhir/http.html#delete">https://www.hl7.org/fhir/http.html#delete</a> */ @DELETE 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 <a href="https://www.hl7.org/fhir/http.html#delete">https://www.hl7.org/fhir/http.html#delete</a> */ @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 <a href="https://www.hl7.org/fhir/http.html#read">https://www.hl7.org/fhir/http.html#read</a> */ @GET @Path("/{id}") public Response find(@PathParam("id") final String id) throws IOException { return execute(getResourceRequest(RequestTypeEnum.GET, RestOperationTypeEnum.READ).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 <a href="https://www.hl7.org/fhir/operations.html">https://www.hl7.org/fhir/operations.html</a> */ 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); } /** * 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 <a href="https://www.hl7.org/fhir/http.html#history">https://www.hl7.org/fhir/http.html#history</a> */ @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); } /** * Compartment Based Access * * @param id the resource to which the compartment belongs * @param compartment the compartment * @return the repsonse * @see <a href="https://www.hl7.org/fhir/http.html#search">https://www.hl7.org/fhir/http.html#search</a> * @see <a href="https://www.hl7.org/fhir/compartments.html#compartment">https://www.hl7.org/fhir/compartments.html#compartment</a> */ @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); } @POST @Path("/$validate") public Response validate(final String resource) throws IOException { return customOperation(resource, RequestTypeEnum.POST, null, "$validate", RestOperationTypeEnum.EXTENDED_OPERATION_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: 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<R> 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()); } }