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