
001/* 002 * #%L 003 * HAPI FHIR JAX-RS Server 004 * %% 005 * Copyright (C) 2014 - 2025 Smile CDR, Inc. 006 * %% 007 * Licensed under the Apache License, Version 2.0 (the "License"); 008 * you may not use this file except in compliance with the License. 009 * You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, software 014 * distributed under the License is distributed on an "AS IS" BASIS, 015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 * #L% 019 */ 020package ca.uhn.fhir.jaxrs.server.util; 021 022import ca.uhn.fhir.context.ConfigurationException; 023import ca.uhn.fhir.context.FhirContext; 024import ca.uhn.fhir.context.FhirVersionEnum; 025import ca.uhn.fhir.i18n.Msg; 026import ca.uhn.fhir.jaxrs.server.AbstractJaxRsProvider; 027import ca.uhn.fhir.rest.api.Constants; 028import ca.uhn.fhir.rest.api.RequestTypeEnum; 029import ca.uhn.fhir.rest.api.RestOperationTypeEnum; 030import ca.uhn.fhir.rest.api.server.IHasServletAttributes; 031import ca.uhn.fhir.rest.api.server.IRestfulResponse; 032import ca.uhn.fhir.rest.api.server.RequestDetails; 033import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; 034import ca.uhn.fhir.rest.server.method.ResourceParameter; 035import ca.uhn.fhir.util.UrlUtil; 036import jakarta.ws.rs.core.HttpHeaders; 037import jakarta.ws.rs.core.MediaType; 038import org.apache.commons.lang3.StringUtils; 039 040import java.io.InputStream; 041import java.io.Reader; 042import java.nio.charset.Charset; 043import java.util.Collections; 044import java.util.HashMap; 045import java.util.List; 046import java.util.Map; 047 048/** 049 * The JaxRsRequest is a jax-rs specific implementation of the RequestDetails. 050 * 051 * @author Peter Van Houte | peter.vanhoute@agfa.com | Agfa Healthcare 052 */ 053public class JaxRsRequest extends RequestDetails implements IHasServletAttributes { 054 055 private HttpHeaders myHeaders; 056 private String myResourceString; 057 private AbstractJaxRsProvider myServer; 058 private Map<String, Object> myAttributes = new HashMap<>(); 059 060 /** 061 * Utility Constructor 062 * 063 * @param server the server 064 * @param resourceString the resource body 065 * @param requestType the request type 066 * @param restOperation the operation type 067 */ 068 public JaxRsRequest( 069 AbstractJaxRsProvider server, 070 String resourceString, 071 RequestTypeEnum requestType, 072 RestOperationTypeEnum restOperation) { 073 super(server.getInterceptorService()); 074 this.myHeaders = server.getHeaders(); 075 this.myResourceString = resourceString; 076 this.setRestOperationType(restOperation); 077 setServer(server); 078 setFhirServerBase(server.getBaseForServer()); 079 setParameters(server.getParameters()); 080 setRequestType(requestType); 081 } 082 083 @Override 084 protected byte[] getByteStreamRequestContents() { 085 return StringUtils.defaultString(myResourceString, "") 086 .getBytes(ResourceParameter.determineRequestCharset(this)); 087 } 088 089 @Override 090 public Charset getCharset() { 091 String charset = null; 092 093 if (myHeaders.getMediaType() != null && myHeaders.getMediaType().getParameters() != null) { 094 charset = myHeaders.getMediaType().getParameters().get(MediaType.CHARSET_PARAMETER); 095 } 096 if (charset != null) { 097 return Charset.forName(charset); 098 } else { 099 return null; 100 } 101 } 102 103 @Override 104 public FhirContext getFhirContext() { 105 return myServer.getFhirContext(); 106 } 107 108 @Override 109 public String getHeader(String headerKey) { 110 List<String> requestHeader = getHeaders(headerKey); 111 return requestHeader.isEmpty() ? null : requestHeader.get(0); 112 } 113 114 @Override 115 public List<String> getHeaders(String name) { 116 List<String> requestHeader = myHeaders.getRequestHeader(name); 117 return requestHeader == null ? Collections.<String>emptyList() : requestHeader; 118 } 119 120 @Override 121 public void addHeader(String theName, String theValue) { 122 throw new UnsupportedOperationException(Msg.code(2499) + "Headers can not be modified in JAX-RS"); 123 } 124 125 @Override 126 public void setHeaders(String theName, List<String> theValue) { 127 throw new UnsupportedOperationException(Msg.code(2500) + "Headers can not be modified in JAX-RS"); 128 } 129 130 /** 131 * Gets an attribute from the servlet request. Attributes are used for interacting with servlet request 132 * attributes to communicate between servlet filters. These methods should not be used to pass information 133 * between interceptor methods. Use {@link #getUserData()} instead to pass information 134 * between interceptor methods. 135 * 136 * @param theAttributeName The attribute name 137 * @return The attribute value, or null if the attribute is not set 138 */ 139 @Override 140 public Object getServletAttribute(String theAttributeName) { 141 return myAttributes.get(theAttributeName); 142 } 143 144 /** 145 * Sets an attribute on the servlet request. Attributes are used for interacting with servlet request 146 * attributes to communicate between servlet filters. These methods should not be used to pass information 147 * between interceptor methods. Use {@link #getUserData()} instead to pass information 148 * between interceptor methods. 149 * 150 * @param theAttributeName The attribute name 151 * @param theAttributeValue The attribute value 152 */ 153 @Override 154 public void setServletAttribute(String theAttributeName, Object theAttributeValue) { 155 myAttributes.put(theAttributeName, theAttributeValue); 156 } 157 158 /** 159 * @deprecated Use {@link #getUserData()}. If servlet attributes are truly required, then use {@link IHasServletAttributes#getServletAttribute(String)}. 160 */ 161 @Deprecated 162 @Override 163 public Object getAttribute(String theAttributeName) { 164 return getServletAttribute(theAttributeName); 165 } 166 167 /** 168 * @deprecated Use {@link #getUserData()}. If servlet attributes are truly required, then use {@link IHasServletAttributes#setServletAttribute(String, Object)}. 169 */ 170 @Deprecated 171 @Override 172 public void setAttribute(String theAttributeName, Object theAttributeValue) { 173 setServletAttribute(theAttributeName, theAttributeValue); 174 } 175 176 @Override 177 public InputStream getInputStream() { 178 // not yet implemented 179 throw new UnsupportedOperationException(Msg.code(599)); 180 } 181 182 @Override 183 public Reader getReader() { 184 // not yet implemented 185 throw new UnsupportedOperationException(Msg.code(600)); 186 } 187 188 @Override 189 public IRestfulResponse getResponse() { 190 if (super.getResponse() == null) { 191 setResponse(new JaxRsResponse(this)); 192 } 193 return super.getResponse(); 194 } 195 196 @Override 197 public AbstractJaxRsProvider getServer() { 198 return myServer; 199 } 200 201 /** 202 * Set the server 203 * 204 * @param theServer the server to set 205 */ 206 public void setServer(AbstractJaxRsProvider theServer) { 207 this.myServer = theServer; 208 } 209 210 @Override 211 public String getServerBaseForRequest() { 212 return getServer().getServerAddressStrategy().determineServerBase(null, null); 213 } 214 215 /** 216 * An implementation of the builder pattern for the JaxRsRequest 217 */ 218 public static class Builder { 219 private final String myResourceName; 220 private final RequestTypeEnum myRequestType; 221 private final String myRequestUrl; 222 private final RestOperationTypeEnum myRestOperation; 223 private final AbstractJaxRsProvider myServer; 224 225 private String myResource; 226 private String myCompartment; 227 private String myId; 228 private String myVersion; 229 230 /** 231 * Utility Constructor 232 * 233 * @param theServer the server 234 * @param theRequestType the request type 235 * @param theRestOperation the rest operation 236 * @param theRequestUrl the request url 237 */ 238 public Builder( 239 AbstractJaxRsProvider theServer, 240 RequestTypeEnum theRequestType, 241 RestOperationTypeEnum theRestOperation, 242 String theRequestUrl, 243 String theResourceName) { 244 this.myServer = theServer; 245 this.myRequestType = theRequestType; 246 this.myRestOperation = theRestOperation; 247 this.myRequestUrl = theRequestUrl; 248 this.myResourceName = theResourceName; 249 } 250 251 /** 252 * Create the jax-rs request 253 * 254 * @return the jax-rs request 255 */ 256 public JaxRsRequest build() { 257 JaxRsRequest result = new JaxRsRequest(myServer, myResource, myRequestType, myRestOperation); 258 if ((StringUtils.isNotBlank(myVersion) || StringUtils.isNotBlank(myCompartment)) 259 && StringUtils.isBlank(myId)) { 260 throw new InvalidRequestException(Msg.code(601) + "Don't know how to handle request path: " 261 + myServer.getUriInfo().getRequestUri().toASCIIString()); 262 } 263 264 FhirVersionEnum fhirContextVersion = 265 myServer.getFhirContext().getVersion().getVersion(); 266 267 if (StringUtils.isNotBlank(myVersion)) { 268 switch (fhirContextVersion) { 269 case R4: 270 result.setId(new org.hl7.fhir.r4.model.IdType( 271 myServer.getBaseForRequest(), UrlUtil.unescape(myId), UrlUtil.unescape(myVersion))); 272 break; 273 case DSTU3: 274 result.setId(new org.hl7.fhir.dstu3.model.IdType( 275 myServer.getBaseForRequest(), UrlUtil.unescape(myId), UrlUtil.unescape(myVersion))); 276 break; 277 case DSTU2_1: 278 result.setId(new org.hl7.fhir.dstu2016may.model.IdType( 279 myServer.getBaseForRequest(), UrlUtil.unescape(myId), UrlUtil.unescape(myVersion))); 280 break; 281 case DSTU2_HL7ORG: 282 result.setId(new org.hl7.fhir.dstu2.model.IdType( 283 myServer.getBaseForRequest(), UrlUtil.unescape(myId), UrlUtil.unescape(myVersion))); 284 break; 285 case DSTU2: 286 result.setId(new ca.uhn.fhir.model.primitive.IdDt( 287 myServer.getBaseForRequest(), UrlUtil.unescape(myId), UrlUtil.unescape(myVersion))); 288 break; 289 default: 290 throw new ConfigurationException( 291 Msg.code(602) + "Unsupported Fhir version: " + fhirContextVersion); 292 } 293 } else if (StringUtils.isNotBlank(myId)) { 294 switch (fhirContextVersion) { 295 case R4: 296 result.setId( 297 new org.hl7.fhir.r4.model.IdType(myServer.getBaseForRequest(), UrlUtil.unescape(myId))); 298 break; 299 case DSTU3: 300 result.setId(new org.hl7.fhir.dstu3.model.IdType( 301 myServer.getBaseForRequest(), UrlUtil.unescape(myId))); 302 break; 303 case DSTU2_1: 304 result.setId(new org.hl7.fhir.dstu2016may.model.IdType( 305 myServer.getBaseForRequest(), UrlUtil.unescape(myId))); 306 break; 307 case DSTU2_HL7ORG: 308 result.setId(new org.hl7.fhir.dstu2.model.IdType( 309 myServer.getBaseForRequest(), UrlUtil.unescape(myId))); 310 break; 311 case DSTU2: 312 result.setId(new ca.uhn.fhir.model.primitive.IdDt( 313 myServer.getBaseForRequest(), UrlUtil.unescape(myId))); 314 break; 315 default: 316 throw new ConfigurationException( 317 Msg.code(603) + "Unsupported Fhir version: " + fhirContextVersion); 318 } 319 } 320 321 if (myRestOperation == RestOperationTypeEnum.UPDATE) { 322 String contentLocation = result.getHeader(Constants.HEADER_CONTENT_LOCATION); 323 if (contentLocation != null) { 324 switch (fhirContextVersion) { 325 case R4: 326 result.setId(new org.hl7.fhir.r4.model.IdType(contentLocation)); 327 break; 328 case DSTU3: 329 result.setId(new org.hl7.fhir.dstu3.model.IdType(contentLocation)); 330 break; 331 case DSTU2_1: 332 result.setId(new org.hl7.fhir.dstu2016may.model.IdType(contentLocation)); 333 break; 334 case DSTU2_HL7ORG: 335 result.setId(new org.hl7.fhir.dstu2.model.IdType(contentLocation)); 336 break; 337 case DSTU2: 338 result.setId(new ca.uhn.fhir.model.primitive.IdDt(contentLocation)); 339 break; 340 default: 341 throw new ConfigurationException( 342 Msg.code(604) + "Unsupported Fhir version: " + fhirContextVersion); 343 } 344 } 345 } 346 347 result.setCompartmentName(myCompartment); 348 result.setCompleteUrl(myRequestUrl); 349 result.setResourceName(myResourceName); 350 351 return result; 352 } 353 354 /** 355 * Set the compartment 356 * 357 * @param compartment the compartment 358 * @return the builder 359 */ 360 public Builder compartment(String compartment) { 361 this.myCompartment = compartment; 362 return this; 363 } 364 365 /** 366 * Set the id 367 * 368 * @param id the resource id 369 * @return the builder 370 */ 371 public Builder id(String id) { 372 this.myId = id; 373 return this; 374 } 375 376 /** 377 * Set the resource 378 * 379 * @param resource the body contents of an http method 380 * @return the builder 381 */ 382 public Builder resource(String resource) { 383 this.myResource = resource; 384 return this; 385 } 386 387 /** 388 * Set the id version 389 * 390 * @param version the version of the resource 391 * @return the builder 392 */ 393 public Builder version(String version) { 394 this.myVersion = version; 395 return this; 396 } 397 } 398}