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}