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}