001package ca.uhn.fhir.jaxrs.server; 002 003/* 004 * #%L 005 * HAPI FHIR JAX-RS Server 006 * %% 007 * Copyright (C) 2014 - 2022 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}