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.model.language;
018
019import java.util.List;
020import java.util.Map;
021
022import javax.xml.bind.annotation.XmlAccessType;
023import javax.xml.bind.annotation.XmlAccessorType;
024import javax.xml.bind.annotation.XmlAnyAttribute;
025import javax.xml.bind.annotation.XmlAttribute;
026import javax.xml.bind.annotation.XmlID;
027import javax.xml.bind.annotation.XmlRootElement;
028import javax.xml.bind.annotation.XmlTransient;
029import javax.xml.bind.annotation.XmlType;
030import javax.xml.bind.annotation.XmlValue;
031import javax.xml.namespace.QName;
032
033import org.apache.camel.AfterPropertiesConfigured;
034import org.apache.camel.CamelContext;
035import org.apache.camel.CamelContextAware;
036import org.apache.camel.Exchange;
037import org.apache.camel.Expression;
038import org.apache.camel.ExpressionFactory;
039import org.apache.camel.ExtendedCamelContext;
040import org.apache.camel.NoSuchLanguageException;
041import org.apache.camel.Predicate;
042import org.apache.camel.model.DefinitionPropertyPlaceholderConfigurer;
043import org.apache.camel.model.OtherAttributesAware;
044import org.apache.camel.spi.Language;
045import org.apache.camel.spi.Metadata;
046import org.apache.camel.spi.RouteContext;
047import org.apache.camel.support.ExpressionToPredicateAdapter;
048import org.apache.camel.support.ScriptHelper;
049import org.apache.camel.util.CollectionStringBuffer;
050import org.apache.camel.util.ObjectHelper;
051
052/**
053 * A useful base class for an expression
054 */
055@Metadata(label = "language", title = "Expression")
056@XmlRootElement
057@XmlType(name = "expression") // must be named expression
058@XmlAccessorType(XmlAccessType.FIELD)
059public class ExpressionDefinition implements Expression, Predicate, OtherAttributesAware, ExpressionFactory, DefinitionPropertyPlaceholderConfigurer {
060    @XmlAttribute
061    @XmlID
062    private String id;
063    @XmlValue
064    @Metadata(required = true)
065    private String expression;
066    @XmlAttribute
067    @Metadata(defaultValue = "true")
068    private Boolean trim;
069    @XmlTransient
070    private Predicate predicate;
071    @XmlTransient
072    private Expression expressionValue;
073    @XmlTransient
074    private ExpressionDefinition expressionType;
075    // use xs:any to support optional property placeholders
076    @XmlAnyAttribute
077    private Map<QName, Object> otherAttributes;
078
079    public ExpressionDefinition() {
080    }
081
082    public ExpressionDefinition(String expression) {
083        this.expression = expression;
084    }
085
086    public ExpressionDefinition(Predicate predicate) {
087        this.predicate = predicate;
088    }
089
090    public ExpressionDefinition(Expression expression) {
091        this.expressionValue = expression;
092    }
093
094    public static String getLabel(List<ExpressionDefinition> expressions) {
095        CollectionStringBuffer buffer = new CollectionStringBuffer();
096        for (ExpressionDefinition expression : expressions) {
097            buffer.append(expression.getLabel());
098        }
099        return buffer.toString();
100    }
101
102    @Override
103    public String toString() {
104        // favour using the output from expression value
105        if (getExpressionValue() != null) {
106            return getExpressionValue().toString();
107        }
108
109        StringBuilder sb = new StringBuilder();
110        if (getLanguage() != null) {
111            sb.append(getLanguage()).append("{");
112        }
113        if (getPredicate() != null) {
114            sb.append(getPredicate().toString());
115        } else if (getExpression() != null) {
116            sb.append(getExpression());
117        }
118        if (getLanguage() != null) {
119            sb.append("}");
120        }
121        return sb.toString();
122    }
123
124    public Object evaluate(Exchange exchange) {
125        return evaluate(exchange, Object.class);
126    }
127
128    @Override
129    public <T> T evaluate(Exchange exchange, Class<T> type) {
130        if (expressionValue == null) {
131            expressionValue = createExpression(exchange.getContext());
132        }
133        ObjectHelper.notNull(expressionValue, "expressionValue");
134        return expressionValue.evaluate(exchange, type);
135    }
136
137    public void assertMatches(String text, Exchange exchange) throws AssertionError {
138        if (!matches(exchange)) {
139            throw new AssertionError(text + getExpression() + " for exchange: " + exchange);
140        }
141    }
142
143    @Override
144    public boolean matches(Exchange exchange) {
145        if (predicate == null) {
146            predicate = createPredicate(exchange.getContext());
147        }
148        ObjectHelper.notNull(predicate, "predicate");
149        return predicate.matches(exchange);
150    }
151
152    public String getLanguage() {
153        return "";
154    }
155
156    public final Predicate createPredicate(RouteContext routeContext) {
157        return createPredicate(routeContext.getCamelContext());
158    }
159
160    public Predicate createPredicate(CamelContext camelContext) {
161        if (predicate == null) {
162            if (getExpressionType() != null) {
163                predicate = getExpressionType().createPredicate(camelContext);
164            } else if (getExpressionValue() != null) {
165                predicate = new ExpressionToPredicateAdapter(getExpressionValue());
166            } else if (getExpression() != null) {
167                ObjectHelper.notNull("language", getLanguage());
168                Language language = camelContext.resolveLanguage(getLanguage());
169                if (language == null) {
170                    throw new NoSuchLanguageException(getLanguage());
171                }
172                String exp = getExpression();
173                // should be true by default
174                boolean isTrim = getTrim() == null || getTrim();
175                // trim if configured to trim
176                if (exp != null && isTrim) {
177                    exp = exp.trim();
178                }
179                // resolve the expression as it may be an external script from
180                // the classpath/file etc
181                exp = ScriptHelper.resolveOptionalExternalScript(camelContext, exp);
182
183                predicate = language.createPredicate(exp);
184                configurePredicate(camelContext, predicate);
185            }
186        }
187        // inject CamelContext if its aware
188        if (predicate instanceof CamelContextAware) {
189            ((CamelContextAware)predicate).setCamelContext(camelContext);
190        }
191        return predicate;
192    }
193
194    public final Expression createExpression(RouteContext routeContext) {
195        return createExpression(routeContext.getCamelContext());
196    }
197
198    @Override
199    public Expression createExpression(CamelContext camelContext) {
200        if (getExpressionValue() == null) {
201            if (getExpressionType() != null) {
202                setExpressionValue(getExpressionType().createExpression(camelContext));
203            } else if (getExpression() != null) {
204                ObjectHelper.notNull("language", getLanguage());
205                Language language = camelContext.resolveLanguage(getLanguage());
206                if (language == null) {
207                    throw new NoSuchLanguageException(getLanguage());
208                }
209                String exp = getExpression();
210                // should be true by default
211                boolean isTrim = getTrim() == null || getTrim();
212                // trim if configured to trim
213                if (exp != null && isTrim) {
214                    exp = exp.trim();
215                }
216                // resolve the expression as it may be an external script from
217                // the classpath/file etc
218                exp = ScriptHelper.resolveOptionalExternalScript(camelContext, exp);
219
220                setExpressionValue(language.createExpression(exp));
221                configureExpression(camelContext, getExpressionValue());
222            }
223        }
224        // inject CamelContext if its aware
225        if (getExpressionValue() instanceof CamelContextAware) {
226            ((CamelContextAware)getExpressionValue()).setCamelContext(camelContext);
227        }
228        return getExpressionValue();
229    }
230
231    public String getExpression() {
232        return expression;
233    }
234
235    /**
236     * The expression value in your chosen language syntax
237     */
238    public void setExpression(String expression) {
239        this.expression = expression;
240    }
241
242    public String getId() {
243        return id;
244    }
245
246    /**
247     * Sets the id of this node
248     */
249    public void setId(String value) {
250        this.id = value;
251    }
252
253    public Predicate getPredicate() {
254        return predicate;
255    }
256
257    public Expression getExpressionValue() {
258        return expressionValue;
259    }
260
261    protected void setExpressionValue(Expression expressionValue) {
262        this.expressionValue = expressionValue;
263    }
264
265    public ExpressionDefinition getExpressionType() {
266        return expressionType;
267    }
268
269    public Boolean getTrim() {
270        return trim;
271    }
272
273    /**
274     * Whether to trim the value to remove leading and trailing whitespaces and
275     * line breaks
276     */
277    public void setTrim(Boolean trim) {
278        this.trim = trim;
279    }
280
281    @Override
282    public Map<QName, Object> getOtherAttributes() {
283        return otherAttributes;
284    }
285
286    @Override
287    public void setOtherAttributes(Map<QName, Object> otherAttributes) {
288        this.otherAttributes = otherAttributes;
289    }
290
291    /**
292     * Returns some descriptive text to describe this node
293     */
294    public String getLabel() {
295        Predicate predicate = getPredicate();
296        if (predicate != null) {
297            return predicate.toString();
298        }
299        Expression expressionValue = getExpressionValue();
300        if (expressionValue != null) {
301            return expressionValue.toString();
302        }
303
304        String exp = getExpression();
305        return exp != null ? exp : "";
306    }
307
308    /**
309     * Allows derived classes to set a lazily created expressionType instance
310     * such as if using the {@link org.apache.camel.builder.ExpressionClause}
311     */
312    protected void setExpressionType(ExpressionDefinition expressionType) {
313        this.expressionType = expressionType;
314    }
315
316    protected void configurePredicate(CamelContext camelContext, Predicate predicate) {
317        // allows to perform additional logic after the properties has been
318        // configured which may be needed
319        // in the various camel components outside camel-core
320        if (predicate instanceof AfterPropertiesConfigured) {
321            ((AfterPropertiesConfigured)predicate).afterPropertiesConfigured(camelContext);
322        }
323    }
324
325    protected void configureExpression(CamelContext camelContext, Expression expression) {
326        // allows to perform additional logic after the properties has been
327        // configured which may be needed
328        // in the various camel components outside camel-core
329        if (expression instanceof AfterPropertiesConfigured) {
330            ((AfterPropertiesConfigured)expression).afterPropertiesConfigured(camelContext);
331        }
332    }
333
334    /**
335     * Sets a named property on the object instance using introspection
336     */
337    protected void setProperty(CamelContext camelContext, Object bean, String name, Object value) {
338        try {
339            camelContext.adapt(ExtendedCamelContext.class).getBeanIntrospection().setProperty(camelContext, bean, name, value);
340        } catch (Exception e) {
341            throw new IllegalArgumentException("Failed to set property " + name + " on " + bean + ". Reason: " + e, e);
342        }
343    }
344}