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}