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.List; 021import java.util.regex.Matcher; 022import java.util.regex.Pattern; 023 024/** 025 * Helper for Camel OGNL (Object-Graph Navigation Language) expressions. 026 */ 027public final class OgnlHelper { 028 029 private static final Pattern INDEX_PATTERN = Pattern.compile("^(.*)\\[(.*)\\]$"); 030 031 private OgnlHelper() { 032 } 033 034 /** 035 * Tests whether or not the given String is a Camel OGNL expression. 036 * <p/> 037 * An expression is considered an OGNL expression when it contains either one of the following chars: . or [ 038 * 039 * @param expression the String 040 * @return <tt>true</tt> if a Camel OGNL expression, otherwise <tt>false</tt>. 041 */ 042 public static boolean isValidOgnlExpression(String expression) { 043 if (ObjectHelper.isEmpty(expression)) { 044 return false; 045 } 046 047 // the brackets should come in a pair 048 int bracketBegin = StringHelper.countChar(expression, '['); 049 int bracketEnd = StringHelper.countChar(expression, ']'); 050 if (bracketBegin > 0 && bracketEnd > 0) { 051 return bracketBegin == bracketEnd; 052 } 053 054 return expression.contains("."); 055 } 056 057 public static boolean isInvalidValidOgnlExpression(String expression) { 058 if (ObjectHelper.isEmpty(expression)) { 059 return false; 060 } 061 062 if (!expression.contains(".") && !expression.contains("[") && !expression.contains("]")) { 063 return false; 064 } 065 066 // the brackets should come in pair 067 int bracketBegin = StringHelper.countChar(expression, '['); 068 int bracketEnd = StringHelper.countChar(expression, ']'); 069 if (bracketBegin > 0 || bracketEnd > 0) { 070 return bracketBegin != bracketEnd; 071 } 072 073 // check for double dots 074 if (expression.contains("..")) { 075 return true; 076 } 077 078 return false; 079 } 080 081 /** 082 * Validates whether the method name is using valid java identifiers in the name 083 * Will throw {@link IllegalArgumentException} if the method name is invalid. 084 */ 085 public static void validateMethodName(String method) { 086 if (ObjectHelper.isEmpty(method)) { 087 return; 088 } 089 for (int i = 0; i < method.length(); i++) { 090 char ch = method.charAt(i); 091 if (i == 0 && '.' == ch) { 092 // its a dot before a method name 093 continue; 094 } 095 if (ch == '(' || ch == '[' || ch == '.' || ch == '?') { 096 // break when method name ends and sub method or arguments begin 097 break; 098 } 099 if (i == 0 && !Character.isJavaIdentifierStart(ch)) { 100 throw new IllegalArgumentException("Method name must start with a valid java identifier at position: 0 in method: " + method); 101 } else if (!Character.isJavaIdentifierPart(ch)) { 102 throw new IllegalArgumentException("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 if (ObjectHelper.isEmpty(ognlExpression)) { 115 return false; 116 } 117 118 return ognlExpression.startsWith("?"); 119 } 120 121 /** 122 * Removes any leading operators from the Camel OGNL expression. 123 * <p/> 124 * Will remove any leading of the following chars: ? or . 125 * 126 * @param ognlExpression the Camel OGNL expression 127 * @return the Camel OGNL expression without any leading operators. 128 */ 129 public static String removeLeadingOperators(String ognlExpression) { 130 if (ObjectHelper.isEmpty(ognlExpression)) { 131 return ognlExpression; 132 } 133 134 if (ognlExpression.startsWith("?")) { 135 ognlExpression = ognlExpression.substring(1); 136 } 137 if (ognlExpression.startsWith(".")) { 138 ognlExpression = ognlExpression.substring(1); 139 } 140 141 return ognlExpression; 142 } 143 144 /** 145 * Removes any trailing operators from the Camel OGNL expression. 146 * 147 * @param ognlExpression the Camel OGNL expression 148 * @return the Camel OGNL expression without any trailing operators. 149 */ 150 public static String removeTrailingOperators(String ognlExpression) { 151 if (ObjectHelper.isEmpty(ognlExpression)) { 152 return ognlExpression; 153 } 154 155 if (ognlExpression.contains("[")) { 156 return StringHelper.before(ognlExpression, "["); 157 } 158 return ognlExpression; 159 } 160 161 public static String removeOperators(String ognlExpression) { 162 return removeLeadingOperators(removeTrailingOperators(ognlExpression)); 163 } 164 165 public static KeyValueHolder<String, String> isOgnlIndex(String ognlExpression) { 166 Matcher matcher = INDEX_PATTERN.matcher(ognlExpression); 167 if (matcher.matches()) { 168 169 // to avoid empty strings as we want key/value to be null in such cases 170 String key = matcher.group(1); 171 if (ObjectHelper.isEmpty(key)) { 172 key = null; 173 } 174 175 // to avoid empty strings as we want key/value to be null in such cases 176 String value = matcher.group(2); 177 if (ObjectHelper.isEmpty(value)) { 178 value = null; 179 } 180 181 return new KeyValueHolder<>(key, value); 182 } 183 184 return null; 185 } 186 187 /** 188 * Regular expression with repeating groups is a pain to get right 189 * and then nobody understands the reg exp afterwards. 190 * So we use a bit ugly/low-level Java code to split the OGNL into methods. 191 * 192 * @param ognl the ognl expression 193 * @return a list of methods, will return an empty list, if ognl expression has no methods 194 * @throws IllegalArgumentException if the last method has a missing ending parenthesis 195 */ 196 public static List<String> splitOgnl(String ognl) { 197 List<String> methods = new ArrayList<>(); 198 199 // return an empty list if ognl is empty 200 if (ObjectHelper.isEmpty(ognl)) { 201 return methods; 202 } 203 204 StringBuilder sb = new StringBuilder(); 205 206 int j = 0; // j is used as counter per method 207 int squareBracketCnt = 0; // special to keep track if and how deep we are inside a square bracket block, eg: [foo] 208 int parenthesisBracketCnt = 0; // special to keep track if and how deep we are inside a parenthesis block, eg: bar(${body}, ${header.foo}) 209 210 for (int i = 0; i < ognl.length(); i++) { 211 char ch = ognl.charAt(i); 212 // special for starting a new method 213 if (j == 0 || (j == 1 && ognl.charAt(i - 1) == '?') 214 || (ch != '.' && ch != '?' && ch != ']')) { 215 sb.append(ch); 216 // special if we are doing square bracket 217 if (ch == '[' && parenthesisBracketCnt == 0) { 218 squareBracketCnt++; 219 } else if (ch == '(') { 220 parenthesisBracketCnt++; 221 } else if (ch == ')') { 222 parenthesisBracketCnt--; 223 } 224 j++; // advance 225 } else { 226 if (ch == '.' && squareBracketCnt == 0 && parenthesisBracketCnt == 0) { 227 // only treat dot as a method separator if not inside a square bracket block 228 // as dots can be used in key names when accessing maps 229 230 // a dit denotes end of this method and a new method is to be invoked 231 String s = sb.toString(); 232 233 // reset sb 234 sb.setLength(0); 235 236 // pass over ? to the new method 237 if (s.endsWith("?")) { 238 sb.append("?"); 239 s = s.substring(0, s.length() - 1); 240 } 241 242 // add the method 243 methods.add(s); 244 245 // reset j to begin a new method 246 j = 0; 247 } else if (ch == ']' && parenthesisBracketCnt == 0) { 248 // append ending ] to method name 249 sb.append(ch); 250 String s = sb.toString(); 251 252 // reset sb 253 sb.setLength(0); 254 255 // add the method 256 methods.add(s); 257 258 // reset j to begin a new method 259 j = 0; 260 261 // no more square bracket 262 squareBracketCnt--; 263 } 264 265 // and don't lose the char if its not an ] end marker (as we already added that) 266 if (ch != ']' || parenthesisBracketCnt > 0) { 267 sb.append(ch); 268 } 269 270 // only advance if already begun on the new method 271 if (j > 0) { 272 j++; 273 } 274 } 275 } 276 277 // add remainder in buffer when reached end of data 278 if (sb.length() > 0) { 279 methods.add(sb.toString()); 280 } 281 282 String last = methods.isEmpty() ? null : methods.get(methods.size() - 1); 283 if (parenthesisBracketCnt > 0 && last != null) { 284 // there is an unclosed parenthesis bracket on the last method, so it should end with a parenthesis 285 if (last.contains("(") && !last.endsWith(")")) { 286 throw new IllegalArgumentException("Method should end with parenthesis, was " + last); 287 } 288 } 289 290 return methods; 291 } 292 293}