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}