001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.camel.util; 018 019import java.util.ArrayList; 020import java.util.Collections; 021import java.util.List; 022import java.util.regex.Matcher; 023import java.util.regex.Pattern; 024 025/** 026 * Helper for Camel OGNL (Object-Graph Navigation Language) expressions. 027 */ 028public final class OgnlHelper { 029 030 private static final Pattern INDEX_PATTERN = Pattern.compile("^(.*)\\[(.*)\\]$"); 031 032 private OgnlHelper() { 033 } 034 035 /** 036 * Tests whether or not the given String is a Camel OGNL expression. 037 * <p/> 038 * An expression is considered an OGNL expression when it contains either one of the following chars: . or [ 039 * 040 * @param expression the String 041 * @return <tt>true</tt> if a Camel OGNL expression, otherwise <tt>false</tt>. 042 */ 043 public static boolean isValidOgnlExpression(String expression) { 044 if (expression == null || expression.isEmpty()) { 045 return false; 046 } 047 048 // the brackets should come in a pair 049 int bracketBegin = StringHelper.countChar(expression, '['); 050 int bracketEnd = StringHelper.countChar(expression, ']'); 051 if (bracketBegin > 0 && bracketEnd > 0) { 052 return bracketBegin == bracketEnd; 053 } 054 055 return expression.contains("."); 056 } 057 058 public static boolean isInvalidValidOgnlExpression(String expression) { 059 if (expression == null) { 060 return false; 061 } 062 063 if (expression.indexOf('.') == -1 && expression.indexOf('[') == -1 && expression.indexOf(']') == -1) { 064 return false; 065 } 066 067 // the brackets should come in pair 068 int bracketBegin = StringHelper.countChar(expression, '['); 069 int bracketEnd = StringHelper.countChar(expression, ']'); 070 if (bracketBegin > 0 || bracketEnd > 0) { 071 return bracketBegin != bracketEnd; 072 } 073 074 // check for double dots 075 if (expression.contains("..")) { 076 return true; 077 } 078 079 return false; 080 } 081 082 /** 083 * Validates whether the method name is using valid java identifiers in the name Will throw 084 * {@link IllegalArgumentException} if the method name is invalid. 085 */ 086 public static void validateMethodName(String method) { 087 for (int i = 0; i < method.length(); i++) { 088 char ch = method.charAt(i); 089 if (i == 0 && '.' == ch) { 090 // its a dot before a method name 091 continue; 092 } 093 if (ch == '(' || ch == '[' || ch == '.' || ch == '?') { 094 // break when method name ends and sub method or arguments begin 095 break; 096 } 097 if (i == 0 && !Character.isJavaIdentifierStart(ch)) { 098 throw new IllegalArgumentException( 099 "Method name must start with a valid java identifier at position: 0 in method: " + method); 100 } else if (!Character.isJavaIdentifierPart(ch)) { 101 throw new IllegalArgumentException( 102 "Method name must be valid java identifier at position: " + i + " in method: " + method); 103 } 104 } 105 } 106 107 /** 108 * Tests whether or not the given Camel OGNL expression is using the null safe operator or not. 109 * 110 * @param ognlExpression the Camel OGNL expression 111 * @return <tt>true</tt> if the null safe operator is used, otherwise <tt>false</tt>. 112 */ 113 public static boolean isNullSafeOperator(String ognlExpression) { 114 return ognlExpression.startsWith("?"); 115 } 116 117 /** 118 * Removes any leading operators from the Camel OGNL expression. 119 * <p/> 120 * Will remove any leading of the following chars: ? or . 121 * 122 * @param ognlExpression the Camel OGNL expression 123 * @return the Camel OGNL expression without any leading operators. 124 */ 125 public static String removeLeadingOperators(String ognlExpression) { 126 if (ognlExpression.startsWith("?")) { 127 ognlExpression = ognlExpression.substring(1); 128 } 129 if (ognlExpression.startsWith(".")) { 130 ognlExpression = ognlExpression.substring(1); 131 } 132 return ognlExpression; 133 } 134 135 /** 136 * Removes any trailing operators from the Camel OGNL expression. 137 * 138 * @param ognlExpression the Camel OGNL expression 139 * @return the Camel OGNL expression without any trailing operators. 140 */ 141 public static String removeTrailingOperators(String ognlExpression) { 142 return StringHelper.before(ognlExpression, "[", ognlExpression); 143 } 144 145 public static String removeOperators(String ognlExpression) { 146 return removeLeadingOperators(removeTrailingOperators(ognlExpression)); 147 } 148 149 public static KeyValueHolder<String, String> isOgnlIndex(String ognlExpression) { 150 Matcher matcher = INDEX_PATTERN.matcher(ognlExpression); 151 if (matcher.matches()) { 152 153 // to avoid empty strings as we want key/value to be null in such cases 154 String key = matcher.group(1); 155 if (key != null && key.isEmpty()) { 156 key = null; 157 } 158 159 // to avoid empty strings as we want key/value to be null in such cases 160 String value = matcher.group(2); 161 if (value != null && value.isEmpty()) { 162 value = null; 163 } 164 165 return new KeyValueHolder<>(key, value); 166 } 167 168 return null; 169 } 170 171 /** 172 * Regular expression with repeating groups is a pain to get right and then nobody understands the reg exp 173 * afterwards. So we use a bit ugly/low-level Java code to split the OGNL into methods. 174 * 175 * @param ognl the ognl expression 176 * @return a list of methods, will return an empty list, if ognl expression has no methods 177 * @throws IllegalArgumentException if the last method has a missing ending parenthesis 178 */ 179 public static List<String> splitOgnl(String ognl) { 180 // return an empty list if ognl is empty 181 if (ognl == null || ognl.isBlank()) { 182 return Collections.emptyList(); 183 } 184 185 List<String> methods = new ArrayList<>(4); 186 StringBuilder sb = new StringBuilder(); 187 188 int j = 0; // j is used as counter per method 189 int squareBracketCnt = 0; // special to keep track if and how deep we are inside a square bracket block, eg: [foo] 190 int parenthesisBracketCnt = 0; // special to keep track if and how deep we are inside a parenthesis block, eg: bar(${body}, ${header.foo}) 191 boolean singleQuoted = false; 192 boolean doubleQuoted = false; 193 194 for (int i = 0; i < ognl.length(); i++) { 195 char ch = ognl.charAt(i); 196 197 if (!doubleQuoted && ch == '\'') { 198 singleQuoted = !singleQuoted; 199 } else if (!singleQuoted && ch == '\"') { 200 doubleQuoted = !doubleQuoted; 201 } 202 if (singleQuoted || doubleQuoted) { 203 // quoted text so append as literal text 204 sb.append(ch); 205 continue; 206 } 207 208 // special for starting a new method 209 if (j == 0 || j == 1 && ognl.charAt(i - 1) == '?' 210 || ch != '.' && ch != '?' && ch != ']') { 211 sb.append(ch); 212 213 // special if we are doing square bracket 214 if (ch == '[' && parenthesisBracketCnt == 0) { 215 squareBracketCnt++; 216 } else if (ch == '(') { 217 parenthesisBracketCnt++; 218 } else if (ch == ')') { 219 parenthesisBracketCnt--; 220 } 221 j++; // advance 222 } else { 223 if (ch == '.' && squareBracketCnt == 0 && parenthesisBracketCnt == 0) { 224 // only treat dot as a method separator if not inside a square bracket block 225 // as dots can be used in key names when accessing maps 226 227 // a dit denotes end of this method and a new method is to be invoked 228 String s = sb.toString(); 229 230 // reset sb 231 sb.setLength(0); 232 233 // pass over ? to the new method 234 if (s.endsWith("?")) { 235 sb.append("?"); 236 s = s.substring(0, s.length() - 1); 237 } 238 239 // add the method 240 methods.add(s); 241 242 // reset j to begin a new method 243 j = 0; 244 } else if (ch == ']' && parenthesisBracketCnt == 0) { 245 // append ending ] to method name 246 sb.append(ch); 247 String s = sb.toString(); 248 249 // reset sb 250 sb.setLength(0); 251 252 // add the method 253 methods.add(s); 254 255 // reset j to begin a new method 256 j = 0; 257 258 // no more square bracket 259 squareBracketCnt--; 260 } 261 262 // and don't lose the char if its not an ] end marker (as we already added that) 263 if (ch != ']' || parenthesisBracketCnt > 0) { 264 sb.append(ch); 265 } 266 267 // only advance if already begun on the new method 268 if (j > 0) { 269 j++; 270 } 271 } 272 } 273 274 // add remainder in buffer when reached end of data 275 if (!sb.isEmpty()) { 276 methods.add(sb.toString()); 277 } 278 279 String last = methods.isEmpty() ? null : methods.get(methods.size() - 1); 280 if (parenthesisBracketCnt > 0 && last != null) { 281 // there is an unclosed parenthesis bracket on the last method, so it should end with a parenthesis 282 if (last.contains("(") && !last.endsWith(")")) { 283 throw new IllegalArgumentException("Method should end with parenthesis, was " + last); 284 } 285 } 286 287 return methods; 288 } 289 290 public static String methodAsDoubleQuotes(String ognl) { 291 StringBuilder sb = new StringBuilder(); 292 293 int singleBracketCnt = 0; 294 int doubleBracketCnt = 0; 295 for (int i = 0; i < ognl.length(); i++) { 296 char ch = ognl.charAt(i); 297 char next = i < ognl.length() - 1 ? ognl.charAt(i + 1) : 0; 298 299 if (ch == '\\' && next == '\'') { 300 if (singleBracketCnt % 2 != 0) { 301 // its an escaped single quote inside an existing quote 302 // then unescape it 303 sb.append('\''); 304 // and skip over to next 305 i++; 306 continue; 307 } 308 } 309 310 if (doubleBracketCnt % 2 == 0 && ch == '\'') { 311 singleBracketCnt++; 312 sb.append('"'); 313 } else if (singleBracketCnt % 2 == 0 && ch == '"') { 314 doubleBracketCnt++; 315 sb.append('"'); 316 } else { 317 sb.append(ch); 318 } 319 } 320 321 return sb.toString(); 322 } 323 324}