001package ca.uhn.fhir.rest.param;
002
003/*
004 * #%L
005 * HAPI FHIR - Core Library
006 * %%
007 * Copyright (C) 2014 - 2017 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 */
022import static org.apache.commons.lang3.StringUtils.isBlank;
023
024import java.io.ByteArrayInputStream;
025import java.io.IOException;
026import java.io.InputStreamReader;
027import java.io.Reader;
028import java.lang.reflect.Method;
029import java.lang.reflect.Modifier;
030import java.nio.charset.Charset;
031import java.util.Collection;
032import java.util.List;
033import java.util.Map;
034
035import org.apache.commons.io.IOUtils;
036import org.apache.commons.lang3.Validate;
037import org.hl7.fhir.instance.model.api.IBaseBinary;
038import org.hl7.fhir.instance.model.api.IBaseResource;
039
040import ca.uhn.fhir.context.FhirContext;
041import ca.uhn.fhir.context.FhirVersionEnum;
042import ca.uhn.fhir.model.api.IResource;
043import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
044import ca.uhn.fhir.model.api.TagList;
045import ca.uhn.fhir.parser.DataFormatException;
046import ca.uhn.fhir.parser.IParser;
047import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
048import ca.uhn.fhir.rest.method.BaseMethodBinding;
049import ca.uhn.fhir.rest.method.IParameter;
050import ca.uhn.fhir.rest.method.MethodUtil;
051import ca.uhn.fhir.rest.method.RequestDetails;
052import ca.uhn.fhir.rest.server.Constants;
053import ca.uhn.fhir.rest.server.EncodingEnum;
054import ca.uhn.fhir.rest.server.IResourceProvider;
055import ca.uhn.fhir.rest.server.RestfulServerUtils;
056import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
057import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
058
059public class ResourceParameter implements IParameter {
060
061        private Mode myMode;
062        private Class<? extends IBaseResource> myResourceType;
063
064        public ResourceParameter(Class<? extends IResource> theParameterType, Object theProvider, Mode theMode) {
065                Validate.notNull(theParameterType, "theParameterType can not be null");
066                Validate.notNull(theMode, "theMode can not be null");
067
068                myResourceType = theParameterType;
069                myMode = theMode;
070
071                Class<? extends IBaseResource> providerResourceType = null;
072                if (theProvider instanceof IResourceProvider) {
073                        providerResourceType = ((IResourceProvider) theProvider).getResourceType();
074                }
075
076                if (Modifier.isAbstract(myResourceType.getModifiers()) && providerResourceType != null) {
077                        myResourceType = providerResourceType;
078                }
079
080        }
081
082        public Mode getMode() {
083                return myMode;
084        }
085
086        public Class<? extends IBaseResource> getResourceType() {
087                return myResourceType;
088        }
089
090        @Override
091        public void initializeTypes(Method theMethod, Class<? extends Collection<?>> theOuterCollectionType, Class<? extends Collection<?>> theInnerCollectionType, Class<?> theParameterType) {
092                // ignore for now
093        }
094
095        @Override
096        public void translateClientArgumentIntoQueryArgument(FhirContext theContext, Object theSourceClientArgument, Map<String, List<String>> theTargetQueryArguments, IBaseResource theTargetResource) throws InternalErrorException {
097                // TODO Auto-generated method stub
098
099        }
100
101        @Override
102        public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, BaseMethodBinding<?> theMethodBinding) throws InternalErrorException, InvalidRequestException {
103                switch (myMode) {
104                case BODY:
105                        try {
106                                return IOUtils.toString(createRequestReader(theRequest));
107                        } catch (IOException e) {
108                                // Shouldn't happen since we're reading from a byte array
109                                throw new InternalErrorException("Failed to load request", e);
110                        }
111                case BODY_BYTE_ARRAY:
112                        return theRequest.loadRequestContents();
113                case ENCODING:
114                        return RestfulServerUtils.determineRequestEncodingNoDefault(theRequest);
115                case RESOURCE:
116                default:
117                        return parseResourceFromRequest(theRequest, theMethodBinding, myResourceType);
118                }
119                // }
120        }
121
122        public static Reader createRequestReader(RequestDetails theRequest, Charset charset) {
123                Reader requestReader = new InputStreamReader(new ByteArrayInputStream(theRequest.loadRequestContents()), charset);
124                return requestReader;
125        }
126
127        public static Reader createRequestReader(RequestDetails theRequest) {
128                return createRequestReader(theRequest, determineRequestCharset(theRequest));
129        }
130
131        public static Charset determineRequestCharset(RequestDetails theRequest) {
132                Charset charset =  theRequest.getCharset();
133                if (charset == null) {
134                        charset = Charset.forName("UTF-8");
135                }
136                return charset;
137        }
138
139        @SuppressWarnings("unchecked")
140        public static <T extends IBaseResource> T loadResourceFromRequest(RequestDetails theRequest, BaseMethodBinding<?> theMethodBinding, Class<T> theResourceType) {
141                FhirContext ctx = theRequest.getServer().getFhirContext();
142
143                final Charset charset = determineRequestCharset(theRequest);
144                Reader requestReader = createRequestReader(theRequest, charset);
145
146                RestOperationTypeEnum restOperationType = theMethodBinding != null ? theMethodBinding.getRestOperationType() : null;
147
148                EncodingEnum encoding = RestfulServerUtils.determineRequestEncodingNoDefault(theRequest);
149                if (encoding == null) {
150                        String ctValue = theRequest.getHeader(Constants.HEADER_CONTENT_TYPE);
151                        if (ctValue != null) {
152                                if (ctValue.startsWith("application/x-www-form-urlencoded")) {
153                                        //FIXME potential null access theMethodBinding
154                                        String msg = theRequest.getServer().getFhirContext().getLocalizer().getMessage(ResourceParameter.class, "invalidContentTypeInRequest", ctValue, theMethodBinding.getRestOperationType());
155                                        throw new InvalidRequestException(msg);
156                                }
157                        }
158                        if (isBlank(ctValue)) {
159                                /*
160                                 * If the client didn't send a content type, try to guess
161                                 */
162                                String body;
163                                try {
164                                        body = IOUtils.toString(requestReader);
165                                } catch (IOException e) {
166                                        // This shouldn't happen since we're reading from a byte array..
167                                        throw new InternalErrorException(e);
168                                }
169                                encoding = MethodUtil.detectEncodingNoDefault(body);
170                                if (encoding == null) {
171                                        String msg = ctx.getLocalizer().getMessage(ResourceParameter.class, "noContentTypeInRequest", restOperationType);
172                                        throw new InvalidRequestException(msg);
173                                }
174                                requestReader = new InputStreamReader(new ByteArrayInputStream(theRequest.loadRequestContents()), charset);
175                        } else {
176                                String msg = ctx.getLocalizer().getMessage(ResourceParameter.class, "invalidContentTypeInRequest", ctValue, restOperationType);
177                                throw new InvalidRequestException(msg);
178                        }
179                }
180
181                IParser parser = encoding.newParser(ctx);
182    parser.setServerBaseUrl(theRequest.getFhirServerBase());
183                T retVal;
184                try {
185                        if (theResourceType != null) {
186                                retVal = parser.parseResource(theResourceType, requestReader);
187                        } else {
188                                retVal = (T) parser.parseResource(requestReader);
189                        }
190                } catch (DataFormatException e) {
191                        String msg = ctx.getLocalizer().getMessage(ResourceParameter.class, "failedToParseRequest", encoding.name(), e.getMessage());
192                        throw new InvalidRequestException(msg);
193                }
194                
195                if (theRequest.getServer().getFhirContext().getVersion().getVersion().equals(FhirVersionEnum.DSTU1)) {
196                        TagList tagList = new TagList();
197                        for (String nextTagComplete : theRequest.getHeaders(Constants.HEADER_CATEGORY)) {
198                                MethodUtil.parseTagValue(tagList, nextTagComplete);
199                        }
200                        if (tagList.isEmpty() == false) {
201                                ((IResource) retVal).getResourceMetadata().put(ResourceMetadataKeyEnum.TAG_LIST, tagList);
202                        }
203                }
204                return retVal;
205        }
206
207        public static IBaseResource parseResourceFromRequest(RequestDetails theRequest, BaseMethodBinding<?> theMethodBinding, Class<? extends IBaseResource> theResourceType) {
208                IBaseResource retVal = null;
209                
210                if (IBaseBinary.class.isAssignableFrom(theResourceType)) {
211                        String ct = theRequest.getHeader(Constants.HEADER_CONTENT_TYPE);
212                        if (EncodingEnum.forContentTypeStrict(ct) == null) {
213                                FhirContext ctx = theRequest.getServer().getFhirContext();
214                                IBaseBinary binary = (IBaseBinary) ctx.getResourceDefinition("Binary").newInstance();
215                                binary.setId(theRequest.getId());
216                                binary.setContentType(ct);
217                                binary.setContent(theRequest.loadRequestContents());
218                                retVal = binary;
219                        }
220                }
221                
222                if (retVal == null) {
223                        retVal = loadResourceFromRequest(theRequest, theMethodBinding, theResourceType);
224                }
225                return retVal;
226        }
227
228        public enum Mode {
229                BODY, BODY_BYTE_ARRAY, ENCODING, RESOURCE
230        }
231
232}