001/* 002 * #%L 003 * HAPI FHIR JAX-RS Server 004 * %% 005 * Copyright (C) 2014 - 2024 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.IRestfulResponse; 031import ca.uhn.fhir.rest.api.server.RequestDetails; 032import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; 033import ca.uhn.fhir.rest.server.method.ResourceParameter; 034import ca.uhn.fhir.util.UrlUtil; 035import jakarta.ws.rs.core.HttpHeaders; 036import jakarta.ws.rs.core.MediaType; 037import org.apache.commons.lang3.StringUtils; 038 039import java.io.IOException; 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 { 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 @Override 131 public Object getAttribute(String theAttributeName) { 132 return myAttributes.get(theAttributeName); 133 } 134 135 @Override 136 public void setAttribute(String theAttributeName, Object theAttributeValue) { 137 myAttributes.put(theAttributeName, theAttributeValue); 138 } 139 140 @Override 141 public InputStream getInputStream() { 142 // not yet implemented 143 throw new UnsupportedOperationException(Msg.code(599)); 144 } 145 146 @Override 147 public Reader getReader() throws IOException { 148 // not yet implemented 149 throw new UnsupportedOperationException(Msg.code(600)); 150 } 151 152 @Override 153 public IRestfulResponse getResponse() { 154 if (super.getResponse() == null) { 155 setResponse(new JaxRsResponse(this)); 156 } 157 return super.getResponse(); 158 } 159 160 @Override 161 public AbstractJaxRsProvider getServer() { 162 return myServer; 163 } 164 165 /** 166 * Set the server 167 * 168 * @param theServer the server to set 169 */ 170 public void setServer(AbstractJaxRsProvider theServer) { 171 this.myServer = theServer; 172 } 173 174 @Override 175 public String getServerBaseForRequest() { 176 return getServer().getServerAddressStrategy().determineServerBase(null, null); 177 } 178 179 /** 180 * An implementation of the builder pattern for the JaxRsRequest 181 */ 182 public static class Builder { 183 private final String myResourceName; 184 private String myCompartment; 185 private String myId; 186 private RequestTypeEnum myRequestType; 187 private String myRequestUrl; 188 private String myResource; 189 private RestOperationTypeEnum myRestOperation; 190 private AbstractJaxRsProvider myServer; 191 private String myVersion; 192 193 /** 194 * Utility Constructor 195 * 196 * @param theServer the server 197 * @param theRequestType the request type 198 * @param theRestOperation the rest operation 199 * @param theRequestUrl 200 */ 201 public Builder( 202 AbstractJaxRsProvider theServer, 203 RequestTypeEnum theRequestType, 204 RestOperationTypeEnum theRestOperation, 205 String theRequestUrl, 206 String theResourceName) { 207 this.myServer = theServer; 208 this.myRequestType = theRequestType; 209 this.myRestOperation = theRestOperation; 210 this.myRequestUrl = theRequestUrl; 211 this.myResourceName = theResourceName; 212 } 213 214 /** 215 * Create the jax-rs request 216 * 217 * @return the jax-rs request 218 */ 219 public JaxRsRequest build() { 220 JaxRsRequest result = new JaxRsRequest(myServer, myResource, myRequestType, myRestOperation); 221 if ((StringUtils.isNotBlank(myVersion) || StringUtils.isNotBlank(myCompartment)) 222 && StringUtils.isBlank(myId)) { 223 throw new InvalidRequestException(Msg.code(601) + "Don't know how to handle request path: " 224 + myServer.getUriInfo().getRequestUri().toASCIIString()); 225 } 226 227 FhirVersionEnum fhirContextVersion = 228 myServer.getFhirContext().getVersion().getVersion(); 229 230 if (StringUtils.isNotBlank(myVersion)) { 231 switch (fhirContextVersion) { 232 case R4: 233 result.setId(new org.hl7.fhir.r4.model.IdType( 234 myServer.getBaseForRequest(), UrlUtil.unescape(myId), UrlUtil.unescape(myVersion))); 235 break; 236 case DSTU3: 237 result.setId(new org.hl7.fhir.dstu3.model.IdType( 238 myServer.getBaseForRequest(), UrlUtil.unescape(myId), UrlUtil.unescape(myVersion))); 239 break; 240 case DSTU2_1: 241 result.setId(new org.hl7.fhir.dstu2016may.model.IdType( 242 myServer.getBaseForRequest(), UrlUtil.unescape(myId), UrlUtil.unescape(myVersion))); 243 break; 244 case DSTU2_HL7ORG: 245 result.setId(new org.hl7.fhir.dstu2.model.IdType( 246 myServer.getBaseForRequest(), UrlUtil.unescape(myId), UrlUtil.unescape(myVersion))); 247 break; 248 case DSTU2: 249 result.setId(new ca.uhn.fhir.model.primitive.IdDt( 250 myServer.getBaseForRequest(), UrlUtil.unescape(myId), UrlUtil.unescape(myVersion))); 251 break; 252 default: 253 throw new ConfigurationException( 254 Msg.code(602) + "Unsupported Fhir version: " + fhirContextVersion); 255 } 256 } else if (StringUtils.isNotBlank(myId)) { 257 switch (fhirContextVersion) { 258 case R4: 259 result.setId( 260 new org.hl7.fhir.r4.model.IdType(myServer.getBaseForRequest(), UrlUtil.unescape(myId))); 261 break; 262 case DSTU3: 263 result.setId(new org.hl7.fhir.dstu3.model.IdType( 264 myServer.getBaseForRequest(), UrlUtil.unescape(myId))); 265 break; 266 case DSTU2_1: 267 result.setId(new org.hl7.fhir.dstu2016may.model.IdType( 268 myServer.getBaseForRequest(), UrlUtil.unescape(myId))); 269 break; 270 case DSTU2_HL7ORG: 271 result.setId(new org.hl7.fhir.dstu2.model.IdType( 272 myServer.getBaseForRequest(), UrlUtil.unescape(myId))); 273 break; 274 case DSTU2: 275 result.setId(new ca.uhn.fhir.model.primitive.IdDt( 276 myServer.getBaseForRequest(), UrlUtil.unescape(myId))); 277 break; 278 default: 279 throw new ConfigurationException( 280 Msg.code(603) + "Unsupported Fhir version: " + fhirContextVersion); 281 } 282 } 283 284 if (myRestOperation == RestOperationTypeEnum.UPDATE) { 285 String contentLocation = result.getHeader(Constants.HEADER_CONTENT_LOCATION); 286 if (contentLocation != null) { 287 switch (fhirContextVersion) { 288 case R4: 289 result.setId(new org.hl7.fhir.r4.model.IdType(contentLocation)); 290 break; 291 case DSTU3: 292 result.setId(new org.hl7.fhir.dstu3.model.IdType(contentLocation)); 293 break; 294 case DSTU2_1: 295 result.setId(new org.hl7.fhir.dstu2016may.model.IdType(contentLocation)); 296 break; 297 case DSTU2_HL7ORG: 298 result.setId(new org.hl7.fhir.dstu2.model.IdType(contentLocation)); 299 break; 300 case DSTU2: 301 result.setId(new ca.uhn.fhir.model.primitive.IdDt(contentLocation)); 302 break; 303 default: 304 throw new ConfigurationException( 305 Msg.code(604) + "Unsupported Fhir version: " + fhirContextVersion); 306 } 307 } 308 } 309 310 result.setCompartmentName(myCompartment); 311 result.setCompleteUrl(myRequestUrl); 312 result.setResourceName(myResourceName); 313 314 return result; 315 } 316 317 /** 318 * Set the compartment 319 * 320 * @param compartment the compartment 321 * @return the builder 322 */ 323 public Builder compartment(String compartment) { 324 this.myCompartment = compartment; 325 return this; 326 } 327 328 /** 329 * Set the id 330 * 331 * @param id the resource id 332 * @return the builder 333 */ 334 public Builder id(String id) { 335 this.myId = id; 336 return this; 337 } 338 339 /** 340 * Set the resource 341 * 342 * @param resource the body contents of an http method 343 * @return the builder 344 */ 345 public Builder resource(String resource) { 346 this.myResource = resource; 347 return this; 348 } 349 350 /** 351 * Set the id version 352 * 353 * @param version the version of the resource 354 * @return the builder 355 */ 356 public Builder version(String version) { 357 this.myVersion = version; 358 return this; 359 } 360 } 361}