001package ca.uhn.fhir.rest.param; 002 003import ca.uhn.fhir.context.ConfigurationException; 004import ca.uhn.fhir.context.FhirContext; 005import ca.uhn.fhir.i18n.Msg; 006import ca.uhn.fhir.model.api.IQueryParameterOr; 007import ca.uhn.fhir.model.api.IQueryParameterType; 008import ca.uhn.fhir.model.primitive.IdDt; 009import ca.uhn.fhir.model.primitive.IntegerDt; 010import ca.uhn.fhir.rest.annotation.IdParam; 011import ca.uhn.fhir.rest.api.Constants; 012import ca.uhn.fhir.rest.api.QualifiedParamList; 013import ca.uhn.fhir.util.ReflectionUtil; 014import ca.uhn.fhir.util.UrlUtil; 015import org.hl7.fhir.instance.model.api.IIdType; 016import org.hl7.fhir.instance.model.api.IPrimitiveType; 017 018import java.lang.annotation.Annotation; 019import java.lang.reflect.Method; 020import java.util.ArrayList; 021import java.util.Collection; 022import java.util.Collections; 023import java.util.List; 024import java.util.stream.Collectors; 025 026/* 027 * #%L 028 * HAPI FHIR - Core Library 029 * %% 030 * Copyright (C) 2014 - 2022 Smile CDR, Inc. 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 * Removes :modifiers and .chains from URL parameter names 060 */ 061 public static String stripModifierPart(String theParam) { 062 for (int i = 0; i < theParam.length(); i++) { 063 char nextChar = theParam.charAt(i); 064 if (nextChar == ':' || nextChar == '.') { 065 return theParam.substring(0, i); 066 } 067 } 068 return theParam; 069 } 070 071 /** 072 * 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 073 * Section</a> 074 */ 075 public static String escape(String theValue) { 076 if (theValue == null) { 077 return null; 078 } 079 StringBuilder b = new StringBuilder(); 080 081 for (int i = 0; i < theValue.length(); i++) { 082 char next = theValue.charAt(i); 083 switch (next) { 084 case '$': 085 case ',': 086 case '|': 087 case '\\': 088 b.append('\\'); 089 break; 090 default: 091 break; 092 } 093 b.append(next); 094 } 095 096 return b.toString(); 097 } 098 099 /** 100 * 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 101 * Section</a> 102 */ 103 public static String escapeWithDefault(Object theValue) { 104 if (theValue == null) { 105 return ""; 106 } 107 return escape(theValue.toString()); 108 } 109 110 /** 111 * Applies {@link #escapeWithDefault(Object)} followed by {@link UrlUtil#escapeUrlParam(String)} 112 */ 113 public static String escapeAndUrlEncode(String theInput) { 114 return UrlUtil.escapeUrlParam(escapeWithDefault(theInput)); 115 } 116 117 public static Integer findIdParameterIndex(Method theMethod, FhirContext theContext) { 118 Integer index = findParamAnnotationIndex(theMethod, IdParam.class); 119 if (index != null) { 120 Class<?> paramType = theMethod.getParameterTypes()[index]; 121 if (IIdType.class.equals(paramType)) { 122 return index; 123 } 124 boolean isRi = theContext.getVersion().getVersion().isRi(); 125 boolean usesHapiId = IdDt.class.equals(paramType); 126 if (isRi == usesHapiId) { 127 throw new ConfigurationException(Msg.code(1936) + "Method uses the wrong Id datatype (IdDt / IdType) for the given context FHIR version: " + theMethod.toString()); 128 } 129 } 130 return index; 131 } 132 133 // public static Integer findSinceParameterIndex(Method theMethod) { 134 // return findParamIndex(theMethod, Since.class); 135 // } 136 137 public static Integer findParamAnnotationIndex(Method theMethod, Class<?> toFind) { 138 int paramIndex = 0; 139 for (Annotation[] annotations : theMethod.getParameterAnnotations()) { 140 for (Annotation nextAnnotation : annotations) { 141 Class<? extends Annotation> class1 = nextAnnotation.annotationType(); 142 if (toFind.isAssignableFrom(class1)) { 143 return paramIndex; 144 } 145 } 146 paramIndex++; 147 } 148 return null; 149 } 150 151 public static Object fromInteger(Class<?> theType, IntegerDt theArgument) { 152 if (theArgument == null) { 153 return null; 154 } 155 if (theType.equals(Integer.class)) { 156 return theArgument.getValue(); 157 } 158 IPrimitiveType<?> retVal = (IPrimitiveType<?>) ReflectionUtil.newInstance(theType); 159 retVal.setValueAsString(theArgument.getValueAsString()); 160 return retVal; 161 } 162 163 public static boolean isBindableIntegerType(Class<?> theClass) { 164 return Integer.class.isAssignableFrom(theClass) 165 || IPrimitiveType.class.isAssignableFrom(theClass); 166 } 167 168 public static String escapeAndJoinOrList(Collection<String> theValues) { 169 return theValues 170 .stream() 171 .map(ParameterUtil::escape) 172 .collect(Collectors.joining(",")); 173 } 174 175 public static int nonEscapedIndexOf(String theString, char theCharacter) { 176 for (int i = 0; i < theString.length(); i++) { 177 if (theString.charAt(i) == theCharacter) { 178 if (i == 0 || theString.charAt(i - 1) != '\\') { 179 return i; 180 } 181 } 182 } 183 return -1; 184 } 185 186 public static String parseETagValue(String value) { 187 String eTagVersion; 188 value = value.trim(); 189 if (value.length() > 1) { 190 if (value.charAt(value.length() - 1) == '"') { 191 if (value.charAt(0) == '"') { 192 eTagVersion = value.substring(1, value.length() - 1); 193 } else if (value.length() > 3 && value.charAt(0) == 'W' && value.charAt(1) == '/' 194 && value.charAt(2) == '"') { 195 eTagVersion = value.substring(3, value.length() - 1); 196 } else { 197 eTagVersion = value; 198 } 199 } else { 200 eTagVersion = value; 201 } 202 } else { 203 eTagVersion = value; 204 } 205 return eTagVersion; 206 } 207 208 public static IQueryParameterOr<?> singleton(final IQueryParameterType theParam, final String theParamName) { 209 return new IQueryParameterOr<IQueryParameterType>() { 210 211 private static final long serialVersionUID = 1L; 212 213 @Override 214 public List<IQueryParameterType> getValuesAsQueryTokens() { 215 return Collections.singletonList(theParam); 216 } 217 218 @Override 219 public void setValuesAsQueryTokens(FhirContext theContext, String theParamName, 220 QualifiedParamList theParameters) { 221 if (theParameters.isEmpty()) { 222 return; 223 } 224 if (theParameters.size() > 1) { 225 throw new IllegalArgumentException(Msg.code(1937) + "Type " + theParam.getClass().getCanonicalName() + " does not support multiple values"); 226 } 227 theParam.setValueAsQueryToken(theContext, theParamName, theParameters.getQualifier(), 228 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) || Constants.PARAM_INCLUDE_QUALIFIER_ITERATE.equals(theQualifier); 330 } 331}