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