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