
001/* 002 * #%L 003 * HAPI FHIR - Server Framework 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.rest.server.servlet; 021 022import com.google.common.collect.ListMultimap; 023import com.google.common.collect.MultimapBuilder; 024import jakarta.annotation.Nonnull; 025import jakarta.servlet.http.HttpServletRequest; 026import jakarta.servlet.http.HttpServletResponse; 027 028import java.util.List; 029import java.util.Map; 030 031/** 032 * This class wraps a {@link ServletRequestDetails} object for 033 * processing sub-requests, such as processing individual 034 * entries in a transaction or batch bundle. An instance of this class is used for modifying some of the data 035 * in the request details, such as the request headers, for an individual entry, 036 * without affecting the original ServletRequestDetails. 037 */ 038public class ServletSubRequestDetails extends ServletRequestDetails { 039 040 private final ServletRequestDetails myWrap; 041 042 /** 043 * Map with case-insensitive keys 044 * This map contains only the headers modified by the user after this object is created. 045 * If a header is not modified, the original value from the wrapped RequestDetails is returned by the 046 * getters in this class. 047 * <p> 048 * The reason for implementing the map this way, which is just keeping track of the overrides, as opposed 049 * to creating a copy of the header map of the wrapped RequestDetails at the time this object is created, 050 * is that there some test code where the header values are stubbed for the wrapped details using Mockito 051 * like `when(requestDetails.getHeader("headerName")).thenReturn("headerValue")`. 052 * Creating a copy of the headers by iterating the map of the wrapped instance wouldn't satisfy such stubbing, 053 * the stubbed values are not actually in the map. 054 * For stubbing to work we have to make a call the getHeader method of the wrapped RequestDetails. 055 * This is what the getters in this class do. 056 */ 057 private final ListMultimap<String, String> myHeaderOverrides = MultimapBuilder.treeKeys( 058 String.CASE_INSENSITIVE_ORDER) 059 .arrayListValues() 060 .build(); 061 062 /** 063 * Constructor 064 * 065 * @param theRequestDetails The parent request details 066 */ 067 public ServletSubRequestDetails(@Nonnull ServletRequestDetails theRequestDetails) { 068 super(theRequestDetails.getInterceptorBroadcaster()); 069 myWrap = theRequestDetails; 070 } 071 072 @Override 073 public HttpServletRequest getServletRequest() { 074 return myWrap.getServletRequest(); 075 } 076 077 @Override 078 public HttpServletResponse getServletResponse() { 079 return myWrap.getServletResponse(); 080 } 081 082 @Override 083 public void addHeader(String theName, String theValue) { 084 myHeaderOverrides.put(theName, theValue); 085 } 086 087 @Override 088 public String getHeader(String theName) { 089 List<String> list = myHeaderOverrides.get(theName); 090 if (list.isEmpty()) { 091 return myWrap.getHeader(theName); 092 } 093 return list.get(0); 094 } 095 096 @Override 097 public List<String> getHeaders(String theName) { 098 List<String> list = myHeaderOverrides.get(theName.toLowerCase()); 099 if (list.isEmpty()) { 100 return myWrap.getHeaders(theName); 101 } 102 return list; 103 } 104 105 @Override 106 public void setHeaders(String theName, List<String> theValues) { 107 myHeaderOverrides.removeAll(theName); 108 myHeaderOverrides.putAll(theName, theValues); 109 } 110 111 @Override 112 public Map<Object, Object> getUserData() { 113 return myWrap.getUserData(); 114 } 115 116 @Override 117 public boolean isSubRequest() { 118 return true; 119 } 120}