View Javadoc
1   package ca.uhn.fhir.rest.param;
2   
3   import ca.uhn.fhir.context.ConfigurationException;
4   import ca.uhn.fhir.context.FhirContext;
5   import ca.uhn.fhir.context.RuntimeSearchParam;
6   import ca.uhn.fhir.model.api.IQueryParameterAnd;
7   import ca.uhn.fhir.model.api.IQueryParameterOr;
8   import ca.uhn.fhir.model.api.IQueryParameterType;
9   import ca.uhn.fhir.model.primitive.IdDt;
10  import ca.uhn.fhir.model.primitive.IntegerDt;
11  import ca.uhn.fhir.rest.annotation.IdParam;
12  import ca.uhn.fhir.rest.api.QualifiedParamList;
13  import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
14  import ca.uhn.fhir.rest.param.binder.QueryParameterAndBinder;
15  import ca.uhn.fhir.util.ReflectionUtil;
16  import ca.uhn.fhir.util.UrlUtil;
17  import org.hl7.fhir.instance.model.api.IIdType;
18  import org.hl7.fhir.instance.model.api.IPrimitiveType;
19  
20  import java.lang.annotation.Annotation;
21  import java.lang.reflect.Method;
22  import java.util.ArrayList;
23  import java.util.Collection;
24  import java.util.Collections;
25  import java.util.List;
26  import java.util.stream.Collectors;
27  
28  /*
29   * #%L
30   * HAPI FHIR - Core Library
31   * %%
32   * Copyright (C) 2014 - 2019 University Health Network
33   * %%
34   * Licensed under the Apache License, Version 2.0 (the "License");
35   * you may not use this file except in compliance with the License.
36   * You may obtain a copy of the License at
37   * 
38   * http://www.apache.org/licenses/LICENSE-2.0
39   * 
40   * Unless required by applicable law or agreed to in writing, software
41   * distributed under the License is distributed on an "AS IS" BASIS,
42   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
43   * See the License for the specific language governing permissions and
44   * limitations under the License.
45   * #L%
46   */
47  
48  public class ParameterUtil {
49  
50  	@SuppressWarnings("unchecked")
51  	public static <T extends IIdType> T convertIdToType(IIdType value, Class<T> theIdParamType) {
52  		if (value != null && !theIdParamType.isAssignableFrom(value.getClass())) {
53  			IIdType newValue = ReflectionUtil.newInstance(theIdParamType);
54  			newValue.setValue(value.getValue());
55  			value = newValue;
56  		}
57  		return (T) value;
58  	}
59  
60  	/**
61  	 * This is a utility method intended provided to help the JPA module.
62  	 */
63  	public static IQueryParameterAnd<?> parseQueryParams(FhirContext theContext, RestSearchParameterTypeEnum paramType,
64  			String theUnqualifiedParamName, List<QualifiedParamList> theParameters) {
65  		QueryParameterAndBinder binder = null;
66  		switch (paramType) {
67  		case COMPOSITE:
68  			throw new UnsupportedOperationException();
69  		case DATE:
70  			binder = new QueryParameterAndBinder(DateAndListParam.class,
71  					Collections.<Class<? extends IQueryParameterType>> emptyList());
72  			break;
73  		case NUMBER:
74  			binder = new QueryParameterAndBinder(NumberAndListParam.class,
75  					Collections.<Class<? extends IQueryParameterType>> emptyList());
76  			break;
77  		case QUANTITY:
78  			binder = new QueryParameterAndBinder(QuantityAndListParam.class,
79  					Collections.<Class<? extends IQueryParameterType>> emptyList());
80  			break;
81  		case REFERENCE:
82  			binder = new QueryParameterAndBinder(ReferenceAndListParam.class,
83  					Collections.<Class<? extends IQueryParameterType>> emptyList());
84  			break;
85  		case STRING:
86  			binder = new QueryParameterAndBinder(StringAndListParam.class,
87  					Collections.<Class<? extends IQueryParameterType>> emptyList());
88  			break;
89  		case TOKEN:
90  			binder = new QueryParameterAndBinder(TokenAndListParam.class,
91  					Collections.<Class<? extends IQueryParameterType>> emptyList());
92  			break;
93  		case URI:
94  			binder = new QueryParameterAndBinder(UriAndListParam.class,
95  					Collections.<Class<? extends IQueryParameterType>> emptyList());
96  			break;
97  		case HAS:
98  			binder = new QueryParameterAndBinder(HasAndListParam.class,
99  					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 }