001package ca.uhn.fhir.rest.param; 002 003import java.lang.annotation.Annotation; 004import java.lang.reflect.Method; 005/* 006 * #%L 007 * HAPI FHIR - Core Library 008 * %% 009 * Copyright (C) 2014 - 2017 University Health Network 010 * %% 011 * Licensed under the Apache License, Version 2.0 (the "License"); 012 * you may not use this file except in compliance with the License. 013 * You may obtain a copy of the License at 014 * 015 * http://www.apache.org/licenses/LICENSE-2.0 016 * 017 * Unless required by applicable law or agreed to in writing, software 018 * distributed under the License is distributed on an "AS IS" BASIS, 019 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 020 * See the License for the specific language governing permissions and 021 * limitations under the License. 022 * #L% 023 */ 024import java.util.*; 025 026import org.hl7.fhir.instance.model.api.IIdType; 027import org.hl7.fhir.instance.model.api.IPrimitiveType; 028 029import ca.uhn.fhir.context.*; 030import ca.uhn.fhir.model.api.*; 031import ca.uhn.fhir.model.primitive.IdDt; 032import ca.uhn.fhir.model.primitive.IntegerDt; 033import ca.uhn.fhir.rest.annotation.IdParam; 034import ca.uhn.fhir.rest.annotation.TagListParam; 035import ca.uhn.fhir.rest.api.QualifiedParamList; 036import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum; 037import ca.uhn.fhir.rest.param.binder.QueryParameterAndBinder; 038import ca.uhn.fhir.util.ReflectionUtil; 039import ca.uhn.fhir.util.UrlUtil; 040 041public class ParameterUtil { 042 043 private static final String LABEL = "label=\""; 044 private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ParameterUtil.class); 045 private static final String SCHEME = "scheme=\""; 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 * This is a utility method intended provided to help the JPA module. 059 */ 060 public static IQueryParameterAnd<?> parseQueryParams(FhirContext theContext, RestSearchParameterTypeEnum paramType, 061 String theUnqualifiedParamName, List<QualifiedParamList> theParameters) { 062 QueryParameterAndBinder binder = null; 063 switch (paramType) { 064 case COMPOSITE: 065 throw new UnsupportedOperationException(); 066 case DATE: 067 binder = new QueryParameterAndBinder(DateAndListParam.class, 068 Collections.<Class<? extends IQueryParameterType>> emptyList()); 069 break; 070 case NUMBER: 071 binder = new QueryParameterAndBinder(NumberAndListParam.class, 072 Collections.<Class<? extends IQueryParameterType>> emptyList()); 073 break; 074 case QUANTITY: 075 binder = new QueryParameterAndBinder(QuantityAndListParam.class, 076 Collections.<Class<? extends IQueryParameterType>> emptyList()); 077 break; 078 case REFERENCE: 079 binder = new QueryParameterAndBinder(ReferenceAndListParam.class, 080 Collections.<Class<? extends IQueryParameterType>> emptyList()); 081 break; 082 case STRING: 083 binder = new QueryParameterAndBinder(StringAndListParam.class, 084 Collections.<Class<? extends IQueryParameterType>> emptyList()); 085 break; 086 case TOKEN: 087 binder = new QueryParameterAndBinder(TokenAndListParam.class, 088 Collections.<Class<? extends IQueryParameterType>> emptyList()); 089 break; 090 case URI: 091 binder = new QueryParameterAndBinder(UriAndListParam.class, 092 Collections.<Class<? extends IQueryParameterType>> emptyList()); 093 break; 094 case HAS: 095 binder = new QueryParameterAndBinder(HasAndListParam.class, 096 Collections.<Class<? extends IQueryParameterType>> emptyList()); 097 break; 098 } 099 100 // FIXME null access 101 return binder.parse(theContext, theUnqualifiedParamName, theParameters); 102 } 103 104 /** 105 * This is a utility method intended provided to help the JPA module. 106 */ 107 public static IQueryParameterAnd<?> parseQueryParams(FhirContext theContext, RuntimeSearchParam theParamDef, 108 String theUnqualifiedParamName, List<QualifiedParamList> theParameters) { 109 RestSearchParameterTypeEnum paramType = theParamDef.getParamType(); 110 return parseQueryParams(theContext, paramType, theUnqualifiedParamName, theParameters); 111 } 112 113 /** 114 * 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 115 * Section</a> 116 */ 117 public static String escape(String theValue) { 118 if (theValue == null) { 119 return null; 120 } 121 StringBuilder b = new StringBuilder(); 122 123 for (int i = 0; i < theValue.length(); i++) { 124 char next = theValue.charAt(i); 125 switch (next) { 126 case '$': 127 case ',': 128 case '|': 129 case '\\': 130 b.append('\\'); 131 break; 132 default: 133 break; 134 } 135 b.append(next); 136 } 137 138 return b.toString(); 139 } 140 141 /** 142 * 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 143 * Section</a> 144 */ 145 public static String escapeAndUrlEncode(String theValue) { 146 if (theValue == null) { 147 return null; 148 } 149 150 String escaped = escape(theValue); 151 return UrlUtil.escape(escaped); 152 } 153 154 /** 155 * 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 156 * Section</a> 157 */ 158 public static String escapeWithDefault(Object theValue) { 159 if (theValue == null) { 160 return ""; 161 } 162 return escape(theValue.toString()); 163 } 164 165 public static Integer findIdParameterIndex(Method theMethod, FhirContext theContext) { 166 Integer index = findParamAnnotationIndex(theMethod, IdParam.class); 167 if (index != null) { 168 Class<?> paramType = theMethod.getParameterTypes()[index]; 169 if (IIdType.class.equals(paramType)) { 170 return index; 171 } 172 boolean isRi = theContext.getVersion().getVersion().isRi(); 173 boolean usesHapiId = IdDt.class.equals(paramType); 174 if (isRi == usesHapiId) { 175 throw new ConfigurationException("Method uses the wrong Id datatype (IdDt / IdType) for the given context FHIR version: " + theMethod.toString()); 176 } 177 } 178 return index; 179 } 180 181 // public static Integer findSinceParameterIndex(Method theMethod) { 182 // return findParamIndex(theMethod, Since.class); 183 // } 184 185 public static Integer findParamAnnotationIndex(Method theMethod, Class<?> toFind) { 186 int paramIndex = 0; 187 for (Annotation[] annotations : theMethod.getParameterAnnotations()) { 188 for (int annotationIndex = 0; annotationIndex < annotations.length; annotationIndex++) { 189 Annotation nextAnnotation = annotations[annotationIndex]; 190 Class<? extends Annotation> class1 = nextAnnotation.annotationType(); 191 if (toFind.isAssignableFrom(class1)) { 192 return paramIndex; 193 } 194 } 195 paramIndex++; 196 } 197 return null; 198 } 199 200 public static Integer findTagListParameterIndex(Method theMethod) { 201 return findParamAnnotationIndex(theMethod, TagListParam.class); 202 } 203 204 public static Object fromInteger(Class<?> theType, IntegerDt theArgument) { 205 if (theType.equals(Integer.class)) { 206 if (theArgument == null) { 207 return null; 208 } 209 return theArgument.getValue(); 210 } 211 IPrimitiveType<?> retVal = (IPrimitiveType<?>) ReflectionUtil.newInstance(theType); 212 retVal.setValueAsString(theArgument.getValueAsString()); 213 return retVal; 214 } 215 216 public static boolean isBindableIntegerType(Class<?> theClass) { 217 return Integer.class.isAssignableFrom(theClass) 218 || IPrimitiveType.class.isAssignableFrom(theClass); 219 } 220 221 public static int nonEscapedIndexOf(String theString, char theCharacter) { 222 for (int i = 0; i < theString.length(); i++) { 223 if (theString.charAt(i) == theCharacter) { 224 if (i == 0 || theString.charAt(i - 1) != '\\') { 225 return i; 226 } 227 } 228 } 229 return -1; 230 } 231 232 public static String parseETagValue(String value) { 233 String eTagVersion; 234 value = value.trim(); 235 if (value.length() > 1) { 236 if (value.charAt(value.length() - 1) == '"') { 237 if (value.charAt(0) == '"') { 238 eTagVersion = value.substring(1, value.length() - 1); 239 } else if (value.length() > 3 && value.charAt(0) == 'W' && value.charAt(1) == '/' 240 && value.charAt(2) == '"') { 241 eTagVersion = value.substring(3, value.length() - 1); 242 } else { 243 eTagVersion = value; 244 } 245 } else { 246 eTagVersion = value; 247 } 248 } else { 249 eTagVersion = value; 250 } 251 return eTagVersion; 252 } 253 254 public static IQueryParameterOr<?> singleton(final IQueryParameterType theParam, final String theParamName) { 255 return new IQueryParameterOr<IQueryParameterType>() { 256 257 private static final long serialVersionUID = 1L; 258 259 @Override 260 public List<IQueryParameterType> getValuesAsQueryTokens() { 261 return Collections.singletonList(theParam); 262 } 263 264 @Override 265 public void setValuesAsQueryTokens(FhirContext theContext, String theParamName, 266 QualifiedParamList theParameters) { 267 if (theParameters.isEmpty()) { 268 return; 269 } 270 if (theParameters.size() > 1) { 271 throw new IllegalArgumentException( 272 "Type " + theParam.getClass().getCanonicalName() + " does not support multiple values"); 273 } 274 theParam.setValueAsQueryToken(theContext, theParamName, theParameters.getQualifier(), 275 theParameters.get(0)); 276 } 277 }; 278 } 279 280 static List<String> splitParameterString(String theInput, boolean theUnescapeComponents) { 281 return splitParameterString(theInput, ',', theUnescapeComponents); 282 } 283 284 static List<String> splitParameterString(String theInput, char theDelimiter, boolean theUnescapeComponents) { 285 ArrayList<String> retVal = new ArrayList<String>(); 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 theValue; 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}