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            boolean invalid = OgnlHelper.isInvalidValidOgnlExpression(remainder);
310            if (invalid) {
311                throw new SimpleParserException("Valid syntax: ${body.OGNL} was: " + function, token.getIndex());
312            }
313            return ExpressionBuilder.bodyOgnlExpression(remainder);
314        }
315
316        // headerAs
317        remainder = ifStartsWithReturnRemainder("headerAs(", function);
318        if (remainder != null) {
319            String keyAndType = ObjectHelper.before(remainder, ")");
320            if (keyAndType == null) {
321                throw new SimpleParserException("Valid syntax: ${headerAs(key, type)} was: " + function, token.getIndex());
322            }
323
324            String key = ObjectHelper.before(keyAndType, ",");
325            String type = ObjectHelper.after(keyAndType, ",");
326            remainder = ObjectHelper.after(remainder, ")");
327            if (ObjectHelper.isEmpty(key) || ObjectHelper.isEmpty(type) || ObjectHelper.isNotEmpty(remainder)) {
328                throw new SimpleParserException("Valid syntax: ${headerAs(key, type)} was: " + function, token.getIndex());
329            }
330            key = StringHelper.removeQuotes(key);
331            type = StringHelper.removeQuotes(type);
332            return ExpressionBuilder.headerExpression(key, type);
333        }
334
335        // headers function
336        if ("in.headers".equals(function) || "headers".equals(function)) {
337            return ExpressionBuilder.headersExpression();
338        }
339
340        // in header function
341        remainder = ifStartsWithReturnRemainder("in.headers", function);
342        if (remainder == null) {
343            remainder = ifStartsWithReturnRemainder("in.header", function);
344        }
345        if (remainder == null) {
346            remainder = ifStartsWithReturnRemainder("headers", function);
347        }
348        if (remainder == null) {
349            remainder = ifStartsWithReturnRemainder("header", function);
350        }
351        if (remainder != null) {
352            // remove leading character (dot or ?)
353            if (remainder.startsWith(".") || remainder.startsWith("?")) {
354                remainder = remainder.substring(1);
355            }
356            // remove starting and ending brackets
357            if (remainder.startsWith("[") && remainder.endsWith("]")) {
358                remainder = remainder.substring(1, remainder.length() - 1);
359            }
360            // remove quotes from key
361            String key = StringHelper.removeLeadingAndEndingQuotes(remainder);
362
363            // validate syntax
364            boolean invalid = OgnlHelper.isInvalidValidOgnlExpression(key);
365            if (invalid) {
366                throw new SimpleParserException("Valid syntax: ${header.name[key]} was: " + function, token.getIndex());
367            }
368
369            if (OgnlHelper.isValidOgnlExpression(key)) {
370                // ognl based header
371                return ExpressionBuilder.headersOgnlExpression(key);
372            } else {
373                // regular header
374                return ExpressionBuilder.headerExpression(key);
375            }
376        }
377
378        // out header function
379        remainder = ifStartsWithReturnRemainder("out.header.", function);
380        if (remainder == null) {
381            remainder = ifStartsWithReturnRemainder("out.headers.", function);
382        }
383        if (remainder != null) {
384            return ExpressionBuilder.outHeaderExpression(remainder);
385        }
386
387        return null;
388    }
389
390    private Expression createSimpleExpressionDirectly(String expression) {
391        if (ObjectHelper.isEqualToAny(expression, "body", "in.body")) {
392            return ExpressionBuilder.bodyExpression();
393        } else if (ObjectHelper.equal(expression, "out.body")) {
394            return ExpressionBuilder.outBodyExpression();
395        } else if (ObjectHelper.equal(expression, "id")) {
396            return ExpressionBuilder.messageIdExpression();
397        } else if (ObjectHelper.equal(expression, "exchangeId")) {
398            return ExpressionBuilder.exchangeIdExpression();
399        } else if (ObjectHelper.equal(expression, "exchange")) {
400            return ExpressionBuilder.exchangeExpression();
401        } else if (ObjectHelper.equal(expression, "exception")) {
402            return ExpressionBuilder.exchangeExceptionExpression();
403        } else if (ObjectHelper.equal(expression, "exception.message")) {
404            return ExpressionBuilder.exchangeExceptionMessageExpression();
405        } else if (ObjectHelper.equal(expression, "exception.stacktrace")) {
406            return ExpressionBuilder.exchangeExceptionStackTraceExpression();
407        } else if (ObjectHelper.equal(expression, "threadName")) {
408            return ExpressionBuilder.threadNameExpression();
409        } else if (ObjectHelper.equal(expression, "camelId")) {
410            return ExpressionBuilder.camelContextNameExpression();
411        } else if (ObjectHelper.equal(expression, "routeId")) {
412            return ExpressionBuilder.routeIdExpression();
413        } else if (ObjectHelper.equal(expression, "null")) {
414            return ExpressionBuilder.nullExpression();
415        }
416
417        return null;
418    }
419
420    private Expression createSimpleFileExpression(String remainder, boolean strict) {
421        if (ObjectHelper.equal(remainder, "name")) {
422            return ExpressionBuilder.fileNameExpression();
423        } else if (ObjectHelper.equal(remainder, "name.noext")) {
424            return ExpressionBuilder.fileNameNoExtensionExpression();
425        } else if (ObjectHelper.equal(remainder, "name.noext.single")) {
426            return ExpressionBuilder.fileNameNoExtensionSingleExpression();
427        } else if (ObjectHelper.equal(remainder, "name.ext") || ObjectHelper.equal(remainder, "ext")) {
428            return ExpressionBuilder.fileExtensionExpression();
429        } else if (ObjectHelper.equal(remainder, "name.ext.single")) {
430            return ExpressionBuilder.fileExtensionSingleExpression();
431        } else if (ObjectHelper.equal(remainder, "onlyname")) {
432            return ExpressionBuilder.fileOnlyNameExpression();
433        } else if (ObjectHelper.equal(remainder, "onlyname.noext")) {
434            return ExpressionBuilder.fileOnlyNameNoExtensionExpression();
435        } else if (ObjectHelper.equal(remainder, "onlyname.noext.single")) {
436            return ExpressionBuilder.fileOnlyNameNoExtensionSingleExpression();
437        } else if (ObjectHelper.equal(remainder, "parent")) {
438            return ExpressionBuilder.fileParentExpression();
439        } else if (ObjectHelper.equal(remainder, "path")) {
440            return ExpressionBuilder.filePathExpression();
441        } else if (ObjectHelper.equal(remainder, "absolute")) {
442            return ExpressionBuilder.fileAbsoluteExpression();
443        } else if (ObjectHelper.equal(remainder, "absolute.path")) {
444            return ExpressionBuilder.fileAbsolutePathExpression();
445        } else if (ObjectHelper.equal(remainder, "length") || ObjectHelper.equal(remainder, "size")) {
446            return ExpressionBuilder.fileSizeExpression();
447        } else if (ObjectHelper.equal(remainder, "modified")) {
448            return ExpressionBuilder.fileLastModifiedExpression();
449        }
450        if (strict) {
451            throw new SimpleParserException("Unknown file language syntax: " + remainder, token.getIndex());
452        }
453        return null;
454    }
455
456    private Expression createSimpleExpressionMisc(String function) {
457        String remainder;
458
459        // random function
460        remainder = ifStartsWithReturnRemainder("random(", function);
461        if (remainder != null) {
462            String values = ObjectHelper.before(remainder, ")");
463            if (values == null || ObjectHelper.isEmpty(values)) {
464                throw new SimpleParserException("Valid syntax: ${random(min,max)} or ${random(max)} was: " + function, token.getIndex());
465            }
466            if (values.contains(",")) {
467                String[] tokens = values.split(",", -1);
468                if (tokens.length > 2) {
469                    throw new SimpleParserException("Valid syntax: ${random(min,max)} or ${random(max)} was: " + function, token.getIndex());
470                }
471                return ExpressionBuilder.randomExpression(tokens[0].trim(), tokens[1].trim());
472            } else {
473                return ExpressionBuilder.randomExpression("0", values.trim());
474            }
475        }
476
477        // skip function
478        remainder = ifStartsWithReturnRemainder("skip(", function);
479        if (remainder != null) {
480            String values = ObjectHelper.before(remainder, ")");
481            if (values == null || ObjectHelper.isEmpty(values)) {
482                throw new SimpleParserException("Valid syntax: ${skip(number)} was: " + function, token.getIndex());
483            }
484            String exp = "${body}";
485            int num = Integer.parseInt(values.trim());
486            return ExpressionBuilder.skipExpression(exp, num);
487        }
488
489        // collate function
490        remainder = ifStartsWithReturnRemainder("collate(", function);
491        if (remainder != null) {
492            String values = ObjectHelper.before(remainder, ")");
493            if (values == null || ObjectHelper.isEmpty(values)) {
494                throw new SimpleParserException("Valid syntax: ${collate(group)} was: " + function, token.getIndex());
495            }
496            String exp = "${body}";
497            int num = Integer.parseInt(values.trim());
498            return ExpressionBuilder.collateExpression(exp, num);
499        }
500
501        // messageHistory function
502        remainder = ifStartsWithReturnRemainder("messageHistory", function);
503        if (remainder != null) {
504            boolean detailed;
505            String values = ObjectHelper.between(remainder, "(", ")");
506            if (values == null || ObjectHelper.isEmpty(values)) {
507                detailed = true;
508            } else {
509                detailed = Boolean.valueOf(values);
510            }
511            return ExpressionBuilder.messageHistoryExpression(detailed);
512        } else if (ObjectHelper.equal(function, "messageHistory")) {
513            return ExpressionBuilder.messageHistoryExpression(true);
514        }
515        return null;
516    }
517
518    private String ifStartsWithReturnRemainder(String prefix, String text) {
519        if (text.startsWith(prefix)) {
520            String remainder = text.substring(prefix.length());
521            if (remainder.length() > 0) {
522                return remainder;
523            }
524        }
525        return null;
526    }
527
528}