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