001package ca.uhn.fhir.rest.method;
002
003import static org.apache.commons.lang3.StringUtils.isBlank;
004import static org.apache.commons.lang3.StringUtils.isNotBlank;
005
006import java.io.IOException;
007import java.io.PushbackReader;
008import java.io.Reader;
009import java.io.UnsupportedEncodingException;
010import java.lang.annotation.Annotation;
011import java.lang.reflect.Method;
012import java.net.URLEncoder;
013import java.util.ArrayList;
014import java.util.Collection;
015import java.util.Collections;
016import java.util.Date;
017import java.util.List;
018import java.util.Map;
019import java.util.Map.Entry;
020
021import javax.servlet.ServletRequest;
022import javax.servlet.ServletResponse;
023import javax.servlet.http.HttpServletRequest;
024import javax.servlet.http.HttpServletResponse;
025
026import org.apache.commons.lang3.StringUtils;
027import org.apache.http.client.utils.DateUtils;
028import org.hl7.fhir.instance.model.api.IAnyResource;
029import org.hl7.fhir.instance.model.api.IBaseMetaType;
030import org.hl7.fhir.instance.model.api.IBaseResource;
031import org.hl7.fhir.instance.model.api.IIdType;
032
033import ca.uhn.fhir.context.ConfigurationException;
034import ca.uhn.fhir.context.FhirContext;
035import ca.uhn.fhir.context.FhirVersionEnum;
036import ca.uhn.fhir.context.RuntimeResourceDefinition;
037import ca.uhn.fhir.context.RuntimeSearchParam;
038import ca.uhn.fhir.model.api.IQueryParameterAnd;
039import ca.uhn.fhir.model.api.IQueryParameterOr;
040import ca.uhn.fhir.model.api.IQueryParameterType;
041import ca.uhn.fhir.model.api.IResource;
042import ca.uhn.fhir.model.api.Include;
043import ca.uhn.fhir.model.api.PathSpecification;
044import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
045import ca.uhn.fhir.model.api.Tag;
046import ca.uhn.fhir.model.api.TagList;
047import ca.uhn.fhir.model.api.annotation.Description;
048import ca.uhn.fhir.model.base.resource.BaseOperationOutcome;
049import ca.uhn.fhir.model.primitive.IdDt;
050import ca.uhn.fhir.model.primitive.InstantDt;
051import ca.uhn.fhir.model.primitive.StringDt;
052import ca.uhn.fhir.parser.IParser;
053import ca.uhn.fhir.rest.annotation.ConditionalUrlParam;
054import ca.uhn.fhir.rest.annotation.Count;
055import ca.uhn.fhir.rest.annotation.Elements;
056import ca.uhn.fhir.rest.annotation.IdParam;
057import ca.uhn.fhir.rest.annotation.IncludeParam;
058import ca.uhn.fhir.rest.annotation.Operation;
059import ca.uhn.fhir.rest.annotation.OperationParam;
060import ca.uhn.fhir.rest.annotation.OptionalParam;
061import ca.uhn.fhir.rest.annotation.RequiredParam;
062import ca.uhn.fhir.rest.annotation.ResourceParam;
063import ca.uhn.fhir.rest.annotation.Search;
064import ca.uhn.fhir.rest.annotation.ServerBase;
065import ca.uhn.fhir.rest.annotation.Since;
066import ca.uhn.fhir.rest.annotation.Sort;
067import ca.uhn.fhir.rest.annotation.TagListParam;
068import ca.uhn.fhir.rest.annotation.TransactionParam;
069import ca.uhn.fhir.rest.annotation.Validate;
070import ca.uhn.fhir.rest.annotation.VersionIdParam;
071import ca.uhn.fhir.rest.api.MethodOutcome;
072import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
073import ca.uhn.fhir.rest.api.SummaryEnum;
074import ca.uhn.fhir.rest.api.ValidationModeEnum;
075import ca.uhn.fhir.rest.client.BaseHttpClientInvocation;
076import ca.uhn.fhir.rest.method.OperationParameter.IConverter;
077import ca.uhn.fhir.rest.param.CollectionBinder;
078import ca.uhn.fhir.rest.param.DateAndListParam;
079import ca.uhn.fhir.rest.param.NumberAndListParam;
080import ca.uhn.fhir.rest.param.QuantityAndListParam;
081import ca.uhn.fhir.rest.param.ReferenceAndListParam;
082import ca.uhn.fhir.rest.param.ResourceParameter;
083import ca.uhn.fhir.rest.param.ResourceParameter.Mode;
084import ca.uhn.fhir.rest.param.StringAndListParam;
085import ca.uhn.fhir.rest.param.TokenAndListParam;
086import ca.uhn.fhir.rest.param.TransactionParameter;
087import ca.uhn.fhir.rest.param.UriAndListParam;
088import ca.uhn.fhir.rest.server.Constants;
089import ca.uhn.fhir.rest.server.EncodingEnum;
090import ca.uhn.fhir.rest.server.IDynamicSearchResourceProvider;
091import ca.uhn.fhir.rest.server.SearchParameterMap;
092import ca.uhn.fhir.util.ReflectionUtil;
093
094/*
095 * #%L
096 * HAPI FHIR - Core Library
097 * %%
098 * Copyright (C) 2014 - 2016 University Health Network
099 * %%
100 * Licensed under the Apache License, Version 2.0 (the "License");
101 * you may not use this file except in compliance with the License.
102 * You may obtain a copy of the License at
103 * 
104 *      http://www.apache.org/licenses/LICENSE-2.0
105 * 
106 * Unless required by applicable law or agreed to in writing, software
107 * distributed under the License is distributed on an "AS IS" BASIS,
108 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
109 * See the License for the specific language governing permissions and
110 * limitations under the License.
111 * #L%
112 */
113
114public class MethodUtil {
115        private static final String LABEL = "label=\"";
116        private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(MethodUtil.class);
117
118        private static final String SCHEME = "scheme=\"";
119
120        static void addTagsToPostOrPut(FhirContext theContext, IBaseResource resource, BaseHttpClientInvocation retVal) {
121                if (theContext.getVersion().getVersion().equals(FhirVersionEnum.DSTU1)) {
122                        TagList list = (TagList) ((IResource)resource).getResourceMetadata().get(ResourceMetadataKeyEnum.TAG_LIST);
123                        if (list != null) {
124                                for (Tag tag : list) {
125                                        if (StringUtils.isNotBlank(tag.getTerm())) {
126                                                retVal.addHeader(Constants.HEADER_CATEGORY, tag.toHeaderValue());
127                                        }
128                                }
129                        }
130                }
131        }
132
133        
134        public static IIdType convertIdToType(IIdType value, Class<? extends IIdType> idParamType) {
135                if (value != null && !idParamType.isAssignableFrom(value.getClass())) {
136                        try {
137                                IIdType newValue = idParamType.newInstance();
138                                newValue.setValue(value.getValue());
139                                value = newValue;
140                        } catch (InstantiationException e) {
141                                throw new ConfigurationException("Failed to instantiate " + idParamType, e);
142                        } catch (IllegalAccessException e) {
143                                throw new ConfigurationException("Failed to instantiate " + idParamType, e);
144                        }
145                }
146                return value;
147        }
148
149        public static HttpGetClientInvocation createConformanceInvocation() {
150                return new HttpGetClientInvocation("metadata");
151        }
152
153        public static HttpPostClientInvocation createCreateInvocation(IBaseResource theResource, FhirContext theContext) {
154                return createCreateInvocation(theResource, null, null, theContext);
155        }
156
157        public static HttpPostClientInvocation createCreateInvocation(IBaseResource theResource, String theResourceBody, String theId, FhirContext theContext) {
158                RuntimeResourceDefinition def = theContext.getResourceDefinition(theResource);
159                String resourceName = def.getName();
160
161                StringBuilder urlExtension = new StringBuilder();
162                urlExtension.append(resourceName);
163
164                boolean dstu1 = theContext.getVersion().getVersion().equals(FhirVersionEnum.DSTU1);
165                if (dstu1) {
166                        /*
167                         * This was allowable at one point, but as of DSTU2 it isn't.
168                         */
169                        if (StringUtils.isNotBlank(theId)) {
170                                urlExtension.append('/');
171                                urlExtension.append(theId);
172                        }
173                }
174                
175                HttpPostClientInvocation retVal;
176                if (StringUtils.isBlank(theResourceBody)) {
177                        retVal = new HttpPostClientInvocation(theContext, theResource, urlExtension.toString());
178                } else {
179                        retVal = new HttpPostClientInvocation(theContext, theResourceBody, false, urlExtension.toString());
180                }
181                addTagsToPostOrPut(theContext, theResource, retVal);
182
183                if (!dstu1) {
184                        retVal.setOmitResourceId(true);
185                }
186                // addContentTypeHeaderBasedOnDetectedType(retVal, theResourceBody);
187
188                return retVal;
189        }
190
191        public static HttpPostClientInvocation createCreateInvocation(IBaseResource theResource, String theResourceBody, String theId, FhirContext theContext, Map<String, List<String>> theIfNoneExistParams) {
192                HttpPostClientInvocation retVal = createCreateInvocation(theResource, theResourceBody, theId, theContext);
193                retVal.setIfNoneExistParams(theIfNoneExistParams);
194                return retVal;
195        }
196
197        public static HttpPostClientInvocation createCreateInvocation(IBaseResource theResource, String theResourceBody, String theId, FhirContext theContext, String theIfNoneExistUrl) {
198                HttpPostClientInvocation retVal = createCreateInvocation(theResource, theResourceBody, theId, theContext);
199                retVal.setIfNoneExistString(theIfNoneExistUrl);
200                return retVal;
201        }
202
203        public static HttpPutClientInvocation createUpdateInvocation(FhirContext theContext, IBaseResource theResource, String theResourceBody, Map<String, List<String>> theMatchParams) {
204                StringBuilder b = new StringBuilder();
205
206                String resourceType = theContext.getResourceDefinition(theResource).getName();
207                b.append(resourceType);
208
209                boolean haveQuestionMark = false;
210                for (Entry<String, List<String>> nextEntry : theMatchParams.entrySet()) {
211                        for (String nextValue : nextEntry.getValue()) {
212                                b.append(haveQuestionMark ? '&' : '?');
213                                haveQuestionMark = true;
214                                try {
215                                        b.append(URLEncoder.encode(nextEntry.getKey(), "UTF-8"));
216                                        b.append('=');
217                                        b.append(URLEncoder.encode(nextValue, "UTF-8"));
218                                } catch (UnsupportedEncodingException e) {
219                                        throw new ConfigurationException("UTF-8 not supported on this platform");
220                                }
221                        }
222                }
223
224                HttpPutClientInvocation retVal;
225                if (StringUtils.isBlank(theResourceBody)) {
226                        retVal = new HttpPutClientInvocation(theContext, theResource, b.toString());
227                } else {
228                        retVal = new HttpPutClientInvocation(theContext, theResourceBody, false, b.toString());
229                }
230
231                addTagsToPostOrPut(theContext, theResource, retVal);
232
233                return retVal;
234        }
235
236        public static HttpPutClientInvocation createUpdateInvocation(FhirContext theContext, IBaseResource theResource, String theResourceBody, String theMatchUrl) {
237                HttpPutClientInvocation retVal;
238                if (StringUtils.isBlank(theResourceBody)) {
239                        retVal = new HttpPutClientInvocation(theContext, theResource, theMatchUrl);
240                } else {
241                        retVal = new HttpPutClientInvocation(theContext, theResourceBody, false, theMatchUrl);
242                }
243
244                addTagsToPostOrPut(theContext, theResource, retVal);
245
246                return retVal;
247        }
248
249        public static HttpPutClientInvocation createUpdateInvocation(IBaseResource theResource, String theResourceBody, IIdType theId, FhirContext theContext) {
250                String resourceName = theContext.getResourceDefinition(theResource).getName();
251                StringBuilder urlBuilder = new StringBuilder();
252                urlBuilder.append(resourceName);
253                urlBuilder.append('/');
254                urlBuilder.append(theId.getIdPart());
255                String urlExtension = urlBuilder.toString();
256
257                HttpPutClientInvocation retVal;
258                if (StringUtils.isBlank(theResourceBody)) {
259                        retVal = new HttpPutClientInvocation(theContext, theResource, urlExtension);
260                } else {
261                        retVal = new HttpPutClientInvocation(theContext, theResourceBody, false, urlExtension);
262                }
263
264                if (theId.hasVersionIdPart()) {
265                        if (theContext.getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU1)) {
266                                retVal.addHeader(Constants.HEADER_IF_MATCH, '"' + theId.getVersionIdPart() + '"');
267                        } else {
268                                String versionId = theId.getVersionIdPart();
269                                if (StringUtils.isNotBlank(versionId)) {
270                                        urlBuilder.append('/');
271                                        urlBuilder.append(Constants.PARAM_HISTORY);
272                                        urlBuilder.append('/');
273                                        urlBuilder.append(versionId);
274                                        retVal.addHeader(Constants.HEADER_CONTENT_LOCATION, urlBuilder.toString());
275                                }
276                        }
277                }
278
279                addTagsToPostOrPut(theContext, theResource, retVal);
280                // addContentTypeHeaderBasedOnDetectedType(retVal, theResourceBody);
281
282                return retVal;
283        }
284
285        public static EncodingEnum detectEncoding(String theBody) {
286                EncodingEnum retVal = detectEncodingNoDefault(theBody);
287                if (retVal == null) {
288                        retVal = EncodingEnum.XML;
289                }
290                return retVal;
291        }
292
293        public static EncodingEnum detectEncodingNoDefault(String theBody) {
294                EncodingEnum retVal = null;
295                for (int i = 0; i < theBody.length() && retVal == null; i++) {
296                        switch (theBody.charAt(i)) {
297                                case '<':
298                                        retVal = EncodingEnum.XML;
299                                        break;
300                                case '{':
301                                        retVal = EncodingEnum.JSON;
302                                        break;
303                        }
304                }
305                return retVal;
306        }
307
308        public static void extractDescription(SearchParameter theParameter, Annotation[] theAnnotations) {
309                for (Annotation annotation : theAnnotations) {
310                        if (annotation instanceof Description) {
311                                Description desc = (Description) annotation;
312                                if (isNotBlank(desc.formalDefinition())) {
313                                        theParameter.setDescription(desc.formalDefinition());
314                                } else {
315                                        theParameter.setDescription(desc.shortDefinition());
316                                }
317                        }
318                }
319        }
320
321        public static Integer findConditionalOperationParameterIndex(Method theMethod) {
322                return MethodUtil.findParamAnnotationIndex(theMethod, ConditionalUrlParam.class);
323        }
324
325        public static Integer findIdParameterIndex(Method theMethod, FhirContext theContext) {
326                Integer index = MethodUtil.findParamAnnotationIndex(theMethod, IdParam.class);
327                if (index != null) {
328                        Class<?> paramType = theMethod.getParameterTypes()[index];
329                        if (IIdType.class.equals(paramType)) {
330                                return index;
331                        }
332                        boolean isRi = theContext.getVersion().getVersion().isRi();
333                        boolean usesHapiId = IdDt.class.equals(paramType);
334                        if (isRi == usesHapiId) {
335                                throw new ConfigurationException("Method uses the wrong Id datatype (IdDt / IdType) for the given context FHIR version: " + theMethod.toString());
336                        }
337                }
338                return index;
339        }
340
341        public static Integer findParamAnnotationIndex(Method theMethod, Class<?> toFind) {
342                int paramIndex = 0;
343                for (Annotation[] annotations : theMethod.getParameterAnnotations()) {
344                        for (int annotationIndex = 0; annotationIndex < annotations.length; annotationIndex++) {
345                                Annotation nextAnnotation = annotations[annotationIndex];
346                                Class<? extends Annotation> class1 = nextAnnotation.getClass();
347                                if (toFind.isAssignableFrom(class1)) {
348                                        return paramIndex;
349                                }
350                        }
351                        paramIndex++;
352                }
353                return null;
354        }
355
356        public static Integer findTagListParameterIndex(Method theMethod) {
357                return MethodUtil.findParamAnnotationIndex(theMethod, TagListParam.class);
358        }
359
360        @SuppressWarnings("deprecation")
361        public static Integer findVersionIdParameterIndex(Method theMethod) {
362                return MethodUtil.findParamAnnotationIndex(theMethod, VersionIdParam.class);
363        }
364
365        @SuppressWarnings("unchecked")
366        public static List<IParameter> getResourceParameters(FhirContext theContext, Method theMethod, Object theProvider, RestOperationTypeEnum theRestfulOperationTypeEnum) {
367                List<IParameter> parameters = new ArrayList<IParameter>();
368
369                Class<?>[] parameterTypes = theMethod.getParameterTypes();
370                int paramIndex = 0;
371                for (Annotation[] annotations : theMethod.getParameterAnnotations()) {
372
373                        IParameter param = null;
374                        Class<?> parameterType = parameterTypes[paramIndex];
375                        Class<? extends java.util.Collection<?>> outerCollectionType = null;
376                        Class<? extends java.util.Collection<?>> innerCollectionType = null;
377                        if (SearchParameterMap.class.equals(parameterType)) {
378                                if (theProvider instanceof IDynamicSearchResourceProvider) {
379                                        Search searchAnnotation = theMethod.getAnnotation(Search.class);
380                                        if (searchAnnotation != null && searchAnnotation.dynamic()) {
381                                                param = new DynamicSearchParameter((IDynamicSearchResourceProvider) theProvider);
382                                        }
383                                }
384                        } else if (TagList.class.isAssignableFrom(parameterType)) {
385                                // TagList is handled directly within the method bindings
386                                param = new NullParameter();
387                        } else {
388                                if (Collection.class.isAssignableFrom(parameterType)) {
389                                        innerCollectionType = (Class<? extends java.util.Collection<?>>) parameterType;
390                                        parameterType = ReflectionUtil.getGenericCollectionTypeOfMethodParameter(theMethod, paramIndex);
391                                }
392                                if (Collection.class.isAssignableFrom(parameterType)) {
393                                        outerCollectionType = innerCollectionType;
394                                        innerCollectionType = (Class<? extends java.util.Collection<?>>) parameterType;
395                                        parameterType = ReflectionUtil.getGenericCollectionTypeOfMethodParameter(theMethod, paramIndex);
396                                }
397                                if (Collection.class.isAssignableFrom(parameterType)) {
398                                        throw new ConfigurationException("Argument #" + paramIndex + " of Method '" + theMethod.getName() + "' in type '" + theMethod.getDeclaringClass().getCanonicalName() + "' is of an invalid generic type (can not be a collection of a collection of a collection)");
399                                }
400                        }
401                        if (parameterType.equals(HttpServletRequest.class) || parameterType.equals(ServletRequest.class)) {
402                                param = new ServletRequestParameter();
403                        } else if (parameterType.equals(RequestDetails.class)) {
404                                param = new RequestDetailsParameter();
405                        } else if (parameterType.equals(SummaryEnum.class)) {
406                                param = new SummaryEnumParameter();
407                        } else if (parameterType.equals(HttpServletResponse.class) || parameterType.equals(ServletResponse.class)) {
408                                param = new ServletResponseParameter();
409                        } else {
410                                for (int i = 0; i < annotations.length && param == null; i++) {
411                                        Annotation nextAnnotation = annotations[i];
412
413                                        if (nextAnnotation instanceof RequiredParam) {
414                                                SearchParameter parameter = new SearchParameter();
415                                                parameter.setName(((RequiredParam) nextAnnotation).name());
416                                                parameter.setRequired(true);
417                                                parameter.setDeclaredTypes(((RequiredParam) nextAnnotation).targetTypes());
418                                                parameter.setCompositeTypes(((RequiredParam) nextAnnotation).compositeTypes());
419                                                parameter.setChainlists(((RequiredParam) nextAnnotation).chainWhitelist(), ((RequiredParam) nextAnnotation).chainBlacklist());
420                                                parameter.setType(parameterType, innerCollectionType, outerCollectionType);
421                                                MethodUtil.extractDescription(parameter, annotations);
422                                                param = parameter;
423                                        } else if (nextAnnotation instanceof OptionalParam) {
424                                                SearchParameter parameter = new SearchParameter();
425                                                parameter.setName(((OptionalParam) nextAnnotation).name());
426                                                parameter.setRequired(false);
427                                                parameter.setDeclaredTypes(((OptionalParam) nextAnnotation).targetTypes());
428                                                parameter.setCompositeTypes(((OptionalParam) nextAnnotation).compositeTypes());
429                                                parameter.setChainlists(((OptionalParam) nextAnnotation).chainWhitelist(), ((OptionalParam) nextAnnotation).chainBlacklist());
430                                                parameter.setType(parameterType, innerCollectionType, outerCollectionType);
431                                                MethodUtil.extractDescription(parameter, annotations);
432                                                param = parameter;
433                                        } else if (nextAnnotation instanceof IncludeParam) {
434                                                Class<? extends Collection<Include>> instantiableCollectionType;
435                                                Class<?> specType;
436
437                                                if (parameterType == String.class) {
438                                                        instantiableCollectionType = null;
439                                                        specType = String.class;
440                                                } else if ((parameterType != Include.class && parameterType != PathSpecification.class) || innerCollectionType == null || outerCollectionType != null) {
441                                                        throw new ConfigurationException("Method '" + theMethod.getName() + "' is annotated with @" + IncludeParam.class.getSimpleName() + " but has a type other than Collection<" + Include.class.getSimpleName() + ">");
442                                                } else {
443                                                        instantiableCollectionType = (Class<? extends Collection<Include>>) CollectionBinder.getInstantiableCollectionType(innerCollectionType, "Method '" + theMethod.getName() + "'");
444                                                        specType = parameterType;
445                                                }
446
447                                                param = new IncludeParameter((IncludeParam) nextAnnotation, instantiableCollectionType, specType);
448                                        } else if (nextAnnotation instanceof ResourceParam) {
449                                                Mode mode;
450                                                if (IBaseResource.class.isAssignableFrom(parameterType)) {
451                                                        mode = Mode.RESOURCE;
452                                                } else if (String.class.equals(parameterType)) {
453                                                        mode = ResourceParameter.Mode.BODY;
454                                                } else if (EncodingEnum.class.equals(parameterType)) {
455                                                        mode = Mode.ENCODING;
456                                                } else {
457                                                        throw new ConfigurationException("Method '" + theMethod.getName() + "' is annotated with @" + ResourceParam.class.getSimpleName() + " but has a type that is not an implemtation of " + IResource.class.getCanonicalName());
458                                                }
459                                                param = new ResourceParameter((Class<? extends IResource>) parameterType, theProvider, mode);
460                                        } else if (nextAnnotation instanceof IdParam || nextAnnotation instanceof VersionIdParam) {
461                                                param = new NullParameter();
462                                        } else if (nextAnnotation instanceof ServerBase) {
463                                                param = new ServerBaseParamBinder();
464                                        } else if (nextAnnotation instanceof Elements) {
465                                                param = new ElementsParameter();
466                                        } else if (nextAnnotation instanceof Since) {
467                                                param = new SinceParameter();
468                                        } else if (nextAnnotation instanceof Count) {
469                                                param = new CountParameter();
470                                        } else if (nextAnnotation instanceof Sort) {
471                                                param = new SortParameter();
472                                        } else if (nextAnnotation instanceof TransactionParam) {
473                                                param = new TransactionParameter(theContext);
474                                        } else if (nextAnnotation instanceof ConditionalUrlParam) {
475                                                param = new ConditionalParamBinder(theRestfulOperationTypeEnum, ((ConditionalUrlParam)nextAnnotation).supportsMultiple());
476                                        } else if (nextAnnotation instanceof OperationParam) {
477                                                Operation op = theMethod.getAnnotation(Operation.class);
478                                                param = new OperationParameter(theContext, op.name(), ((OperationParam) nextAnnotation));
479                                        } else if (nextAnnotation instanceof Validate.Mode) {
480                                                if (parameterType.equals(ValidationModeEnum.class) == false) {
481                                                        throw new ConfigurationException("Parameter annotated with @" + Validate.class.getSimpleName() + "." + Validate.Mode.class.getSimpleName() + " must be of type " + ValidationModeEnum.class.getName());
482                                                }
483                                                param = new OperationParameter(theContext, Constants.EXTOP_VALIDATE, Constants.EXTOP_VALIDATE_MODE, 0, 1).setConverter(new IConverter() {
484                                                        @Override
485                                                        public Object incomingServer(Object theObject) {
486                                                                return ValidationModeEnum.valueOf(theObject.toString().toUpperCase());
487                                                        }
488                                                        
489                                                        @Override
490                                                        public Object outgoingClient(Object theObject) {
491                                                                return new StringDt(((ValidationModeEnum)theObject).name().toLowerCase());
492                                                        }
493                                                });
494                                        } else if (nextAnnotation instanceof Validate.Profile) {
495                                                if (parameterType.equals(String.class) == false) {
496                                                        throw new ConfigurationException("Parameter annotated with @" + Validate.class.getSimpleName() + "." + Validate.Profile.class.getSimpleName() + " must be of type " + String.class.getName());
497                                                }
498                                                param = new OperationParameter(theContext, Constants.EXTOP_VALIDATE, Constants.EXTOP_VALIDATE_PROFILE, 0, 1).setConverter(new IConverter() {
499                                                        @Override
500                                                        public Object incomingServer(Object theObject) {
501                                                                return theObject.toString();
502                                                        }
503                                                        
504                                                        @Override
505                                                        public Object outgoingClient(Object theObject) {
506                                                                return new StringDt(theObject.toString());
507                                                        }
508                                                });
509                                        } else {
510                                                continue;
511                                        }
512
513                                }
514
515                        }
516
517                        if (param == null) {
518                                throw new ConfigurationException("Parameter #" + ((paramIndex + 1)) + "/" + (parameterTypes.length) + " of method '" + theMethod.getName() + "' on type '" + theMethod.getDeclaringClass().getCanonicalName()
519                                                + "' has no recognized FHIR interface parameter annotations. Don't know how to handle this parameter");
520                        }
521
522                        param.initializeTypes(theMethod, outerCollectionType, innerCollectionType, parameterType);
523                        parameters.add(param);
524
525                        paramIndex++;
526                }
527                return parameters;
528        }
529
530        public static void parseClientRequestResourceHeaders(IIdType theRequestedId, Map<String, List<String>> theHeaders, IBaseResource resource) {
531                List<String> lmHeaders = theHeaders.get(Constants.HEADER_LAST_MODIFIED_LOWERCASE);
532                if (lmHeaders != null && lmHeaders.size() > 0 && StringUtils.isNotBlank(lmHeaders.get(0))) {
533                        String headerValue = lmHeaders.get(0);
534                        Date headerDateValue;
535                        try {
536                                headerDateValue = DateUtils.parseDate(headerValue);
537                                if (resource instanceof IResource) {
538                                        IResource iResource = (IResource) resource;
539                                        InstantDt existing = ResourceMetadataKeyEnum.UPDATED.get(iResource);
540                                        if (existing == null || existing.isEmpty()) {
541                                                InstantDt lmValue = new InstantDt(headerDateValue);
542                                                iResource.getResourceMetadata().put(ResourceMetadataKeyEnum.UPDATED, lmValue);
543                                        }
544                                } else if (resource instanceof IAnyResource) {
545                                        IAnyResource anyResource = (IAnyResource) resource;
546                                        if (anyResource.getMeta().getLastUpdated() == null) {
547                                                anyResource.getMeta().setLastUpdated(headerDateValue);
548                                        }
549                                }
550                        } catch (Exception e) {
551                                ourLog.warn("Unable to parse date string '{}'. Error is: {}", headerValue, e.toString());
552                        }
553                }
554
555                List<String> clHeaders = theHeaders.get(Constants.HEADER_CONTENT_LOCATION_LC);
556                if (clHeaders != null && clHeaders.size() > 0 && StringUtils.isNotBlank(clHeaders.get(0))) {
557                        String headerValue = clHeaders.get(0);
558                        if (isNotBlank(headerValue)) {
559                                new IdDt(headerValue).applyTo(resource);
560                        }
561                }
562
563                IdDt existing = IdDt.of(resource);
564
565                List<String> eTagHeaders = theHeaders.get(Constants.HEADER_ETAG_LC);
566                String eTagVersion = null;
567                if (eTagHeaders != null && eTagHeaders.size() > 0) {
568                        eTagVersion = parseETagValue(eTagHeaders.get(0));
569                }
570                if (isNotBlank(eTagVersion)) {
571                        if (existing == null || existing.isEmpty()) {
572                                if (theRequestedId != null) {
573                                        theRequestedId.withVersion(eTagVersion).applyTo(resource);
574                                }
575                        } else if (existing.hasVersionIdPart() == false) {
576                                existing.withVersion(eTagVersion).applyTo(resource);
577                        }
578                } else if (existing == null || existing.isEmpty()) {
579                        if (theRequestedId != null) {
580                                theRequestedId.applyTo(resource);
581                        }
582                }
583
584                List<String> categoryHeaders = theHeaders.get(Constants.HEADER_CATEGORY_LC);
585                if (categoryHeaders != null && categoryHeaders.size() > 0 && StringUtils.isNotBlank(categoryHeaders.get(0))) {
586                        TagList tagList = new TagList();
587                        for (String header : categoryHeaders) {
588                                parseTagValue(tagList, header);
589                        }
590                        if (resource instanceof IResource) {
591                                ResourceMetadataKeyEnum.TAG_LIST.put((IResource) resource, tagList);
592                        } else if (resource instanceof IAnyResource) {
593                                IBaseMetaType meta = ((IAnyResource) resource).getMeta();
594                                for (Tag next : tagList) {
595                                        meta.addTag().setSystem(next.getScheme()).setCode(next.getTerm()).setDisplay(next.getLabel());
596                                }
597                        }
598                }
599        }
600
601        public static String parseETagValue(String value) {
602                String eTagVersion;
603                value = value.trim();
604                if (value.length() > 1) {
605                        if (value.charAt(value.length() - 1) == '"') {
606                                if (value.charAt(0) == '"') {
607                                        eTagVersion = value.substring(1, value.length() - 1);
608                                } else if (value.length() > 3 && value.charAt(0) == 'W' && value.charAt(1) == '/' && value.charAt(2) == '"') {
609                                        eTagVersion = value.substring(3, value.length() - 1);
610                                } else {
611                                        eTagVersion = value;
612                                }
613                        } else {
614                                eTagVersion = value;
615                        }
616                } else {
617                        eTagVersion = value;
618                }
619                return eTagVersion;
620        }
621
622        /**
623         * This is a utility method intended provided to help the JPA module.
624         */
625        public static IQueryParameterAnd<?> parseQueryParams(RuntimeSearchParam theParamDef, String theUnqualifiedParamName, List<QualifiedParamList> theParameters) {
626                QueryParameterAndBinder binder = null;
627                switch (theParamDef.getParamType()) {
628                case COMPOSITE:
629                        throw new UnsupportedOperationException();
630                case DATE:
631                        binder = new QueryParameterAndBinder(DateAndListParam.class, Collections.<Class<? extends IQueryParameterType>> emptyList());
632                        break;
633                case NUMBER:
634                        binder = new QueryParameterAndBinder(NumberAndListParam.class, Collections.<Class<? extends IQueryParameterType>> emptyList());
635                        break;
636                case QUANTITY:
637                        binder = new QueryParameterAndBinder(QuantityAndListParam.class, Collections.<Class<? extends IQueryParameterType>> emptyList());
638                        break;
639                case REFERENCE:
640                        binder = new QueryParameterAndBinder(ReferenceAndListParam.class, Collections.<Class<? extends IQueryParameterType>> emptyList());
641                        break;
642                case STRING:
643                        binder = new QueryParameterAndBinder(StringAndListParam.class, Collections.<Class<? extends IQueryParameterType>> emptyList());
644                        break;
645                case TOKEN:
646                        binder = new QueryParameterAndBinder(TokenAndListParam.class, Collections.<Class<? extends IQueryParameterType>> emptyList());
647                        break;
648                case URI:
649                        binder = new QueryParameterAndBinder(UriAndListParam.class, Collections.<Class<? extends IQueryParameterType>> emptyList());
650                        break;
651                }
652
653                return binder.parse(theUnqualifiedParamName, theParameters);
654        }
655
656        public static void parseTagValue(TagList tagList, String nextTagComplete) {
657                StringBuilder next = new StringBuilder(nextTagComplete);
658                parseTagValue(tagList, nextTagComplete, next);
659        }
660
661        private static void parseTagValue(TagList theTagList, String theCompleteHeaderValue, StringBuilder theBuffer) {
662                int firstSemicolon = theBuffer.indexOf(";");
663                int deleteTo;
664                if (firstSemicolon == -1) {
665                        firstSemicolon = theBuffer.indexOf(",");
666                        if (firstSemicolon == -1) {
667                                firstSemicolon = theBuffer.length();
668                                deleteTo = theBuffer.length();
669                        } else {
670                                deleteTo = firstSemicolon;
671                        }
672                } else {
673                        deleteTo = firstSemicolon + 1;
674                }
675
676                String term = theBuffer.substring(0, firstSemicolon);
677                String scheme = null;
678                String label = null;
679                if (isBlank(term)) {
680                        return;
681                }
682
683                theBuffer.delete(0, deleteTo);
684                while (theBuffer.length() > 0 && theBuffer.charAt(0) == ' ') {
685                        theBuffer.deleteCharAt(0);
686                }
687
688                while (theBuffer.length() > 0) {
689                        boolean foundSomething = false;
690                        if (theBuffer.length() > SCHEME.length() && theBuffer.substring(0, SCHEME.length()).equals(SCHEME)) {
691                                int closeIdx = theBuffer.indexOf("\"", SCHEME.length());
692                                scheme = theBuffer.substring(SCHEME.length(), closeIdx);
693                                theBuffer.delete(0, closeIdx + 1);
694                                foundSomething = true;
695                        }
696                        if (theBuffer.length() > LABEL.length() && theBuffer.substring(0, LABEL.length()).equals(LABEL)) {
697                                int closeIdx = theBuffer.indexOf("\"", LABEL.length());
698                                label = theBuffer.substring(LABEL.length(), closeIdx);
699                                theBuffer.delete(0, closeIdx + 1);
700                                foundSomething = true;
701                        }
702                        // TODO: support enc2231-string as described in
703                        // http://tools.ietf.org/html/draft-johnston-http-category-header-02
704                        // TODO: support multiple tags in one header as described in
705                        // http://hl7.org/implement/standards/fhir/http.html#tags
706
707                        while (theBuffer.length() > 0 && (theBuffer.charAt(0) == ' ' || theBuffer.charAt(0) == ';')) {
708                                theBuffer.deleteCharAt(0);
709                        }
710
711                        if (!foundSomething) {
712                                break;
713                        }
714                }
715
716                if (theBuffer.length() > 0 && theBuffer.charAt(0) == ',') {
717                        theBuffer.deleteCharAt(0);
718                        while (theBuffer.length() > 0 && theBuffer.charAt(0) == ' ') {
719                                theBuffer.deleteCharAt(0);
720                        }
721                        theTagList.add(new Tag(scheme, term, label));
722                        parseTagValue(theTagList, theCompleteHeaderValue, theBuffer);
723                } else {
724                        theTagList.add(new Tag(scheme, term, label));
725                }
726
727                if (theBuffer.length() > 0) {
728                        ourLog.warn("Ignoring extra text at the end of " + Constants.HEADER_CATEGORY + " tag '" + theBuffer.toString() + "' - Complete tag value was: " + theCompleteHeaderValue);
729                }
730
731        }
732
733        public static MethodOutcome process2xxResponse(FhirContext theContext, String theResourceName, int theResponseStatusCode, String theResponseMimeType, Reader theResponseReader, Map<String, List<String>> theHeaders) {
734                List<String> locationHeaders = new ArrayList<String>();
735                List<String> lh = theHeaders.get(Constants.HEADER_LOCATION_LC);
736                if (lh != null) {
737                        locationHeaders.addAll(lh);
738                }
739                List<String> clh = theHeaders.get(Constants.HEADER_CONTENT_LOCATION_LC);
740                if (clh != null) {
741                        locationHeaders.addAll(clh);
742                }
743
744                MethodOutcome retVal = new MethodOutcome();
745                if (locationHeaders != null && locationHeaders.size() > 0) {
746                        String locationHeader = locationHeaders.get(0);
747                        BaseOutcomeReturningMethodBinding.parseContentLocation(theContext, retVal, theResourceName, locationHeader);
748                }
749                if (theResponseStatusCode != Constants.STATUS_HTTP_204_NO_CONTENT) {
750                        EncodingEnum ct = EncodingEnum.forContentType(theResponseMimeType);
751                        if (ct != null) {
752                                PushbackReader reader = new PushbackReader(theResponseReader);
753
754                                try {
755                                        int firstByte = reader.read();
756                                        if (firstByte == -1) {
757                                                BaseOutcomeReturningMethodBinding.ourLog.debug("No content in response, not going to read");
758                                                reader = null;
759                                        } else {
760                                                reader.unread(firstByte);
761                                        }
762                                } catch (IOException e) {
763                                        BaseOutcomeReturningMethodBinding.ourLog.debug("No content in response, not going to read", e);
764                                        reader = null;
765                                }
766
767                                if (reader != null) {
768                                        IParser parser = ct.newParser(theContext);
769                                        IBaseResource outcome = parser.parseResource(reader);
770                                        if (outcome instanceof BaseOperationOutcome) {
771                                                retVal.setOperationOutcome((BaseOperationOutcome) outcome);
772                                        } else {
773                                                retVal.setResource(outcome);
774                                        }
775                                }
776
777                        } else {
778                                BaseOutcomeReturningMethodBinding.ourLog.debug("Ignoring response content of type: {}", theResponseMimeType);
779                        }
780                }
781                return retVal;
782        }
783
784        public static IQueryParameterOr<?> singleton(final IQueryParameterType theParam) {
785                return new IQueryParameterOr<IQueryParameterType>() {
786
787                        @Override
788                        public List<IQueryParameterType> getValuesAsQueryTokens() {
789                                return Collections.singletonList(theParam);
790                        }
791
792                        @Override
793                        public void setValuesAsQueryTokens(QualifiedParamList theParameters) {
794                                if (theParameters.isEmpty()) {
795                                        return;
796                                }
797                                if (theParameters.size() > 1) {
798                                        throw new IllegalArgumentException("Type " + theParam.getClass().getCanonicalName() + " does not support multiple values");
799                                }
800                                theParam.setValueAsQueryToken(theParameters.getQualifier(), theParameters.get(0));
801                        }
802                };
803        }
804
805}