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