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}