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