001package ca.uhn.fhir.rest.param;
002
003import java.lang.annotation.Annotation;
004import java.lang.reflect.Method;
005/*
006 * #%L
007 * HAPI FHIR - Core Library
008 * %%
009 * Copyright (C) 2014 - 2017 University Health Network
010 * %%
011 * Licensed under the Apache License, Version 2.0 (the "License");
012 * you may not use this file except in compliance with the License.
013 * You may obtain a copy of the License at
014 * 
015 * http://www.apache.org/licenses/LICENSE-2.0
016 * 
017 * Unless required by applicable law or agreed to in writing, software
018 * distributed under the License is distributed on an "AS IS" BASIS,
019 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
020 * See the License for the specific language governing permissions and
021 * limitations under the License.
022 * #L%
023 */
024import java.util.*;
025
026import org.hl7.fhir.instance.model.api.IIdType;
027import org.hl7.fhir.instance.model.api.IPrimitiveType;
028
029import ca.uhn.fhir.context.*;
030import ca.uhn.fhir.model.api.*;
031import ca.uhn.fhir.model.primitive.IdDt;
032import ca.uhn.fhir.model.primitive.IntegerDt;
033import ca.uhn.fhir.rest.annotation.IdParam;
034import ca.uhn.fhir.rest.annotation.TagListParam;
035import ca.uhn.fhir.rest.api.QualifiedParamList;
036import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
037import ca.uhn.fhir.rest.param.binder.QueryParameterAndBinder;
038import ca.uhn.fhir.util.ReflectionUtil;
039import ca.uhn.fhir.util.UrlUtil;
040
041public class ParameterUtil {
042
043        private static final String LABEL = "label=\"";
044        private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ParameterUtil.class);
045        private static final String SCHEME = "scheme=\"";
046
047        @SuppressWarnings("unchecked")
048        public static <T extends IIdType> T convertIdToType(IIdType value, Class<T> theIdParamType) {
049                if (value != null && !theIdParamType.isAssignableFrom(value.getClass())) {
050                        IIdType newValue = ReflectionUtil.newInstance(theIdParamType);
051                        newValue.setValue(value.getValue());
052                        value = newValue;
053                }
054                return (T) value;
055        }
056
057        /**
058         * This is a utility method intended provided to help the JPA module.
059         */
060        public static IQueryParameterAnd<?> parseQueryParams(FhirContext theContext, RestSearchParameterTypeEnum paramType,
061                        String theUnqualifiedParamName, List<QualifiedParamList> theParameters) {
062                QueryParameterAndBinder binder = null;
063                switch (paramType) {
064                case COMPOSITE:
065                        throw new UnsupportedOperationException();
066                case DATE:
067                        binder = new QueryParameterAndBinder(DateAndListParam.class,
068                                        Collections.<Class<? extends IQueryParameterType>> emptyList());
069                        break;
070                case NUMBER:
071                        binder = new QueryParameterAndBinder(NumberAndListParam.class,
072                                        Collections.<Class<? extends IQueryParameterType>> emptyList());
073                        break;
074                case QUANTITY:
075                        binder = new QueryParameterAndBinder(QuantityAndListParam.class,
076                                        Collections.<Class<? extends IQueryParameterType>> emptyList());
077                        break;
078                case REFERENCE:
079                        binder = new QueryParameterAndBinder(ReferenceAndListParam.class,
080                                        Collections.<Class<? extends IQueryParameterType>> emptyList());
081                        break;
082                case STRING:
083                        binder = new QueryParameterAndBinder(StringAndListParam.class,
084                                        Collections.<Class<? extends IQueryParameterType>> emptyList());
085                        break;
086                case TOKEN:
087                        binder = new QueryParameterAndBinder(TokenAndListParam.class,
088                                        Collections.<Class<? extends IQueryParameterType>> emptyList());
089                        break;
090                case URI:
091                        binder = new QueryParameterAndBinder(UriAndListParam.class,
092                                        Collections.<Class<? extends IQueryParameterType>> emptyList());
093                        break;
094                case HAS:
095                        binder = new QueryParameterAndBinder(HasAndListParam.class,
096                                        Collections.<Class<? extends IQueryParameterType>> emptyList());
097                        break;
098                }
099
100                // FIXME null access
101                return binder.parse(theContext, theUnqualifiedParamName, theParameters);
102        }
103
104        /**
105         * This is a utility method intended provided to help the JPA module.
106         */
107        public static IQueryParameterAnd<?> parseQueryParams(FhirContext theContext, RuntimeSearchParam theParamDef,
108                        String theUnqualifiedParamName, List<QualifiedParamList> theParameters) {
109                RestSearchParameterTypeEnum paramType = theParamDef.getParamType();
110                return parseQueryParams(theContext, paramType, theUnqualifiedParamName, theParameters);
111        }
112
113        /**
114         * Escapes a string according to the rules for parameter escaping specified in the <a href="http://www.hl7.org/implement/standards/fhir/search.html#escaping">FHIR Specification Escaping
115         * Section</a>
116         */
117        public static String escape(String theValue) {
118                if (theValue == null) {
119                        return null;
120                }
121                StringBuilder b = new StringBuilder();
122
123                for (int i = 0; i < theValue.length(); i++) {
124                        char next = theValue.charAt(i);
125                        switch (next) {
126                        case '$':
127                        case ',':
128                        case '|':
129                        case '\\':
130                                b.append('\\');
131                                break;
132                        default:
133                                break;
134                        }
135                        b.append(next);
136                }
137
138                return b.toString();
139        }
140
141        /**
142         * Escapes a string according to the rules for parameter escaping specified in the <a href="http://www.hl7.org/implement/standards/fhir/search.html#escaping">FHIR Specification Escaping
143         * Section</a>
144         */
145        public static String escapeAndUrlEncode(String theValue) {
146                if (theValue == null) {
147                        return null;
148                }
149
150                String escaped = escape(theValue);
151                return UrlUtil.escape(escaped);
152        }
153
154        /**
155         * Escapes a string according to the rules for parameter escaping specified in the <a href="http://www.hl7.org/implement/standards/fhir/search.html#escaping">FHIR Specification Escaping
156         * Section</a>
157         */
158        public static String escapeWithDefault(Object theValue) {
159                if (theValue == null) {
160                        return "";
161                }
162                return escape(theValue.toString());
163        }
164
165        public static Integer findIdParameterIndex(Method theMethod, FhirContext theContext) {
166                Integer index = findParamAnnotationIndex(theMethod, IdParam.class);
167                if (index != null) {
168                        Class<?> paramType = theMethod.getParameterTypes()[index];
169                        if (IIdType.class.equals(paramType)) {
170                                return index;
171                        }
172                        boolean isRi = theContext.getVersion().getVersion().isRi();
173                        boolean usesHapiId = IdDt.class.equals(paramType);
174                        if (isRi == usesHapiId) {
175                                throw new ConfigurationException("Method uses the wrong Id datatype (IdDt / IdType) for the given context FHIR version: " + theMethod.toString());
176                        }
177                }
178                return index;
179        }
180
181        // public static Integer findSinceParameterIndex(Method theMethod) {
182        // return findParamIndex(theMethod, Since.class);
183        // }
184
185        public static Integer findParamAnnotationIndex(Method theMethod, Class<?> toFind) {
186                int paramIndex = 0;
187                for (Annotation[] annotations : theMethod.getParameterAnnotations()) {
188                        for (int annotationIndex = 0; annotationIndex < annotations.length; annotationIndex++) {
189                                Annotation nextAnnotation = annotations[annotationIndex];
190                                Class<? extends Annotation> class1 = nextAnnotation.annotationType();
191                                if (toFind.isAssignableFrom(class1)) {
192                                        return paramIndex;
193                                }
194                        }
195                        paramIndex++;
196                }
197                return null;
198        }
199
200        public static Integer findTagListParameterIndex(Method theMethod) {
201                return findParamAnnotationIndex(theMethod, TagListParam.class);
202        }
203
204        public static Object fromInteger(Class<?> theType, IntegerDt theArgument) {
205                if (theType.equals(Integer.class)) {
206                        if (theArgument == null) {
207                                return null;
208                        }
209                        return theArgument.getValue();
210                }
211                IPrimitiveType<?> retVal = (IPrimitiveType<?>) ReflectionUtil.newInstance(theType);
212                retVal.setValueAsString(theArgument.getValueAsString());
213                return retVal;
214        }
215
216        public static boolean isBindableIntegerType(Class<?> theClass) {
217                return Integer.class.isAssignableFrom(theClass)
218                                || IPrimitiveType.class.isAssignableFrom(theClass);
219        }
220
221        public static int nonEscapedIndexOf(String theString, char theCharacter) {
222                for (int i = 0; i < theString.length(); i++) {
223                        if (theString.charAt(i) == theCharacter) {
224                                if (i == 0 || theString.charAt(i - 1) != '\\') {
225                                        return i;
226                                }
227                        }
228                }
229                return -1;
230        }
231
232        public static String parseETagValue(String value) {
233                String eTagVersion;
234                value = value.trim();
235                if (value.length() > 1) {
236                        if (value.charAt(value.length() - 1) == '"') {
237                                if (value.charAt(0) == '"') {
238                                        eTagVersion = value.substring(1, value.length() - 1);
239                                } else if (value.length() > 3 && value.charAt(0) == 'W' && value.charAt(1) == '/'
240                                                && value.charAt(2) == '"') {
241                                        eTagVersion = value.substring(3, value.length() - 1);
242                                } else {
243                                        eTagVersion = value;
244                                }
245                        } else {
246                                eTagVersion = value;
247                        }
248                } else {
249                        eTagVersion = value;
250                }
251                return eTagVersion;
252        }
253
254        public static IQueryParameterOr<?> singleton(final IQueryParameterType theParam, final String theParamName) {
255                return new IQueryParameterOr<IQueryParameterType>() {
256
257                        private static final long serialVersionUID = 1L;
258
259                        @Override
260                        public List<IQueryParameterType> getValuesAsQueryTokens() {
261                                return Collections.singletonList(theParam);
262                        }
263
264                        @Override
265                        public void setValuesAsQueryTokens(FhirContext theContext, String theParamName,
266                                        QualifiedParamList theParameters) {
267                                if (theParameters.isEmpty()) {
268                                        return;
269                                }
270                                if (theParameters.size() > 1) {
271                                        throw new IllegalArgumentException(
272                                                        "Type " + theParam.getClass().getCanonicalName() + " does not support multiple values");
273                                }
274                                theParam.setValueAsQueryToken(theContext, theParamName, theParameters.getQualifier(),
275                                                theParameters.get(0));
276                        }
277                };
278        }
279
280        static List<String> splitParameterString(String theInput, boolean theUnescapeComponents) {
281                return splitParameterString(theInput, ',', theUnescapeComponents);
282        }
283
284        static List<String> splitParameterString(String theInput, char theDelimiter, boolean theUnescapeComponents) {
285                ArrayList<String> retVal = new ArrayList<String>();
286                if (theInput != null) {
287                        StringBuilder b = new StringBuilder();
288                        for (int i = 0; i < theInput.length(); i++) {
289                                char next = theInput.charAt(i);
290                                if (next == theDelimiter) {
291                                        if (i == 0) {
292                                                b.append(next);
293                                        } else {
294                                                char prevChar = theInput.charAt(i - 1);
295                                                if (prevChar == '\\') {
296                                                        b.append(next);
297                                                } else {
298                                                        if (b.length() > 0) {
299                                                                retVal.add(b.toString());
300                                                        } else {
301                                                                retVal.add(null);
302                                                        }
303                                                        b.setLength(0);
304                                                }
305                                        }
306                                } else {
307                                        b.append(next);
308                                }
309                        }
310                        if (b.length() > 0) {
311                                retVal.add(b.toString());
312                        }
313                }
314
315                if (theUnescapeComponents) {
316                        for (int i = 0; i < retVal.size(); i++) {
317                                retVal.set(i, unescape(retVal.get(i)));
318                        }
319                }
320
321                return retVal;
322        }
323
324        public static IntegerDt toInteger(Object theArgument) {
325                if (theArgument instanceof IntegerDt) {
326                        return (IntegerDt) theArgument;
327                }
328                if (theArgument instanceof Integer) {
329                        return new IntegerDt((Integer) theArgument);
330                }
331                if (theArgument instanceof IPrimitiveType) {
332                        IPrimitiveType<?> pt = (IPrimitiveType<?>) theArgument;
333                        return new IntegerDt(pt.getValueAsString());
334                }
335                return null;
336        }
337
338        /**
339         * Unescapes a string according to the rules for parameter escaping specified in the <a href="http://www.hl7.org/implement/standards/fhir/search.html#escaping">FHIR Specification Escaping
340         * Section</a>
341         */
342        public static String unescape(String theValue) {
343                if (theValue == null) {
344                        return theValue;
345                }
346                if (theValue.indexOf('\\') == -1) {
347                        return theValue;
348                }
349
350                StringBuilder b = new StringBuilder();
351
352                for (int i = 0; i < theValue.length(); i++) {
353                        char next = theValue.charAt(i);
354                        if (next == '\\') {
355                                if (i == theValue.length() - 1) {
356                                        b.append(next);
357                                } else {
358                                        switch (theValue.charAt(i + 1)) {
359                                        case '$':
360                                        case ',':
361                                        case '|':
362                                        case '\\':
363                                                continue;
364                                        default:
365                                                b.append(next);
366                                        }
367                                }
368                        } else {
369                                b.append(next);
370                        }
371                }
372
373                return b.toString();
374        }
375
376}