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