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 - 2019 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 = null; 066 switch (paramType) { 067 case COMPOSITE: 068 throw new UnsupportedOperationException(); 069 case DATE: 070 binder = new QueryParameterAndBinder(DateAndListParam.class, 071 Collections.<Class<? extends IQueryParameterType>> emptyList()); 072 break; 073 case NUMBER: 074 binder = new QueryParameterAndBinder(NumberAndListParam.class, 075 Collections.<Class<? extends IQueryParameterType>> emptyList()); 076 break; 077 case QUANTITY: 078 binder = new QueryParameterAndBinder(QuantityAndListParam.class, 079 Collections.<Class<? extends IQueryParameterType>> emptyList()); 080 break; 081 case REFERENCE: 082 binder = new QueryParameterAndBinder(ReferenceAndListParam.class, 083 Collections.<Class<? extends IQueryParameterType>> emptyList()); 084 break; 085 case STRING: 086 binder = new QueryParameterAndBinder(StringAndListParam.class, 087 Collections.<Class<? extends IQueryParameterType>> emptyList()); 088 break; 089 case TOKEN: 090 binder = new QueryParameterAndBinder(TokenAndListParam.class, 091 Collections.<Class<? extends IQueryParameterType>> emptyList()); 092 break; 093 case URI: 094 binder = new QueryParameterAndBinder(UriAndListParam.class, 095 Collections.<Class<? extends IQueryParameterType>> emptyList()); 096 break; 097 case HAS: 098 binder = new QueryParameterAndBinder(HasAndListParam.class, 099 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}