001/* 002 * #%L 003 * HAPI FHIR - Core Library 004 * %% 005 * Copyright (C) 2014 - 2024 Smile CDR, Inc. 006 * %% 007 * Licensed under the Apache License, Version 2.0 (the "License"); 008 * you may not use this file except in compliance with the License. 009 * You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, software 014 * distributed under the License is distributed on an "AS IS" BASIS, 015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 * #L% 019 */ 020package ca.uhn.fhir.rest.param; 021 022import ca.uhn.fhir.context.ConfigurationException; 023import ca.uhn.fhir.context.FhirContext; 024import ca.uhn.fhir.i18n.Msg; 025import ca.uhn.fhir.model.api.IQueryParameterOr; 026import ca.uhn.fhir.model.api.IQueryParameterType; 027import ca.uhn.fhir.model.primitive.IdDt; 028import ca.uhn.fhir.model.primitive.IntegerDt; 029import ca.uhn.fhir.rest.annotation.IdParam; 030import ca.uhn.fhir.rest.api.Constants; 031import ca.uhn.fhir.rest.api.QualifiedParamList; 032import ca.uhn.fhir.util.ReflectionUtil; 033import ca.uhn.fhir.util.UrlUtil; 034import org.hl7.fhir.instance.model.api.IIdType; 035import org.hl7.fhir.instance.model.api.IPrimitiveType; 036 037import java.lang.annotation.Annotation; 038import java.lang.reflect.Method; 039import java.util.ArrayList; 040import java.util.Collection; 041import java.util.Collections; 042import java.util.List; 043import java.util.stream.Collectors; 044 045public class ParameterUtil { 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 * Removes :modifiers and .chains from URL parameter names 059 */ 060 public static String stripModifierPart(String theParam) { 061 for (int i = 0; i < theParam.length(); i++) { 062 char nextChar = theParam.charAt(i); 063 if (nextChar == ':' || nextChar == '.') { 064 return theParam.substring(0, i); 065 } 066 } 067 return theParam; 068 } 069 070 /** 071 * 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 072 * Section</a> 073 */ 074 public static String escape(String theValue) { 075 if (theValue == null) { 076 return null; 077 } 078 StringBuilder b = new StringBuilder(); 079 080 for (int i = 0; i < theValue.length(); i++) { 081 char next = theValue.charAt(i); 082 switch (next) { 083 case '$': 084 case ',': 085 case '|': 086 case '\\': 087 b.append('\\'); 088 break; 089 default: 090 break; 091 } 092 b.append(next); 093 } 094 095 return b.toString(); 096 } 097 098 /** 099 * 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 100 * Section</a> 101 */ 102 public static String escapeWithDefault(Object theValue) { 103 if (theValue == null) { 104 return ""; 105 } 106 return escape(theValue.toString()); 107 } 108 109 /** 110 * Applies {@link #escapeWithDefault(Object)} followed by {@link UrlUtil#escapeUrlParam(String)} 111 */ 112 public static String escapeAndUrlEncode(String theInput) { 113 return UrlUtil.escapeUrlParam(escapeWithDefault(theInput)); 114 } 115 116 public static Integer findIdParameterIndex(Method theMethod, FhirContext theContext) { 117 Integer index = findParamAnnotationIndex(theMethod, IdParam.class); 118 if (index != null) { 119 Class<?> paramType = theMethod.getParameterTypes()[index]; 120 if (IIdType.class.equals(paramType)) { 121 return index; 122 } 123 boolean isRi = theContext.getVersion().getVersion().isRi(); 124 boolean usesHapiId = IdDt.class.equals(paramType); 125 if (isRi == usesHapiId) { 126 throw new ConfigurationException(Msg.code(1936) 127 + "Method uses the wrong Id datatype (IdDt / IdType) for the given context FHIR version: " 128 + theMethod.toString()); 129 } 130 } 131 return index; 132 } 133 134 // public static Integer findSinceParameterIndex(Method theMethod) { 135 // return findParamIndex(theMethod, Since.class); 136 // } 137 138 public static Integer findParamAnnotationIndex(Method theMethod, Class<?> toFind) { 139 int paramIndex = 0; 140 for (Annotation[] annotations : theMethod.getParameterAnnotations()) { 141 for (Annotation nextAnnotation : annotations) { 142 Class<? extends Annotation> class1 = nextAnnotation.annotationType(); 143 if (toFind.isAssignableFrom(class1)) { 144 return paramIndex; 145 } 146 } 147 paramIndex++; 148 } 149 return null; 150 } 151 152 public static Object fromInteger(Class<?> theType, IntegerDt theArgument) { 153 if (theArgument == null) { 154 return null; 155 } 156 if (theType.equals(Integer.class)) { 157 return theArgument.getValue(); 158 } 159 IPrimitiveType<?> retVal = (IPrimitiveType<?>) ReflectionUtil.newInstance(theType); 160 retVal.setValueAsString(theArgument.getValueAsString()); 161 return retVal; 162 } 163 164 public static boolean isBindableIntegerType(Class<?> theClass) { 165 return Integer.class.isAssignableFrom(theClass) || IPrimitiveType.class.isAssignableFrom(theClass); 166 } 167 168 public static String escapeAndJoinOrList(Collection<String> theValues) { 169 return theValues.stream().map(ParameterUtil::escape).collect(Collectors.joining(",")); 170 } 171 172 public static int nonEscapedIndexOf(String theString, char theCharacter) { 173 for (int i = 0; i < theString.length(); i++) { 174 if (theString.charAt(i) == theCharacter) { 175 if (i == 0 || theString.charAt(i - 1) != '\\') { 176 return i; 177 } 178 } 179 } 180 return -1; 181 } 182 183 public static String parseETagValue(String value) { 184 String eTagVersion; 185 value = value.trim(); 186 if (value.length() > 1) { 187 if (value.charAt(value.length() - 1) == '"') { 188 if (value.charAt(0) == '"') { 189 eTagVersion = value.substring(1, value.length() - 1); 190 } else if (value.length() > 3 191 && value.charAt(0) == 'W' 192 && value.charAt(1) == '/' 193 && value.charAt(2) == '"') { 194 eTagVersion = value.substring(3, value.length() - 1); 195 } else { 196 eTagVersion = value; 197 } 198 } else { 199 eTagVersion = value; 200 } 201 } else { 202 eTagVersion = value; 203 } 204 return eTagVersion; 205 } 206 207 public static IQueryParameterOr<?> singleton(final IQueryParameterType theParam, final String theParamName) { 208 return new IQueryParameterOr<IQueryParameterType>() { 209 210 private static final long serialVersionUID = 1L; 211 212 @Override 213 public List<IQueryParameterType> getValuesAsQueryTokens() { 214 return Collections.singletonList(theParam); 215 } 216 217 @Override 218 public void setValuesAsQueryTokens( 219 FhirContext theContext, String theParamName, QualifiedParamList theParameters) { 220 if (theParameters.isEmpty()) { 221 return; 222 } 223 if (theParameters.size() > 1) { 224 throw new IllegalArgumentException(Msg.code(1937) + "Type " 225 + theParam.getClass().getCanonicalName() + " does not support multiple values"); 226 } 227 theParam.setValueAsQueryToken( 228 theContext, theParamName, theParameters.getQualifier(), theParameters.get(0)); 229 } 230 }; 231 } 232 233 static List<String> splitParameterString(String theInput, char theDelimiter, boolean theUnescapeComponents) { 234 ArrayList<String> retVal = new ArrayList<>(); 235 if (theInput != null) { 236 StringBuilder b = new StringBuilder(); 237 for (int i = 0; i < theInput.length(); i++) { 238 char next = theInput.charAt(i); 239 if (next == theDelimiter) { 240 if (i == 0) { 241 b.append(next); 242 } else { 243 char prevChar = theInput.charAt(i - 1); 244 if (prevChar == '\\') { 245 b.append(next); 246 } else { 247 if (b.length() > 0) { 248 retVal.add(b.toString()); 249 } else { 250 retVal.add(null); 251 } 252 b.setLength(0); 253 } 254 } 255 } else { 256 b.append(next); 257 } 258 } 259 if (b.length() > 0) { 260 retVal.add(b.toString()); 261 } 262 } 263 264 if (theUnescapeComponents) { 265 for (int i = 0; i < retVal.size(); i++) { 266 retVal.set(i, unescape(retVal.get(i))); 267 } 268 } 269 270 return retVal; 271 } 272 273 public static IntegerDt toInteger(Object theArgument) { 274 if (theArgument instanceof IntegerDt) { 275 return (IntegerDt) theArgument; 276 } 277 if (theArgument instanceof Integer) { 278 return new IntegerDt((Integer) theArgument); 279 } 280 if (theArgument instanceof IPrimitiveType) { 281 IPrimitiveType<?> pt = (IPrimitiveType<?>) theArgument; 282 return new IntegerDt(pt.getValueAsString()); 283 } 284 return null; 285 } 286 287 /** 288 * 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 289 * Section</a> 290 */ 291 public static String unescape(String theValue) { 292 if (theValue == null) { 293 return null; 294 } 295 if (theValue.indexOf('\\') == -1) { 296 return theValue; 297 } 298 299 StringBuilder b = new StringBuilder(); 300 301 for (int i = 0; i < theValue.length(); i++) { 302 char next = theValue.charAt(i); 303 if (next == '\\') { 304 if (i == theValue.length() - 1) { 305 b.append(next); 306 } else { 307 switch (theValue.charAt(i + 1)) { 308 case '$': 309 case ',': 310 case '|': 311 case '\\': 312 continue; 313 default: 314 b.append(next); 315 } 316 } 317 } else { 318 b.append(next); 319 } 320 } 321 322 return b.toString(); 323 } 324 325 /** 326 * Returns true if the value is :iterate or :recurse (the former name of :iterate) for an _include parameter 327 */ 328 public static boolean isIncludeIterate(String theQualifier) { 329 return Constants.PARAM_INCLUDE_QUALIFIER_RECURSE.equals(theQualifier) 330 || Constants.PARAM_INCLUDE_QUALIFIER_ITERATE.equals(theQualifier); 331 } 332}