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