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        if (strict) {
210            throw new SimpleParserException("Unknown function: " + function, token.getIndex());
211        } else {
212            return null;
213        }
214    }
215
216    private Expression createSimpleExpressionBodyOrHeader(String function, boolean strict) {
217        // bodyAs
218        String remainder = ifStartsWithReturnRemainder("bodyAs", function);
219        if (remainder != null) {
220            String type = ObjectHelper.between(remainder, "(", ")");
221            remainder = ObjectHelper.after(remainder, ")");
222            if (type == null || ObjectHelper.isNotEmpty(remainder)) {
223                throw new SimpleParserException("Valid syntax: ${bodyAs(type)} was: " + function, token.getIndex());
224            }
225            
226            type = StringHelper.removeQuotes(type);
227            return ExpressionBuilder.bodyExpression(type);
228        }
229        // mandatoryBodyAs
230        remainder = ifStartsWithReturnRemainder("mandatoryBodyAs", function);
231        if (remainder != null) {
232            String type = ObjectHelper.between(remainder, "(", ")");
233            remainder = ObjectHelper.after(remainder, ")");
234            if (type == null || ObjectHelper.isNotEmpty(remainder)) {
235                throw new SimpleParserException("Valid syntax: ${mandatoryBodyAs(type)} was: " + function, token.getIndex());
236            }
237            type = StringHelper.removeQuotes(type);
238            return ExpressionBuilder.mandatoryBodyExpression(type);
239        }
240
241        // body OGNL
242        remainder = ifStartsWithReturnRemainder("body", function);
243        if (remainder == null) {
244            remainder = ifStartsWithReturnRemainder("in.body", function);
245        }
246        if (remainder != null) {
247            boolean invalid = OgnlHelper.isInvalidValidOgnlExpression(remainder);
248            if (invalid) {
249                throw new SimpleParserException("Valid syntax: ${body.OGNL} was: " + function, token.getIndex());
250            }
251            return ExpressionBuilder.bodyOgnlExpression(remainder);
252        }
253
254        // headerAs
255        remainder = ifStartsWithReturnRemainder("headerAs", function);
256        if (remainder != null) {
257            String keyAndType = ObjectHelper.between(remainder, "(", ")");
258            if (keyAndType == null) {
259                throw new SimpleParserException("Valid syntax: ${headerAs(key, type)} was: " + function, token.getIndex());
260            }
261
262            String key = ObjectHelper.before(keyAndType, ",");
263            String type = ObjectHelper.after(keyAndType, ",");
264            remainder = ObjectHelper.after(remainder, ")");
265            if (ObjectHelper.isEmpty(key) || ObjectHelper.isEmpty(type) || ObjectHelper.isNotEmpty(remainder)) {
266                throw new SimpleParserException("Valid syntax: ${headerAs(key, type)} was: " + function, token.getIndex());
267            }
268            key = StringHelper.removeQuotes(key);
269            type = StringHelper.removeQuotes(type);
270            return ExpressionBuilder.headerExpression(key, type);
271        }
272
273        // headers function
274        if ("in.headers".equals(function) || "headers".equals(function)) {
275            return ExpressionBuilder.headersExpression();
276        }
277
278        // in header function
279        remainder = ifStartsWithReturnRemainder("in.headers", function);
280        if (remainder == null) {
281            remainder = ifStartsWithReturnRemainder("in.header", function);
282        }
283        if (remainder == null) {
284            remainder = ifStartsWithReturnRemainder("headers", function);
285        }
286        if (remainder == null) {
287            remainder = ifStartsWithReturnRemainder("header", function);
288        }
289        if (remainder != null) {
290            // remove leading character (dot or ?)
291            if (remainder.startsWith(".") || remainder.startsWith("?")) {
292                remainder = remainder.substring(1);
293            }
294            // remove starting and ending brackets
295            if (remainder.startsWith("[") && remainder.endsWith("]")) {
296                remainder = remainder.substring(1, remainder.length() - 1);
297            }
298            // remove quotes from key
299            String key = StringHelper.removeLeadingAndEndingQuotes(remainder);
300
301            // validate syntax
302            boolean invalid = OgnlHelper.isInvalidValidOgnlExpression(key);
303            if (invalid) {
304                throw new SimpleParserException("Valid syntax: ${header.name[key]} was: " + function, token.getIndex());
305            }
306
307            if (OgnlHelper.isValidOgnlExpression(key)) {
308                // ognl based header
309                return ExpressionBuilder.headersOgnlExpression(key);
310            } else {
311                // regular header
312                return ExpressionBuilder.headerExpression(key);
313            }
314        }
315
316        // out header function
317        remainder = ifStartsWithReturnRemainder("out.header.", function);
318        if (remainder == null) {
319            remainder = ifStartsWithReturnRemainder("out.headers.", function);
320        }
321        if (remainder != null) {
322            return ExpressionBuilder.outHeaderExpression(remainder);
323        }
324        
325        // random
326        remainder = ifStartsWithReturnRemainder("random", function);
327        if (remainder != null) {
328            String values = ObjectHelper.between(remainder, "(", ")");
329            if (values == null || ObjectHelper.isEmpty(values)) {
330                throw new SimpleParserException("Valid syntax: ${random(min,max)} or ${random(max)} was: " + function, token.getIndex());
331            }
332            if (values.contains(",")) {
333                String[] tokens = values.split(",", -1);
334                if (tokens.length > 2) {
335                    throw new SimpleParserException("Valid syntax: ${random(min,max)} or ${random(max)} was: " + function, token.getIndex());
336                }
337                int min = Integer.parseInt(tokens[0].trim());
338                int max = Integer.parseInt(tokens[1].trim());
339                return ExpressionBuilder.randomExpression(min, max);
340            } else {
341                int max = Integer.parseInt(values.trim());
342                return ExpressionBuilder.randomExpression(max);
343            }
344        }
345
346        return null;
347    }
348
349    private Expression createSimpleExpressionDirectly(String expression) {
350        if (ObjectHelper.isEqualToAny(expression, "body", "in.body")) {
351            return ExpressionBuilder.bodyExpression();
352        } else if (ObjectHelper.equal(expression, "out.body")) {
353            return ExpressionBuilder.outBodyExpression();
354        } else if (ObjectHelper.equal(expression, "id")) {
355            return ExpressionBuilder.messageIdExpression();
356        } else if (ObjectHelper.equal(expression, "exchangeId")) {
357            return ExpressionBuilder.exchangeIdExpression();
358        } else if (ObjectHelper.equal(expression, "exchange")) {
359            return ExpressionBuilder.exchangeExpression();
360        } else if (ObjectHelper.equal(expression, "exception")) {
361            return ExpressionBuilder.exchangeExceptionExpression();
362        } else if (ObjectHelper.equal(expression, "exception.message")) {
363            return ExpressionBuilder.exchangeExceptionMessageExpression();
364        } else if (ObjectHelper.equal(expression, "exception.stacktrace")) {
365            return ExpressionBuilder.exchangeExceptionStackTraceExpression();
366        } else if (ObjectHelper.equal(expression, "threadName")) {
367            return ExpressionBuilder.threadNameExpression();
368        } else if (ObjectHelper.equal(expression, "camelId")) {
369            return ExpressionBuilder.camelContextNameExpression();
370        } else if (ObjectHelper.equal(expression, "routeId")) {
371            return ExpressionBuilder.routeIdExpression();
372        } else if (ObjectHelper.equal(expression, "null")) {
373            return ExpressionBuilder.nullExpression();
374        }
375
376        return null;
377    }
378
379    private Expression createSimpleFileExpression(String remainder, boolean strict) {
380        if (ObjectHelper.equal(remainder, "name")) {
381            return ExpressionBuilder.fileNameExpression();
382        } else if (ObjectHelper.equal(remainder, "name.noext")) {
383            return ExpressionBuilder.fileNameNoExtensionExpression();
384        } else if (ObjectHelper.equal(remainder, "name.noext.single")) {
385            return ExpressionBuilder.fileNameNoExtensionSingleExpression();
386        } else if (ObjectHelper.equal(remainder, "name.ext") || ObjectHelper.equal(remainder, "ext")) {
387            return ExpressionBuilder.fileExtensionExpression();
388        } else if (ObjectHelper.equal(remainder, "name.ext.single")) {
389            return ExpressionBuilder.fileExtensionSingleExpression();
390        } else if (ObjectHelper.equal(remainder, "onlyname")) {
391            return ExpressionBuilder.fileOnlyNameExpression();
392        } else if (ObjectHelper.equal(remainder, "onlyname.noext")) {
393            return ExpressionBuilder.fileOnlyNameNoExtensionExpression();
394        } else if (ObjectHelper.equal(remainder, "onlyname.noext.single")) {
395            return ExpressionBuilder.fileOnlyNameNoExtensionSingleExpression();
396        } else if (ObjectHelper.equal(remainder, "parent")) {
397            return ExpressionBuilder.fileParentExpression();
398        } else if (ObjectHelper.equal(remainder, "path")) {
399            return ExpressionBuilder.filePathExpression();
400        } else if (ObjectHelper.equal(remainder, "absolute")) {
401            return ExpressionBuilder.fileAbsoluteExpression();
402        } else if (ObjectHelper.equal(remainder, "absolute.path")) {
403            return ExpressionBuilder.fileAbsolutePathExpression();
404        } else if (ObjectHelper.equal(remainder, "length") || ObjectHelper.equal(remainder, "size")) {
405            return ExpressionBuilder.fileSizeExpression();
406        } else if (ObjectHelper.equal(remainder, "modified")) {
407            return ExpressionBuilder.fileLastModifiedExpression();
408        }
409        if (strict) {
410            throw new SimpleParserException("Unknown file language syntax: " + remainder, token.getIndex());
411        }
412        return null;
413    }
414
415    private String ifStartsWithReturnRemainder(String prefix, String text) {
416        if (text.startsWith(prefix)) {
417            String remainder = text.substring(prefix.length());
418            if (remainder.length() > 0) {
419                return remainder;
420            }
421        }
422        return null;
423    }
424
425}