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