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 - 2020 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; 066 switch (paramType) { 067 case COMPOSITE: 068 throw new UnsupportedOperationException(); 069 case DATE: 070 binder = new QueryParameterAndBinder(DateAndListParam.class, 071 Collections.emptyList()); 072 break; 073 case NUMBER: 074 binder = new QueryParameterAndBinder(NumberAndListParam.class, 075 Collections.emptyList()); 076 break; 077 case QUANTITY: 078 binder = new QueryParameterAndBinder(QuantityAndListParam.class, 079 Collections.emptyList()); 080 break; 081 case REFERENCE: 082 binder = new QueryParameterAndBinder(ReferenceAndListParam.class, 083 Collections.emptyList()); 084 break; 085 case STRING: 086 binder = new QueryParameterAndBinder(StringAndListParam.class, 087 Collections.emptyList()); 088 break; 089 case TOKEN: 090 binder = new QueryParameterAndBinder(TokenAndListParam.class, 091 Collections.emptyList()); 092 break; 093 case URI: 094 binder = new QueryParameterAndBinder(UriAndListParam.class, 095 Collections.emptyList()); 096 break; 097 case HAS: 098 binder = new QueryParameterAndBinder(HasAndListParam.class, 099 Collections.emptyList()); 100 break; 101 case SPECIAL: 102 binder = new QueryParameterAndBinder(SpecialAndListParam.class, 103 Collections.emptyList()); 104 break; 105 default: 106 throw new IllegalArgumentException("Parameter '" + theUnqualifiedParamName + "' has type " + paramType + " which is currently not supported."); 107 } 108 109 return binder.parse(theContext, theUnqualifiedParamName, theParameters); 110 } 111 112 /** 113 * This is a utility method intended provided to help the JPA module. 114 */ 115 public static IQueryParameterAnd<?> parseQueryParams(FhirContext theContext, RuntimeSearchParam theParamDef, 116 String theUnqualifiedParamName, List<QualifiedParamList> theParameters) { 117 RestSearchParameterTypeEnum paramType = theParamDef.getParamType(); 118 return parseQueryParams(theContext, paramType, theUnqualifiedParamName, theParameters); 119 } 120 121 /** 122 * 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 123 * Section</a> 124 */ 125 public static String escape(String theValue) { 126 if (theValue == null) { 127 return null; 128 } 129 StringBuilder b = new StringBuilder(); 130 131 for (int i = 0; i < theValue.length(); i++) { 132 char next = theValue.charAt(i); 133 switch (next) { 134 case '$': 135 case ',': 136 case '|': 137 case '\\': 138 b.append('\\'); 139 break; 140 default: 141 break; 142 } 143 b.append(next); 144 } 145 146 return b.toString(); 147 } 148 149 /** 150 * 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 151 * Section</a> 152 */ 153 public static String escapeWithDefault(Object theValue) { 154 if (theValue == null) { 155 return ""; 156 } 157 return escape(theValue.toString()); 158 } 159 160 /** 161 * Applies {@link #escapeWithDefault(Object)} followed by {@link UrlUtil#escapeUrlParam(String)} 162 */ 163 public static String escapeAndUrlEncode(String theInput) { 164 return UrlUtil.escapeUrlParam(escapeWithDefault(theInput)); 165 } 166 167 public static Integer findIdParameterIndex(Method theMethod, FhirContext theContext) { 168 Integer index = findParamAnnotationIndex(theMethod, IdParam.class); 169 if (index != null) { 170 Class<?> paramType = theMethod.getParameterTypes()[index]; 171 if (IIdType.class.equals(paramType)) { 172 return index; 173 } 174 boolean isRi = theContext.getVersion().getVersion().isRi(); 175 boolean usesHapiId = IdDt.class.equals(paramType); 176 if (isRi == usesHapiId) { 177 throw new ConfigurationException("Method uses the wrong Id datatype (IdDt / IdType) for the given context FHIR version: " + theMethod.toString()); 178 } 179 } 180 return index; 181 } 182 183 // public static Integer findSinceParameterIndex(Method theMethod) { 184 // return findParamIndex(theMethod, Since.class); 185 // } 186 187 public static Integer findParamAnnotationIndex(Method theMethod, Class<?> toFind) { 188 int paramIndex = 0; 189 for (Annotation[] annotations : theMethod.getParameterAnnotations()) { 190 for (Annotation nextAnnotation : annotations) { 191 Class<? extends Annotation> class1 = nextAnnotation.annotationType(); 192 if (toFind.isAssignableFrom(class1)) { 193 return paramIndex; 194 } 195 } 196 paramIndex++; 197 } 198 return null; 199 } 200 201 public static Object fromInteger(Class<?> theType, IntegerDt theArgument) { 202 if (theArgument == null) { 203 return null; 204 } 205 if (theType.equals(Integer.class)) { 206 return theArgument.getValue(); 207 } 208 IPrimitiveType<?> retVal = (IPrimitiveType<?>) ReflectionUtil.newInstance(theType); 209 retVal.setValueAsString(theArgument.getValueAsString()); 210 return retVal; 211 } 212 213 public static boolean isBindableIntegerType(Class<?> theClass) { 214 return Integer.class.isAssignableFrom(theClass) 215 || IPrimitiveType.class.isAssignableFrom(theClass); 216 } 217 218 public static String escapeAndJoinOrList(Collection<String> theValues) { 219 return theValues 220 .stream() 221 .map(ParameterUtil::escape) 222 .collect(Collectors.joining(",")); 223 } 224 225 public static int nonEscapedIndexOf(String theString, char theCharacter) { 226 for (int i = 0; i < theString.length(); i++) { 227 if (theString.charAt(i) == theCharacter) { 228 if (i == 0 || theString.charAt(i - 1) != '\\') { 229 return i; 230 } 231 } 232 } 233 return -1; 234 } 235 236 public static String parseETagValue(String value) { 237 String eTagVersion; 238 value = value.trim(); 239 if (value.length() > 1) { 240 if (value.charAt(value.length() - 1) == '"') { 241 if (value.charAt(0) == '"') { 242 eTagVersion = value.substring(1, value.length() - 1); 243 } else if (value.length() > 3 && value.charAt(0) == 'W' && value.charAt(1) == '/' 244 && value.charAt(2) == '"') { 245 eTagVersion = value.substring(3, value.length() - 1); 246 } else { 247 eTagVersion = value; 248 } 249 } else { 250 eTagVersion = value; 251 } 252 } else { 253 eTagVersion = value; 254 } 255 return eTagVersion; 256 } 257 258 public static IQueryParameterOr<?> singleton(final IQueryParameterType theParam, final String theParamName) { 259 return new IQueryParameterOr<IQueryParameterType>() { 260 261 private static final long serialVersionUID = 1L; 262 263 @Override 264 public List<IQueryParameterType> getValuesAsQueryTokens() { 265 return Collections.singletonList(theParam); 266 } 267 268 @Override 269 public void setValuesAsQueryTokens(FhirContext theContext, String theParamName, 270 QualifiedParamList theParameters) { 271 if (theParameters.isEmpty()) { 272 return; 273 } 274 if (theParameters.size() > 1) { 275 throw new IllegalArgumentException( 276 "Type " + theParam.getClass().getCanonicalName() + " does not support multiple values"); 277 } 278 theParam.setValueAsQueryToken(theContext, theParamName, theParameters.getQualifier(), 279 theParameters.get(0)); 280 } 281 }; 282 } 283 284 static List<String> splitParameterString(String theInput, char theDelimiter, boolean theUnescapeComponents) { 285 ArrayList<String> retVal = new ArrayList<>(); 286 if (theInput != null) { 287 StringBuilder b = new StringBuilder(); 288 for (int i = 0; i < theInput.length(); i++) { 289 char next = theInput.charAt(i); 290 if (next == theDelimiter) { 291 if (i == 0) { 292 b.append(next); 293 } else { 294 char prevChar = theInput.charAt(i - 1); 295 if (prevChar == '\\') { 296 b.append(next); 297 } else { 298 if (b.length() > 0) { 299 retVal.add(b.toString()); 300 } else { 301 retVal.add(null); 302 } 303 b.setLength(0); 304 } 305 } 306 } else { 307 b.append(next); 308 } 309 } 310 if (b.length() > 0) { 311 retVal.add(b.toString()); 312 } 313 } 314 315 if (theUnescapeComponents) { 316 for (int i = 0; i < retVal.size(); i++) { 317 retVal.set(i, unescape(retVal.get(i))); 318 } 319 } 320 321 return retVal; 322 } 323 324 public static IntegerDt toInteger(Object theArgument) { 325 if (theArgument instanceof IntegerDt) { 326 return (IntegerDt) theArgument; 327 } 328 if (theArgument instanceof Integer) { 329 return new IntegerDt((Integer) theArgument); 330 } 331 if (theArgument instanceof IPrimitiveType) { 332 IPrimitiveType<?> pt = (IPrimitiveType<?>) theArgument; 333 return new IntegerDt(pt.getValueAsString()); 334 } 335 return null; 336 } 337 338 /** 339 * 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 340 * Section</a> 341 */ 342 public static String unescape(String theValue) { 343 if (theValue == null) { 344 return null; 345 } 346 if (theValue.indexOf('\\') == -1) { 347 return theValue; 348 } 349 350 StringBuilder b = new StringBuilder(); 351 352 for (int i = 0; i < theValue.length(); i++) { 353 char next = theValue.charAt(i); 354 if (next == '\\') { 355 if (i == theValue.length() - 1) { 356 b.append(next); 357 } else { 358 switch (theValue.charAt(i + 1)) { 359 case '$': 360 case ',': 361 case '|': 362 case '\\': 363 continue; 364 default: 365 b.append(next); 366 } 367 } 368 } else { 369 b.append(next); 370 } 371 } 372 373 return b.toString(); 374 } 375 376}