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.language.simple.ast;
018
019import org.apache.camel.Expression;
020import org.apache.camel.builder.ExpressionBuilder;
021import org.apache.camel.language.simple.types.SimpleParserException;
022import org.apache.camel.language.simple.types.SimpleToken;
023import org.apache.camel.util.LRUCache;
024import org.apache.camel.util.ObjectHelper;
025import org.apache.camel.util.OgnlHelper;
026import org.apache.camel.util.StringHelper;
027
028/**
029 * Represents one of built-in functions of the
030 * <a href="http://camel.apache.org/simple.html">simple language</a>
031 */
032public class SimpleFunctionExpression extends LiteralExpression {
033
034    // use caches to avoid re-parsing the same expressions over and over again
035    private LRUCache<String, Expression> cacheExpression;
036
037    @Deprecated
038    public SimpleFunctionExpression(SimpleToken token) {
039        super(token);
040    }
041
042    public SimpleFunctionExpression(SimpleToken token, LRUCache<String, Expression> cacheExpression) {
043        super(token);
044        this.cacheExpression = cacheExpression;
045    }
046
047    /**
048     * Creates a Camel {@link Expression} based on this model.
049     *
050     * @param expression not in use
051     */
052    @Override
053    public Expression createExpression(String expression) {
054        String function = text.toString();
055
056        Expression answer = cacheExpression != null ? cacheExpression.get(function) : null;
057        if (answer == null) {
058            answer = createSimpleExpression(function, true);
059            if (cacheExpression != null && answer != null) {
060                cacheExpression.put(function, answer);
061            }
062        }
063        return answer;
064    }
065
066    /**
067     * Creates a Camel {@link Expression} based on this model.
068     *
069     * @param expression not in use
070     * @param strict whether to throw exception if the expression was not a function,
071     *          otherwise <tt>null</tt> is returned
072     * @return the created {@link Expression}
073     * @throws org.apache.camel.language.simple.types.SimpleParserException
074     *          should be thrown if error parsing the model
075     */
076    public Expression createExpression(String expression, boolean strict) {
077        String function = text.toString();
078
079        Expression answer = cacheExpression != null ? cacheExpression.get(function) : null;
080        if (answer == null) {
081            answer = createSimpleExpression(function, strict);
082            if (cacheExpression != null && answer != null) {
083                cacheExpression.put(function, answer);
084            }
085        }
086        return answer;
087    }
088
089    private Expression createSimpleExpression(String function, boolean strict) {
090        // return the function directly if we can create function without analyzing the prefix
091        Expression answer = createSimpleExpressionDirectly(function);
092        if (answer != null) {
093            return answer;
094        }
095
096        // body and headers first
097        answer = createSimpleExpressionBodyOrHeader(function, strict);
098        if (answer != null) {
099            return answer;
100        }
101
102        // camelContext OGNL
103        String remainder = ifStartsWithReturnRemainder("camelContext", function);
104        if (remainder != null) {
105            boolean invalid = OgnlHelper.isInvalidValidOgnlExpression(remainder);
106            if (invalid) {
107                throw new SimpleParserException("Valid syntax: ${camelContext.OGNL} was: " + function, token.getIndex());
108            }
109            return ExpressionBuilder.camelContextOgnlExpression(remainder);
110        }
111
112        // Exception OGNL
113        remainder = ifStartsWithReturnRemainder("exception", function);
114        if (remainder != null) {
115            boolean invalid = OgnlHelper.isInvalidValidOgnlExpression(remainder);
116            if (invalid) {
117                throw new SimpleParserException("Valid syntax: ${exception.OGNL} was: " + function, token.getIndex());
118            }
119            return ExpressionBuilder.exchangeExceptionOgnlExpression(remainder);
120        }
121
122        // property
123        remainder = ifStartsWithReturnRemainder("property", function);
124        if (remainder == null) {
125            remainder = ifStartsWithReturnRemainder("exchangeProperty", function);
126        }
127        if (remainder != null) {
128            // remove leading character (dot or ?)
129            if (remainder.startsWith(".") || remainder.startsWith("?")) {
130                remainder = remainder.substring(1);
131            }
132            // remove starting and ending brackets
133            if (remainder.startsWith("[") && remainder.endsWith("]")) {
134                remainder = remainder.substring(1, remainder.length() - 1);
135            }
136
137            // validate syntax
138            boolean invalid = OgnlHelper.isInvalidValidOgnlExpression(remainder);
139            if (invalid) {
140                throw new SimpleParserException("Valid syntax: ${exchangeProperty.OGNL} was: " + function, token.getIndex());
141            }
142
143            if (OgnlHelper.isValidOgnlExpression(remainder)) {
144                // ognl based property
145                return ExpressionBuilder.propertyOgnlExpression(remainder);
146            } else {
147                // regular property
148                return ExpressionBuilder.exchangePropertyExpression(remainder);
149            }
150        }
151
152        // system property
153        remainder = ifStartsWithReturnRemainder("sys.", function);
154        if (remainder != null) {
155            return ExpressionBuilder.systemPropertyExpression(remainder);
156        }
157        remainder = ifStartsWithReturnRemainder("sysenv.", function);
158        if (remainder != null) {
159            return ExpressionBuilder.systemEnvironmentExpression(remainder);
160        }
161
162        // exchange OGNL
163        remainder = ifStartsWithReturnRemainder("exchange", function);
164        if (remainder != null) {
165            boolean invalid = OgnlHelper.isInvalidValidOgnlExpression(remainder);
166            if (invalid) {
167                throw new SimpleParserException("Valid syntax: ${exchange.OGNL} was: " + function, token.getIndex());
168            }
169            return ExpressionBuilder.exchangeOgnlExpression(remainder);
170        }
171
172        // file: prefix
173        remainder = ifStartsWithReturnRemainder("file:", function);
174        if (remainder != null) {
175            Expression fileExpression = createSimpleFileExpression(remainder, strict);
176            if (fileExpression != null) {
177                return fileExpression;
178            }
179        }
180
181        // date: prefix
182        remainder = ifStartsWithReturnRemainder("date:", function);
183        if (remainder != null) {
184            String[] parts = remainder.split(":", 2);
185            if (parts.length == 1) {
186                return ExpressionBuilder.dateExpression(parts[0]);
187            } else if (parts.length == 2) {
188                return ExpressionBuilder.dateExpression(parts[0], parts[1]);
189            }
190        }
191
192        // date-with-timezone: prefix
193        remainder = ifStartsWithReturnRemainder("date-with-timezone:", function);
194        if (remainder != null) {
195            String[] parts = remainder.split(":", 3);
196            if (parts.length < 3) {
197                throw new SimpleParserException("Valid syntax: ${date-with-timezone:command:timezone:pattern} was: " + function, token.getIndex());
198            }
199            return ExpressionBuilder.dateExpression(parts[0], parts[1], parts[2]);
200        }
201
202        // bean: prefix
203        remainder = ifStartsWithReturnRemainder("bean:", function);
204        if (remainder != null) {
205            return ExpressionBuilder.beanExpression(remainder);
206        }
207
208        // properties: prefix
209        remainder = ifStartsWithReturnRemainder("properties:", function);
210        if (remainder != null) {
211            String[] parts = remainder.split(":");
212            if (parts.length > 2) {
213                throw new SimpleParserException("Valid syntax: ${properties:key[:default]} was: " + function, token.getIndex());
214            }
215            return ExpressionBuilder.propertiesComponentExpression(remainder, null, null);
216        }
217
218        // properties-location: prefix
219        remainder = ifStartsWithReturnRemainder("properties-location:", function);
220        if (remainder != null) {
221            String[] parts = remainder.split(":");
222            if (parts.length > 3) {
223                throw new SimpleParserException("Valid syntax: ${properties-location:location:key[:default]} was: " + function, token.getIndex());
224            }
225
226            String locations = null;
227            String key = remainder;
228            if (parts.length >= 2) {
229                locations = ObjectHelper.before(remainder, ":");
230                key = ObjectHelper.after(remainder, ":");
231            }
232            return ExpressionBuilder.propertiesComponentExpression(key, locations, null);
233        }
234
235        // ref: prefix
236        remainder = ifStartsWithReturnRemainder("ref:", function);
237        if (remainder != null) {
238            return ExpressionBuilder.refExpression(remainder);
239        }
240
241        // const: prefix
242        remainder = ifStartsWithReturnRemainder("type:", function);
243        if (remainder != null) {
244            Expression exp = ExpressionBuilder.typeExpression(remainder);
245            // we want to cache this expression so we wont re-evaluate it as the type/constant wont change
246            return ExpressionBuilder.cacheExpression(exp);
247        }
248
249        // miscellaneous functions
250        Expression misc = createSimpleExpressionMisc(function);
251        if (misc != null) {
252            return misc;
253        }
254
255        if (strict) {
256            throw new SimpleParserException("Unknown function: " + function, token.getIndex());
257        } else {
258            return null;
259        }
260    }
261
262    private Expression createSimpleExpressionBodyOrHeader(String function, boolean strict) {
263        // bodyAs
264        String remainder = ifStartsWithReturnRemainder("bodyAs(", function);
265        if (remainder != null) {
266            String type = ObjectHelper.before(remainder, ")");
267            if (type == null) {
268                throw new SimpleParserException("Valid syntax: ${bodyAs(type)} was: " + function, token.getIndex());
269            }
270            type = StringHelper.removeQuotes(type);
271            remainder = ObjectHelper.after(remainder, ")");
272            if (ObjectHelper.isNotEmpty(remainder)) {
273                boolean invalid = OgnlHelper.isInvalidValidOgnlExpression(remainder);
274                if (invalid) {
275                    throw new SimpleParserException("Valid syntax: ${bodyAs(type).OGNL} was: " + function, token.getIndex());
276                }
277                return ExpressionBuilder.bodyOgnlExpression(type, remainder);
278            } else {
279                return ExpressionBuilder.bodyExpression(type);
280            }
281
282        }
283        // mandatoryBodyAs
284        remainder = ifStartsWithReturnRemainder("mandatoryBodyAs(", function);
285        if (remainder != null) {
286            String type = ObjectHelper.before(remainder, ")");
287            if (type == null) {
288                throw new SimpleParserException("Valid syntax: ${mandatoryBodyAs(type)} was: " + function, token.getIndex());
289            }
290            type = StringHelper.removeQuotes(type);
291            remainder = ObjectHelper.after(remainder, ")");
292            if (ObjectHelper.isNotEmpty(remainder)) {
293                boolean invalid = OgnlHelper.isInvalidValidOgnlExpression(remainder);
294                if (invalid) {
295                    throw new SimpleParserException("Valid syntax: ${mandatoryBodyAs(type).OGNL} was: " + function, token.getIndex());
296                }
297                return ExpressionBuilder.mandatoryBodyOgnlExpression(type, remainder);
298            } else {
299                return ExpressionBuilder.mandatoryBodyExpression(type);
300            }
301        }
302
303        // body OGNL
304        remainder = ifStartsWithReturnRemainder("body", function);
305        if (remainder == null) {
306            remainder = ifStartsWithReturnRemainder("in.body", function);
307        }
308        if (remainder != null) {
309            // OGNL must start with a . ? or [
310            boolean ognlStart = remainder.startsWith(".") || remainder.startsWith("?") || remainder.startsWith("[");
311            boolean invalid = !ognlStart || OgnlHelper.isInvalidValidOgnlExpression(remainder);
312            if (invalid) {
313                throw new SimpleParserException("Valid syntax: ${body.OGNL} was: " + function, token.getIndex());
314            }
315            return ExpressionBuilder.bodyOgnlExpression(remainder);
316        }
317
318        // headerAs
319        remainder = ifStartsWithReturnRemainder("headerAs(", function);
320        if (remainder != null) {
321            String keyAndType = ObjectHelper.before(remainder, ")");
322            if (keyAndType == null) {
323                throw new SimpleParserException("Valid syntax: ${headerAs(key, type)} was: " + function, token.getIndex());
324            }
325
326            String key = ObjectHelper.before(keyAndType, ",");
327            String type = ObjectHelper.after(keyAndType, ",");
328            remainder = ObjectHelper.after(remainder, ")");
329            if (ObjectHelper.isEmpty(key) || ObjectHelper.isEmpty(type) || ObjectHelper.isNotEmpty(remainder)) {
330                throw new SimpleParserException("Valid syntax: ${headerAs(key, type)} was: " + function, token.getIndex());
331            }
332            key = StringHelper.removeQuotes(key);
333            type = StringHelper.removeQuotes(type);
334            return ExpressionBuilder.headerExpression(key, type);
335        }
336
337        // headers function
338        if ("in.headers".equals(function) || "headers".equals(function)) {
339            return ExpressionBuilder.headersExpression();
340        }
341
342        // in header function
343        remainder = ifStartsWithReturnRemainder("in.headers", function);
344        if (remainder == null) {
345            remainder = ifStartsWithReturnRemainder("in.header", function);
346        }
347        if (remainder == null) {
348            remainder = ifStartsWithReturnRemainder("headers", function);
349        }
350        if (remainder == null) {
351            remainder = ifStartsWithReturnRemainder("header", function);
352        }
353        if (remainder != null) {
354            // remove leading character (dot or ?)
355            if (remainder.startsWith(".") || remainder.startsWith("?")) {
356                remainder = remainder.substring(1);
357            }
358            // remove starting and ending brackets
359            if (remainder.startsWith("[") && remainder.endsWith("]")) {
360                remainder = remainder.substring(1, remainder.length() - 1);
361            }
362            // remove quotes from key
363            String key = StringHelper.removeLeadingAndEndingQuotes(remainder);
364
365            // validate syntax
366            boolean invalid = OgnlHelper.isInvalidValidOgnlExpression(key);
367            if (invalid) {
368                throw new SimpleParserException("Valid syntax: ${header.name[key]} was: " + function, token.getIndex());
369            }
370
371            if (OgnlHelper.isValidOgnlExpression(key)) {
372                // ognl based header
373                return ExpressionBuilder.headersOgnlExpression(key);
374            } else {
375                // regular header
376                return ExpressionBuilder.headerExpression(key);
377            }
378        }
379
380        // out header function
381        remainder = ifStartsWithReturnRemainder("out.header.", function);
382        if (remainder == null) {
383            remainder = ifStartsWithReturnRemainder("out.headers.", function);
384        }
385        if (remainder != null) {
386            return ExpressionBuilder.outHeaderExpression(remainder);
387        }
388
389        return null;
390    }
391
392    private Expression createSimpleExpressionDirectly(String expression) {
393        if (ObjectHelper.isEqualToAny(expression, "body", "in.body")) {
394            return ExpressionBuilder.bodyExpression();
395        } else if (ObjectHelper.equal(expression, "out.body")) {
396            return ExpressionBuilder.outBodyExpression();
397        } else if (ObjectHelper.equal(expression, "id")) {
398            return ExpressionBuilder.messageIdExpression();
399        } else if (ObjectHelper.equal(expression, "exchangeId")) {
400            return ExpressionBuilder.exchangeIdExpression();
401        } else if (ObjectHelper.equal(expression, "exchange")) {
402            return ExpressionBuilder.exchangeExpression();
403        } else if (ObjectHelper.equal(expression, "exception")) {
404            return ExpressionBuilder.exchangeExceptionExpression();
405        } else if (ObjectHelper.equal(expression, "exception.message")) {
406            return ExpressionBuilder.exchangeExceptionMessageExpression();
407        } else if (ObjectHelper.equal(expression, "exception.stacktrace")) {
408            return ExpressionBuilder.exchangeExceptionStackTraceExpression();
409        } else if (ObjectHelper.equal(expression, "threadName")) {
410            return ExpressionBuilder.threadNameExpression();
411        } else if (ObjectHelper.equal(expression, "camelId")) {
412            return ExpressionBuilder.camelContextNameExpression();
413        } else if (ObjectHelper.equal(expression, "routeId")) {
414            return ExpressionBuilder.routeIdExpression();
415        } else if (ObjectHelper.equal(expression, "null")) {
416            return ExpressionBuilder.nullExpression();
417        }
418
419        return null;
420    }
421
422    private Expression createSimpleFileExpression(String remainder, boolean strict) {
423        if (ObjectHelper.equal(remainder, "name")) {
424            return ExpressionBuilder.fileNameExpression();
425        } else if (ObjectHelper.equal(remainder, "name.noext")) {
426            return ExpressionBuilder.fileNameNoExtensionExpression();
427        } else if (ObjectHelper.equal(remainder, "name.noext.single")) {
428            return ExpressionBuilder.fileNameNoExtensionSingleExpression();
429        } else if (ObjectHelper.equal(remainder, "name.ext") || ObjectHelper.equal(remainder, "ext")) {
430            return ExpressionBuilder.fileExtensionExpression();
431        } else if (ObjectHelper.equal(remainder, "name.ext.single")) {
432            return ExpressionBuilder.fileExtensionSingleExpression();
433        } else if (ObjectHelper.equal(remainder, "onlyname")) {
434            return ExpressionBuilder.fileOnlyNameExpression();
435        } else if (ObjectHelper.equal(remainder, "onlyname.noext")) {
436            return ExpressionBuilder.fileOnlyNameNoExtensionExpression();
437        } else if (ObjectHelper.equal(remainder, "onlyname.noext.single")) {
438            return ExpressionBuilder.fileOnlyNameNoExtensionSingleExpression();
439        } else if (ObjectHelper.equal(remainder, "parent")) {
440            return ExpressionBuilder.fileParentExpression();
441        } else if (ObjectHelper.equal(remainder, "path")) {
442            return ExpressionBuilder.filePathExpression();
443        } else if (ObjectHelper.equal(remainder, "absolute")) {
444            return ExpressionBuilder.fileAbsoluteExpression();
445        } else if (ObjectHelper.equal(remainder, "absolute.path")) {
446            return ExpressionBuilder.fileAbsolutePathExpression();
447        } else if (ObjectHelper.equal(remainder, "length") || ObjectHelper.equal(remainder, "size")) {
448            return ExpressionBuilder.fileSizeExpression();
449        } else if (ObjectHelper.equal(remainder, "modified")) {
450            return ExpressionBuilder.fileLastModifiedExpression();
451        }
452        if (strict) {
453            throw new SimpleParserException("Unknown file language syntax: " + remainder, token.getIndex());
454        }
455        return null;
456    }
457
458    private Expression createSimpleExpressionMisc(String function) {
459        String remainder;
460
461        // random function
462        remainder = ifStartsWithReturnRemainder("random(", function);
463        if (remainder != null) {
464            String values = ObjectHelper.before(remainder, ")");
465            if (values == null || ObjectHelper.isEmpty(values)) {
466                throw new SimpleParserException("Valid syntax: ${random(min,max)} or ${random(max)} was: " + function, token.getIndex());
467            }
468            if (values.contains(",")) {
469                String[] tokens = values.split(",", -1);
470                if (tokens.length > 2) {
471                    throw new SimpleParserException("Valid syntax: ${random(min,max)} or ${random(max)} was: " + function, token.getIndex());
472                }
473                return ExpressionBuilder.randomExpression(tokens[0].trim(), tokens[1].trim());
474            } else {
475                return ExpressionBuilder.randomExpression("0", values.trim());
476            }
477        }
478
479        // skip function
480        remainder = ifStartsWithReturnRemainder("skip(", function);
481        if (remainder != null) {
482            String values = ObjectHelper.before(remainder, ")");
483            if (values == null || ObjectHelper.isEmpty(values)) {
484                throw new SimpleParserException("Valid syntax: ${skip(number)} was: " + function, token.getIndex());
485            }
486            String exp = "${body}";
487            int num = Integer.parseInt(values.trim());
488            return ExpressionBuilder.skipExpression(exp, num);
489        }
490
491        // collate function
492        remainder = ifStartsWithReturnRemainder("collate(", function);
493        if (remainder != null) {
494            String values = ObjectHelper.before(remainder, ")");
495            if (values == null || ObjectHelper.isEmpty(values)) {
496                throw new SimpleParserException("Valid syntax: ${collate(group)} was: " + function, token.getIndex());
497            }
498            String exp = "${body}";
499            int num = Integer.parseInt(values.trim());
500            return ExpressionBuilder.collateExpression(exp, num);
501        }
502
503        // messageHistory function
504        remainder = ifStartsWithReturnRemainder("messageHistory", function);
505        if (remainder != null) {
506            boolean detailed;
507            String values = ObjectHelper.between(remainder, "(", ")");
508            if (values == null || ObjectHelper.isEmpty(values)) {
509                detailed = true;
510            } else {
511                detailed = Boolean.valueOf(values);
512            }
513            return ExpressionBuilder.messageHistoryExpression(detailed);
514        } else if (ObjectHelper.equal(function, "messageHistory")) {
515            return ExpressionBuilder.messageHistoryExpression(true);
516        }
517        return null;
518    }
519
520    private String ifStartsWithReturnRemainder(String prefix, String text) {
521        if (text.startsWith(prefix)) {
522            String remainder = text.substring(prefix.length());
523            if (remainder.length() > 0) {
524                return remainder;
525            }
526        }
527        return null;
528    }
529
530}