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        // file: prefix
132        remainder = ifStartsWithReturnRemainder("file:", function);
133        if (remainder != null) {
134            Expression fileExpression = createSimpleFileExpression(remainder);
135            if (function != null) {
136                return fileExpression;
137            }
138        }
139
140        // date: prefix
141        remainder = ifStartsWithReturnRemainder("date:", function);
142        if (remainder != null) {
143            String[] parts = remainder.split(":");
144            if (parts.length < 2) {
145                throw new SimpleParserException("Valid syntax: ${date:command:pattern} was: " + function, token.getIndex());
146            }
147            String command = ObjectHelper.before(remainder, ":");
148            String pattern = ObjectHelper.after(remainder, ":");
149            return ExpressionBuilder.dateExpression(command, pattern);
150        }
151
152        // bean: prefix
153        remainder = ifStartsWithReturnRemainder("bean:", function);
154        if (remainder != null) {
155            return ExpressionBuilder.beanExpression(remainder);
156        }
157
158        // properties: prefix
159        remainder = ifStartsWithReturnRemainder("properties:", function);
160        if (remainder != null) {
161            String[] parts = remainder.split(":");
162            if (parts.length > 2) {
163                throw new SimpleParserException("Valid syntax: ${properties:key[:default]} was: " + function, token.getIndex());
164            }
165            return ExpressionBuilder.propertiesComponentExpression(remainder, null);
166        }
167
168        // properties-location: prefix
169        remainder = ifStartsWithReturnRemainder("properties-location:", function);
170        if (remainder != null) {
171            String[] parts = remainder.split(":");
172            if (parts.length > 3) {
173                throw new SimpleParserException("Valid syntax: ${properties-location:location:key[:default]} was: " + function, token.getIndex());
174            }
175
176            String locations = null;
177            String key = remainder;
178            if (parts.length >= 2) {
179                locations = ObjectHelper.before(remainder, ":");
180                key = ObjectHelper.after(remainder, ":");
181            }
182            return ExpressionBuilder.propertiesComponentExpression(key, locations);
183        }
184
185        // ref: prefix
186        remainder = ifStartsWithReturnRemainder("ref:", function);
187        if (remainder != null) {
188            return ExpressionBuilder.refExpression(remainder);
189        }
190
191        // const: prefix
192        remainder = ifStartsWithReturnRemainder("type:", function);
193        if (remainder != null) {
194            Expression exp = ExpressionBuilder.typeExpression(remainder);
195            // we want to cache this expression so we wont re-evaluate it as the type/constant wont change
196            return ExpressionBuilder.cacheExpression(exp);
197        }
198
199        if (strict) {
200            throw new SimpleParserException("Unknown function: " + function, token.getIndex());
201        } else {
202            return null;
203        }
204    }
205
206    private Expression createSimpleExpressionBodyOrHeader(String function, boolean strict) {
207        // bodyAs
208        String remainder = ifStartsWithReturnRemainder("bodyAs", function);
209        if (remainder != null) {
210            String type = ObjectHelper.between(remainder, "(", ")");
211            remainder = ObjectHelper.after(remainder, ")");
212            if (type == null || ObjectHelper.isNotEmpty(remainder)) {
213                throw new SimpleParserException("Valid syntax: ${bodyAs(type)} was: " + function, token.getIndex());
214            }
215            
216            type = StringHelper.removeQuotes(type);
217            return ExpressionBuilder.bodyExpression(type);
218        }
219        // mandatoryBodyAs
220        remainder = ifStartsWithReturnRemainder("mandatoryBodyAs", function);
221        if (remainder != null) {
222            String type = ObjectHelper.between(remainder, "(", ")");
223            remainder = ObjectHelper.after(remainder, ")");
224            if (type == null || ObjectHelper.isNotEmpty(remainder)) {
225                throw new SimpleParserException("Valid syntax: ${mandatoryBodyAs(type)} was: " + function, token.getIndex());
226            }
227            type = StringHelper.removeQuotes(type);
228            return ExpressionBuilder.mandatoryBodyExpression(type);
229        }
230
231        // body OGNL
232        remainder = ifStartsWithReturnRemainder("body", function);
233        if (remainder == null) {
234            remainder = ifStartsWithReturnRemainder("in.body", function);
235        }
236        if (remainder != null) {
237            boolean invalid = OgnlHelper.isInvalidValidOgnlExpression(remainder);
238            if (invalid) {
239                throw new SimpleParserException("Valid syntax: ${body.OGNL} was: " + function, token.getIndex());
240            }
241            return ExpressionBuilder.bodyOgnlExpression(remainder);
242        }
243
244        // headerAs
245        remainder = ifStartsWithReturnRemainder("headerAs", function);
246        if (remainder != null) {
247            String keyAndType = ObjectHelper.between(remainder, "(", ")");
248            if (keyAndType == null) {
249                throw new SimpleParserException("Valid syntax: ${headerAs(key, type)} was: " + function, token.getIndex());
250            }
251
252            String key = ObjectHelper.before(keyAndType, ",");
253            String type = ObjectHelper.after(keyAndType, ",");
254            remainder = ObjectHelper.after(remainder, ")");
255            if (ObjectHelper.isEmpty(key) || ObjectHelper.isEmpty(type) || ObjectHelper.isNotEmpty(remainder)) {
256                throw new SimpleParserException("Valid syntax: ${headerAs(key, type)} was: " + function, token.getIndex());
257            }
258            key = StringHelper.removeQuotes(key);
259            type = StringHelper.removeQuotes(type);
260            return ExpressionBuilder.headerExpression(key, type);
261        }
262
263        // headers function
264        if ("in.headers".equals(function) || "headers".equals(function)) {
265            return ExpressionBuilder.headersExpression();
266        }
267
268        // in header function
269        remainder = ifStartsWithReturnRemainder("in.headers", function);
270        if (remainder == null) {
271            remainder = ifStartsWithReturnRemainder("in.header", function);
272        }
273        if (remainder == null) {
274            remainder = ifStartsWithReturnRemainder("headers", function);
275        }
276        if (remainder == null) {
277            remainder = ifStartsWithReturnRemainder("header", function);
278        }
279        if (remainder != null) {
280            // remove leading character (dot or ?)
281            if (remainder.startsWith(".") || remainder.startsWith("?")) {
282                remainder = remainder.substring(1);
283            }
284            // remove starting and ending brackets
285            if (remainder.startsWith("[") && remainder.endsWith("]")) {
286                remainder = remainder.substring(1, remainder.length() - 1);
287            }
288            // remove quotes from key
289            String key = StringHelper.removeLeadingAndEndingQuotes(remainder);
290
291            // validate syntax
292            boolean invalid = OgnlHelper.isInvalidValidOgnlExpression(key);
293            if (invalid) {
294                throw new SimpleParserException("Valid syntax: ${header.name[key]} was: " + function, token.getIndex());
295            }
296
297            if (OgnlHelper.isValidOgnlExpression(key)) {
298                // ognl based header
299                return ExpressionBuilder.headersOgnlExpression(key);
300            } else {
301                // regular header
302                return ExpressionBuilder.headerExpression(key);
303            }
304        }
305
306        // out header function
307        remainder = ifStartsWithReturnRemainder("out.header.", function);
308        if (remainder == null) {
309            remainder = ifStartsWithReturnRemainder("out.headers.", function);
310        }
311        if (remainder != null) {
312            return ExpressionBuilder.outHeaderExpression(remainder);
313        }
314
315        return null;
316    }
317
318    private Expression createSimpleExpressionDirectly(String expression) {
319        if (ObjectHelper.isEqualToAny(expression, "body", "in.body")) {
320            return ExpressionBuilder.bodyExpression();
321        } else if (ObjectHelper.equal(expression, "out.body")) {
322            return ExpressionBuilder.outBodyExpression();
323        } else if (ObjectHelper.equal(expression, "id")) {
324            return ExpressionBuilder.messageIdExpression();
325        } else if (ObjectHelper.equal(expression, "exchangeId")) {
326            return ExpressionBuilder.exchangeIdExpression();
327        } else if (ObjectHelper.equal(expression, "exception")) {
328            return ExpressionBuilder.exchangeExceptionExpression();
329        } else if (ObjectHelper.equal(expression, "exception.message")) {
330            return ExpressionBuilder.exchangeExceptionMessageExpression();
331        } else if (ObjectHelper.equal(expression, "exception.stacktrace")) {
332            return ExpressionBuilder.exchangeExceptionStackTraceExpression();
333        } else if (ObjectHelper.equal(expression, "threadName")) {
334            return ExpressionBuilder.threadNameExpression();
335        } else if (ObjectHelper.equal(expression, "camelId")) {
336            return ExpressionBuilder.camelContextNameExpression();
337        } else if (ObjectHelper.equal(expression, "routeId")) {
338            return ExpressionBuilder.routeIdExpression();
339        } else if (ObjectHelper.equal(expression, "null")) {
340            return ExpressionBuilder.nullExpression();
341        }
342
343        return null;
344    }
345
346    private Expression createSimpleFileExpression(String remainder) {
347        if (ObjectHelper.equal(remainder, "name")) {
348            return ExpressionBuilder.fileNameExpression();
349        } else if (ObjectHelper.equal(remainder, "name.noext")) {
350            return ExpressionBuilder.fileNameNoExtensionExpression();
351        } else if (ObjectHelper.equal(remainder, "name.noext.single")) {
352            return ExpressionBuilder.fileNameNoExtensionSingleExpression();
353        } else if (ObjectHelper.equal(remainder, "name.ext") || ObjectHelper.equal(remainder, "ext")) {
354            return ExpressionBuilder.fileExtensionExpression();
355        } else if (ObjectHelper.equal(remainder, "name.ext.single")) {
356            return ExpressionBuilder.fileExtensionSingleExpression();
357        } else if (ObjectHelper.equal(remainder, "onlyname")) {
358            return ExpressionBuilder.fileOnlyNameExpression();
359        } else if (ObjectHelper.equal(remainder, "onlyname.noext")) {
360            return ExpressionBuilder.fileOnlyNameNoExtensionExpression();
361        } else if (ObjectHelper.equal(remainder, "onlyname.noext.single")) {
362            return ExpressionBuilder.fileOnlyNameNoExtensionSingleExpression();
363        } else if (ObjectHelper.equal(remainder, "parent")) {
364            return ExpressionBuilder.fileParentExpression();
365        } else if (ObjectHelper.equal(remainder, "path")) {
366            return ExpressionBuilder.filePathExpression();
367        } else if (ObjectHelper.equal(remainder, "absolute")) {
368            return ExpressionBuilder.fileAbsoluteExpression();
369        } else if (ObjectHelper.equal(remainder, "absolute.path")) {
370            return ExpressionBuilder.fileAbsolutePathExpression();
371        } else if (ObjectHelper.equal(remainder, "length") || ObjectHelper.equal(remainder, "size")) {
372            return ExpressionBuilder.fileSizeExpression();
373        } else if (ObjectHelper.equal(remainder, "modified")) {
374            return ExpressionBuilder.fileLastModifiedExpression();
375        }
376        throw new SimpleParserException("Unknown file language syntax: " + remainder, token.getIndex());
377    }
378
379    private String ifStartsWithReturnRemainder(String prefix, String text) {
380        if (text.startsWith(prefix)) {
381            String remainder = text.substring(prefix.length());
382            if (remainder.length() > 0) {
383                return remainder;
384            }
385        }
386        return null;
387    }
388
389}