001package ca.uhn.fhir.jaxrs.server;
002
003/*
004 * #%L
005 * HAPI FHIR JAX-RS Server
006 * %%
007 * Copyright (C) 2014 - 2019 University Health Network
008 * %%
009 * Licensed under the Apache License, Version 2.0 (the "License");
010 * you may not use this file except in compliance with the License.
011 * You may obtain a copy of the License at
012 * 
013 * http://www.apache.org/licenses/LICENSE-2.0
014 * 
015 * Unless required by applicable law or agreed to in writing, software
016 * distributed under the License is distributed on an "AS IS" BASIS,
017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018 * See the License for the specific language governing permissions and
019 * limitations under the License.
020 * #L%
021 */
022
023import java.io.IOException;
024import java.net.URL;
025
026import javax.interceptor.Interceptors;
027import javax.ws.rs.*;
028import javax.ws.rs.core.MediaType;
029import javax.ws.rs.core.Response;
030
031import ca.uhn.fhir.interceptor.api.IInterceptorService;
032import org.hl7.fhir.instance.model.api.IBaseResource;
033
034import ca.uhn.fhir.context.FhirContext;
035import ca.uhn.fhir.context.api.BundleInclusionRule;
036import ca.uhn.fhir.jaxrs.server.interceptor.JaxRsExceptionInterceptor;
037import ca.uhn.fhir.jaxrs.server.util.JaxRsMethodBindings;
038import ca.uhn.fhir.jaxrs.server.util.JaxRsRequest;
039import ca.uhn.fhir.jaxrs.server.util.JaxRsRequest.Builder;
040import ca.uhn.fhir.rest.api.*;
041import ca.uhn.fhir.rest.api.server.IRestfulServer;
042import ca.uhn.fhir.rest.server.IPagingProvider;
043import ca.uhn.fhir.rest.server.IResourceProvider;
044import ca.uhn.fhir.rest.server.method.BaseMethodBinding;
045
046/**
047 * This server is the abstract superclass for all resource providers. It exposes
048 * a large amount of the fhir api functionality using JAXRS
049 * @author Peter Van Houte | peter.vanhoute@agfa.com | Agfa Healthcare
050 */
051@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN, Constants.CT_FHIR_JSON, Constants.CT_FHIR_XML })
052@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, "application/octet-stream" })
053@Interceptors(JaxRsExceptionInterceptor.class)
054public abstract class AbstractJaxRsResourceProvider<R extends IBaseResource> extends AbstractJaxRsProvider
055
056implements IRestfulServer<JaxRsRequest>, IResourceProvider {
057
058    /** the method bindings for this class */
059    private final JaxRsMethodBindings theBindings;
060
061    /**
062     * The default constructor. The method bindings are retrieved from the class
063     * being constructed.
064     */
065    protected AbstractJaxRsResourceProvider() {
066        super();
067        theBindings = JaxRsMethodBindings.getMethodBindings(this, getClass());
068    }
069
070    /**
071     * Provides the ability to specify the {@link FhirContext}.
072     * @param ctx the {@link FhirContext} instance.
073     */
074    protected AbstractJaxRsResourceProvider(final FhirContext ctx) {
075        super(ctx);
076        theBindings = JaxRsMethodBindings.getMethodBindings(this, getClass());
077    }
078
079    /**
080     * This constructor takes in an explicit interface class. This subclass
081     * should be identical to the class being constructed but is given
082     * explicitly in order to avoid issues with proxy classes in a jee
083     * environment.
084     * 
085     * @param theProviderClass the interface of the class
086     */
087    protected AbstractJaxRsResourceProvider(final Class<? extends AbstractJaxRsProvider> theProviderClass) {
088        super();
089        theBindings = JaxRsMethodBindings.getMethodBindings(this, theProviderClass);
090    }
091
092    /**
093     * This constructor takes in an explicit interface class. This subclass
094     * should be identical to the class being constructed but is given
095     * explicitly in order to avoid issues with proxy classes in a jee
096     * environment.
097     *
098     * @param ctx the {@link FhirContext} instance.
099     * @param theProviderClass the interface of the class
100     */
101    protected AbstractJaxRsResourceProvider(final FhirContext ctx, final Class<? extends AbstractJaxRsProvider> theProviderClass) {
102        super(ctx);
103        theBindings = JaxRsMethodBindings.getMethodBindings(this, theProviderClass);
104    }
105
106        /**
107     * The base for request for a resource provider has the following form:</br>
108     * {@link AbstractJaxRsResourceProvider#getBaseForServer()
109     * getBaseForServer()} + "/" +
110     * {@link AbstractJaxRsResourceProvider#getResourceType() getResourceType()}
111     * .{@link java.lang.Class#getSimpleName() getSimpleName()}
112     */
113    @Override
114    public String getBaseForRequest() {
115        try {
116            return new URL(getUriInfo().getBaseUri().toURL(), getResourceType().getSimpleName()).toExternalForm();
117        }
118        catch (final Exception e) {
119            // cannot happen
120            return null;
121        }
122    }
123
124    /**
125     * Create a new resource with a server assigned id
126     * 
127     * @param resource the body of the post method containing resource being created in a xml/json form
128     * @return the response
129     * @see <a href="https://www.hl7.org/fhir/http.html#create">https://www.hl7. org/fhir/http.html#create</a>
130     */
131    @POST
132    public Response create(final String resource)
133            throws IOException {
134        return execute(getResourceRequest(RequestTypeEnum.POST, RestOperationTypeEnum.CREATE).resource(resource));
135    }
136
137    /**
138     * Search the resource type based on some filter criteria
139     * 
140     * @return the response
141     * @see <a href="https://www.hl7.org/fhir/http.html#search">https://www.hl7.org/fhir/http.html#search</a>
142     */
143    @POST
144    @Path("/_search")
145    public Response searchWithPost()
146            throws IOException {
147        return execute(getResourceRequest(RequestTypeEnum.POST, RestOperationTypeEnum.SEARCH_TYPE));
148    }
149
150    /**
151     * Search the resource type based on some filter criteria
152     * 
153     * @return the response
154     * @see <a href="https://www.hl7.org/fhir/http.html#search">https://www.hl7.org/fhir/http.html#search</a>
155     */
156    @GET
157    public Response search()
158            throws IOException {
159        return execute(getResourceRequest(RequestTypeEnum.GET, RestOperationTypeEnum.SEARCH_TYPE));
160    }
161
162    /**
163     * Update an existing resource based on the given condition
164     * @param resource the body contents for the put method
165     * @return the response
166     * @see <a href="https://www.hl7.org/fhir/http.html#update">https://www.hl7.org/fhir/http.html#update</a>
167     */
168    @PUT
169    public Response conditionalUpdate(final String resource)
170            throws IOException {
171        return execute(getResourceRequest(RequestTypeEnum.PUT, RestOperationTypeEnum.UPDATE).resource(resource));
172    }
173
174    /**
175     * Update an existing resource by its id (or create it if it is new)
176     * 
177     * @param id the id of the resource
178     * @param resource the body contents for the put method
179     * @return the response
180     * @see <a href="https://www.hl7.org/fhir/http.html#update">https://www.hl7.org/fhir/http.html#update</a>
181     */
182    @PUT
183    @Path("/{id}")
184    public Response update(@PathParam("id") final String id, final String resource)
185            throws IOException {
186        return execute(getResourceRequest(RequestTypeEnum.PUT, RestOperationTypeEnum.UPDATE).id(id).resource(resource));
187    }
188
189    /**
190     * Delete a resource based on the given condition
191     *
192     * @return the response
193     * @see <a href="https://www.hl7.org/fhir/http.html#delete">https://www.hl7.org/fhir/http.html#delete</a>
194     */
195    @DELETE
196    public Response delete()
197            throws IOException {
198        return execute(getResourceRequest(RequestTypeEnum.DELETE, RestOperationTypeEnum.DELETE));
199    }
200
201    /**
202     * Delete a resource
203     * 
204     * @param id the id of the resource to delete
205     * @return the response
206     * @see <a href="https://www.hl7.org/fhir/http.html#delete">https://www.hl7.org/fhir/http.html#delete</a>
207     */
208    @DELETE
209    @Path("/{id}")
210    public Response delete(@PathParam("id") final String id)
211            throws IOException {
212        return execute(getResourceRequest(RequestTypeEnum.DELETE, RestOperationTypeEnum.DELETE).id(id));
213    }
214
215    /**
216     * Read the current state of the resource
217     * 
218     * @param id the id of the resource to read
219     * @return the response
220     * @see <a href="https://www.hl7.org/fhir/http.html#read">https://www.hl7.org/fhir/http.html#read</a>
221     */
222    @GET
223    @Path("/{id}")
224    public Response find(@PathParam("id") final String id)
225            throws IOException {
226        return execute(getResourceRequest(RequestTypeEnum.GET, RestOperationTypeEnum.READ).id(id));
227    }
228
229    /**
230     * Execute a custom operation
231     * 
232     * @param resource the resource to create
233     * @param requestType the type of request
234     * @param id the id of the resource on which to perform the operation
235     * @param operationName the name of the operation to execute
236     * @param operationType the rest operation type
237     * @return the response
238     * @see <a href="https://www.hl7.org/fhir/operations.html">https://www.hl7.org/fhir/operations.html</a>
239     */
240    protected Response customOperation(final String resource, final RequestTypeEnum requestType, final String id,
241            final String operationName, final RestOperationTypeEnum operationType)
242            throws IOException {
243        final Builder request = getResourceRequest(requestType, operationType).resource(resource).id(id);
244        return execute(request, operationName);
245    }
246
247    /**
248     * Retrieve the update history for a particular resource
249     * 
250     * @param id the id of the resource
251     * @param version the version of the resource
252     * @return the response
253     * @see <a href="https://www.hl7.org/fhir/http.html#history">https://www.hl7.org/fhir/http.html#history</a>
254     */
255    @GET
256    @Path("/{id}/_history/{version}")
257    public Response findHistory(@PathParam("id") final String id, @PathParam("version") final String version)
258            throws IOException {
259        final Builder theRequest = getResourceRequest(RequestTypeEnum.GET, RestOperationTypeEnum.VREAD).id(id).version(version);
260        return execute(theRequest);
261    }
262
263    /**
264     * Compartment Based Access
265     * 
266     * @param id the resource to which the compartment belongs
267     * @param compartment the compartment
268     * @return the repsonse 
269     * @see <a href="https://www.hl7.org/fhir/http.html#search">https://www.hl7.org/fhir/http.html#search</a>
270     * @see <a href="https://www.hl7.org/fhir/compartments.html#compartment">https://www.hl7.org/fhir/compartments.html#compartment</a>
271     */
272    @GET
273    @Path("/{id}/{compartment}")
274    public Response findCompartment(@PathParam("id") final String id, @PathParam("compartment") final String compartment)
275            throws IOException {
276        final Builder theRequest = getResourceRequest(RequestTypeEnum.GET, RestOperationTypeEnum.SEARCH_TYPE).id(id).compartment(
277                compartment);
278        return execute(theRequest, compartment);
279    }
280
281    @POST
282    @Path("/$validate")
283    public Response validate(final String resource) throws IOException {
284        return customOperation(resource, RequestTypeEnum.POST, null, "$validate", RestOperationTypeEnum.EXTENDED_OPERATION_TYPE);
285    }
286    
287    /**
288     * Execute the method described by the requestBuilder and methodKey
289     * 
290     * @param theRequestBuilder the requestBuilder that contains the information about the request
291     * @param methodKey the key determining the method to be executed
292     * @return the response
293     */
294    private Response execute(final Builder theRequestBuilder, final String methodKey)
295            throws IOException {
296        final JaxRsRequest theRequest = theRequestBuilder.build();
297        final BaseMethodBinding<?> method = getBinding(theRequest.getRestOperationType(), methodKey);
298        try {
299            return (Response) method.invokeServer(this, theRequest);
300        }
301        catch (final Throwable theException) {
302            return handleException(theRequest, theException);
303        }
304    }
305
306    /**
307     * Execute the method described by the requestBuilder
308     * 
309     * @param theRequestBuilder the requestBuilder that contains the information about the request
310     * @return the response
311     */
312    private Response execute(final Builder theRequestBuilder)
313            throws IOException {
314        return execute(theRequestBuilder, JaxRsMethodBindings.DEFAULT_METHOD_KEY);
315    }
316
317    /**
318     * Return the method binding for the given rest operation
319     * 
320     * @param restOperation the rest operation to retrieve
321     * @param theBindingKey the key determining the method to be executed (needed for e.g. custom operation)
322     * @return
323     */
324    protected BaseMethodBinding<?> getBinding(final RestOperationTypeEnum restOperation, final String theBindingKey) {
325        return getBindings().getBinding(restOperation, theBindingKey);
326    }
327
328    /**
329     * Default: no paging provider
330     */
331    @Override
332    public IPagingProvider getPagingProvider() {
333        return null;
334    }
335
336    /**
337     * Default: BundleInclusionRule.BASED_ON_INCLUDES
338     */
339    @Override
340    public BundleInclusionRule getBundleInclusionRule() {
341        return BundleInclusionRule.BASED_ON_INCLUDES;
342    }
343
344        @Override
345        public PreferReturnEnum getDefaultPreferReturn() {
346                return PreferReturnEnum.REPRESENTATION;
347        }
348
349        /**
350     * The resource type should return conform to the generic resource included
351     * in the topic
352     */
353    @Override
354    public abstract Class<R> getResourceType();
355
356    /**
357     * Return the bindings defined in this resource provider
358     * 
359     * @return the jax-rs method bindings
360     */
361    public JaxRsMethodBindings getBindings() {
362        return theBindings;
363    }
364
365    /**
366     * Return the request builder based on the resource name for the server
367     * @param requestType the type of the request
368     * @param restOperation the rest operation type
369     * @return the requestbuilder
370     */
371    private Builder getResourceRequest(final RequestTypeEnum requestType, final RestOperationTypeEnum restOperation) {
372        return getRequest(requestType, restOperation, getResourceType().getSimpleName());
373    }
374}