001package ca.uhn.fhir.rest.server.method;
002
003/*
004 * #%L
005 * HAPI FHIR - Server Framework
006 * %%
007 * Copyright (C) 2014 - 2019 University Health Network
008 * %%
009 * Licensed under the Apache License, Version 2.0 (the "License");
010 * you may not use this file except in compliance with the License.
011 * You may obtain a copy of the License at
012 *
013 *      http://www.apache.org/licenses/LICENSE-2.0
014 *
015 * Unless required by applicable law or agreed to in writing, software
016 * distributed under the License is distributed on an "AS IS" BASIS,
017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018 * See the License for the specific language governing permissions and
019 * limitations under the License.
020 * #L%
021 */
022
023import ca.uhn.fhir.context.FhirContext;
024import ca.uhn.fhir.context.FhirVersionEnum;
025import ca.uhn.fhir.parser.DataFormatException;
026import ca.uhn.fhir.parser.IParser;
027import ca.uhn.fhir.rest.api.Constants;
028import ca.uhn.fhir.rest.api.EncodingEnum;
029import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
030import ca.uhn.fhir.rest.api.server.RequestDetails;
031import ca.uhn.fhir.rest.server.IResourceProvider;
032import ca.uhn.fhir.rest.server.RestfulServerUtils;
033import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
034import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
035import ca.uhn.fhir.util.BinaryUtil;
036import org.apache.commons.io.IOUtils;
037import org.apache.commons.lang3.Validate;
038import org.hl7.fhir.instance.model.api.IBaseBinary;
039import org.hl7.fhir.instance.model.api.IBaseResource;
040
041import javax.annotation.Nonnull;
042import java.io.ByteArrayInputStream;
043import java.io.IOException;
044import java.io.InputStreamReader;
045import java.io.Reader;
046import java.lang.reflect.Method;
047import java.lang.reflect.Modifier;
048import java.nio.charset.Charset;
049import java.util.Collection;
050
051import static org.apache.commons.lang3.StringUtils.isBlank;
052import static org.apache.commons.lang3.StringUtils.isNotBlank;
053
054public class ResourceParameter implements IParameter {
055
056        private final boolean myMethodIsOperation;
057        private Mode myMode;
058        private Class<? extends IBaseResource> myResourceType;
059
060        public ResourceParameter(Class<? extends IBaseResource> theParameterType, Object theProvider, Mode theMode, boolean theMethodIsOperation) {
061                Validate.notNull(theParameterType, "theParameterType can not be null");
062                Validate.notNull(theMode, "theMode can not be null");
063
064                myResourceType = theParameterType;
065                myMode = theMode;
066                myMethodIsOperation = theMethodIsOperation;
067
068                Class<? extends IBaseResource> providerResourceType = null;
069                if (theProvider instanceof IResourceProvider) {
070                        providerResourceType = ((IResourceProvider) theProvider).getResourceType();
071                }
072
073                if (Modifier.isAbstract(myResourceType.getModifiers()) && providerResourceType != null) {
074                        myResourceType = providerResourceType;
075                }
076
077        }
078
079        public Mode getMode() {
080                return myMode;
081        }
082
083        public Class<? extends IBaseResource> getResourceType() {
084                return myResourceType;
085        }
086
087        @Override
088        public void initializeTypes(Method theMethod, Class<? extends Collection<?>> theOuterCollectionType, Class<? extends Collection<?>> theInnerCollectionType, Class<?> theParameterType) {
089                // ignore for now
090        }
091
092
093        @Override
094        public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, BaseMethodBinding<?> theMethodBinding) throws InternalErrorException, InvalidRequestException {
095                switch (myMode) {
096                        case BODY:
097                                try {
098                                        return IOUtils.toString(createRequestReader(theRequest));
099                                } catch (IOException e) {
100                                        // Shouldn't happen since we're reading from a byte array
101                                        throw new InternalErrorException("Failed to load request", e);
102                                }
103                        case BODY_BYTE_ARRAY:
104                                return theRequest.loadRequestContents();
105                        case ENCODING:
106                                return RestfulServerUtils.determineRequestEncodingNoDefault(theRequest);
107                        case RESOURCE:
108                        default:
109                                Class<? extends IBaseResource> resourceTypeToParse = myResourceType;
110                                if (myMethodIsOperation) {
111                                        // Operations typically have a Parameters resource as the body
112                                        resourceTypeToParse = null;
113                                }
114                                return parseResourceFromRequest(theRequest, theMethodBinding, resourceTypeToParse);
115                }
116                // }
117        }
118
119        public enum Mode {
120                BODY, BODY_BYTE_ARRAY, ENCODING, RESOURCE
121        }
122
123        private static Reader createRequestReader(RequestDetails theRequest, Charset charset) {
124                return new InputStreamReader(new ByteArrayInputStream(theRequest.loadRequestContents()), charset);
125        }
126
127        // Do not make private
128        @SuppressWarnings("WeakerAccess")
129        public static Reader createRequestReader(RequestDetails theRequest) {
130                return createRequestReader(theRequest, determineRequestCharset(theRequest));
131        }
132
133        public static Charset determineRequestCharset(RequestDetails theRequest) {
134                Charset charset = theRequest.getCharset();
135                if (charset == null) {
136                        charset = Charset.forName("UTF-8");
137                }
138                return charset;
139        }
140
141        @SuppressWarnings("unchecked")
142        static <T extends IBaseResource> T loadResourceFromRequest(RequestDetails theRequest, @Nonnull BaseMethodBinding<?> theMethodBinding, Class<T> theResourceType) {
143                FhirContext ctx = theRequest.getServer().getFhirContext();
144
145                final Charset charset = determineRequestCharset(theRequest);
146                Reader requestReader = createRequestReader(theRequest, charset);
147
148                RestOperationTypeEnum restOperationType = theMethodBinding != null ? theMethodBinding.getRestOperationType() : null;
149
150                EncodingEnum encoding = RestfulServerUtils.determineRequestEncodingNoDefault(theRequest);
151                if (encoding == null) {
152                        String ctValue = theRequest.getHeader(Constants.HEADER_CONTENT_TYPE);
153                        if (ctValue != null) {
154                                if (ctValue.startsWith("application/x-www-form-urlencoded")) {
155                                        String msg = theRequest.getServer().getFhirContext().getLocalizer().getMessage(ResourceParameter.class, "invalidContentTypeInRequest", ctValue, theMethodBinding.getRestOperationType());
156                                        throw new InvalidRequestException(msg);
157                                }
158                        }
159                        if (isBlank(ctValue)) {
160                                String body;
161                                try {
162                                        body = IOUtils.toString(requestReader);
163                                } catch (IOException e) {
164                                        // This shouldn't happen since we're reading from a byte array..
165                                        throw new InternalErrorException(e);
166                                }
167                                if (isBlank(body)) {
168                                        return null;
169                                }
170
171                                String msg = ctx.getLocalizer().getMessage(ResourceParameter.class, "noContentTypeInRequest", restOperationType);
172                                throw new InvalidRequestException(msg);
173                        } else {
174                                String msg = ctx.getLocalizer().getMessage(ResourceParameter.class, "invalidContentTypeInRequest", ctValue, restOperationType);
175                                throw new InvalidRequestException(msg);
176                        }
177                }
178
179                IParser parser = encoding.newParser(ctx);
180                parser.setServerBaseUrl(theRequest.getFhirServerBase());
181                T retVal;
182                try {
183                        if (theResourceType != null) {
184                                retVal = parser.parseResource(theResourceType, requestReader);
185                        } else {
186                                retVal = (T) parser.parseResource(requestReader);
187                        }
188                } catch (DataFormatException e) {
189                        String msg = ctx.getLocalizer().getMessage(ResourceParameter.class, "failedToParseRequest", encoding.name(), e.getMessage());
190                        throw new InvalidRequestException(msg);
191                }
192
193                return retVal;
194        }
195
196        static IBaseResource parseResourceFromRequest(RequestDetails theRequest, @Nonnull BaseMethodBinding<?> theMethodBinding, Class<? extends IBaseResource> theResourceType) {
197                if (theRequest.getResource() != null) {
198                        return theRequest.getResource();
199                }
200
201                IBaseResource retVal = null;
202
203                if (theResourceType != null && IBaseBinary.class.isAssignableFrom(theResourceType)) {
204                        String ct = theRequest.getHeader(Constants.HEADER_CONTENT_TYPE);
205                        if (EncodingEnum.forContentTypeStrict(ct) == null) {
206                                FhirContext ctx = theRequest.getServer().getFhirContext();
207                                IBaseBinary binary = BinaryUtil.newBinary(ctx);
208                                binary.setId(theRequest.getId());
209                                binary.setContentType(ct);
210                                binary.setContent(theRequest.loadRequestContents());
211                                retVal = binary;
212
213                                /*
214                                 * Security context header, which is only in
215                                 * DSTU3+
216                                 */
217                                if (ctx.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.DSTU3)) {
218                                        String securityContext = theRequest.getHeader(Constants.HEADER_X_SECURITY_CONTEXT);
219                                        if (isNotBlank(securityContext)) {
220                                                BinaryUtil.setSecurityContext(ctx, binary, securityContext);
221                                        }
222                                }
223                        }
224                }
225
226                if (retVal == null) {
227                        retVal = loadResourceFromRequest(theRequest, theMethodBinding, theResourceType);
228                }
229
230                theRequest.setResource(retVal);
231
232                return retVal;
233        }
234
235}