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