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