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.Collections; 024import java.util.List; 025 026/* 027 * #%L 028 * HAPI FHIR - Core Library 029 * %% 030 * Copyright (C) 2014 - 2018 University Health Network 031 * %% 032 * Licensed under the Apache License, Version 2.0 (the "License"); 033 * you may not use this file except in compliance with the License. 034 * You may obtain a copy of the License at 035 * 036 * http://www.apache.org/licenses/LICENSE-2.0 037 * 038 * Unless required by applicable law or agreed to in writing, software 039 * distributed under the License is distributed on an "AS IS" BASIS, 040 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 041 * See the License for the specific language governing permissions and 042 * limitations under the License. 043 * #L% 044 */ 045 046public class ParameterUtil { 047 048 @SuppressWarnings("unchecked") 049 public static <T extends IIdType> T convertIdToType(IIdType value, Class<T> theIdParamType) { 050 if (value != null && !theIdParamType.isAssignableFrom(value.getClass())) { 051 IIdType newValue = ReflectionUtil.newInstance(theIdParamType); 052 newValue.setValue(value.getValue()); 053 value = newValue; 054 } 055 return (T) value; 056 } 057 058 /** 059 * This is a utility method intended provided to help the JPA module. 060 */ 061 public static IQueryParameterAnd<?> parseQueryParams(FhirContext theContext, RestSearchParameterTypeEnum paramType, 062 String theUnqualifiedParamName, List<QualifiedParamList> theParameters) { 063 QueryParameterAndBinder binder = null; 064 switch (paramType) { 065 case COMPOSITE: 066 throw new UnsupportedOperationException(); 067 case DATE: 068 binder = new QueryParameterAndBinder(DateAndListParam.class, 069 Collections.<Class<? extends IQueryParameterType>> emptyList()); 070 break; 071 case NUMBER: 072 binder = new QueryParameterAndBinder(NumberAndListParam.class, 073 Collections.<Class<? extends IQueryParameterType>> emptyList()); 074 break; 075 case QUANTITY: 076 binder = new QueryParameterAndBinder(QuantityAndListParam.class, 077 Collections.<Class<? extends IQueryParameterType>> emptyList()); 078 break; 079 case REFERENCE: 080 binder = new QueryParameterAndBinder(ReferenceAndListParam.class, 081 Collections.<Class<? extends IQueryParameterType>> emptyList()); 082 break; 083 case STRING: 084 binder = new QueryParameterAndBinder(StringAndListParam.class, 085 Collections.<Class<? extends IQueryParameterType>> emptyList()); 086 break; 087 case TOKEN: 088 binder = new QueryParameterAndBinder(TokenAndListParam.class, 089 Collections.<Class<? extends IQueryParameterType>> emptyList()); 090 break; 091 case URI: 092 binder = new QueryParameterAndBinder(UriAndListParam.class, 093 Collections.<Class<? extends IQueryParameterType>> emptyList()); 094 break; 095 case HAS: 096 binder = new QueryParameterAndBinder(HasAndListParam.class, 097 Collections.<Class<? extends IQueryParameterType>> emptyList()); 098 break; 099 } 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}