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