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 javax.xml.bind.annotation.XmlAccessType;
020import javax.xml.bind.annotation.XmlAccessorType;
021import javax.xml.bind.annotation.XmlAttribute;
022import javax.xml.bind.annotation.XmlRootElement;
023import javax.xml.bind.annotation.XmlTransient;
024import javax.xml.xpath.XPathFactory;
025
026import org.apache.camel.CamelContext;
027import org.apache.camel.Expression;
028import org.apache.camel.Predicate;
029import org.apache.camel.builder.xml.XPathBuilder;
030import org.apache.camel.spi.Metadata;
031import org.apache.camel.util.ObjectHelper;
032
033/**
034 * To use XPath (XML) in Camel expressions or predicates.
035 */
036@Metadata(firstVersion = "1.1.0", label = "language,core,xml", title = "XPath")
037@XmlRootElement(name = "xpath")
038@XmlAccessorType(XmlAccessType.FIELD)
039public class XPathExpression extends NamespaceAwareExpression {
040    @XmlAttribute(name = "documentType")
041    private String documentTypeName;
042    @XmlAttribute(name = "resultType") @Metadata(defaultValue = "NODESET", enums = "NUMBER,STRING,BOOLEAN,NODESET,NODE")
043    private String resultTypeName;
044    @XmlAttribute
045    private Boolean saxon;
046    @XmlAttribute @Metadata(label = "advanced")
047    private String factoryRef;
048    @XmlAttribute @Metadata(label = "advanced")
049    private String objectModel;
050    @XmlAttribute
051    private Boolean logNamespaces;
052    @XmlAttribute
053    private String headerName;
054    @XmlTransient
055    private Class<?> documentType;
056    @XmlTransient
057    private Class<?> resultType;
058    @XmlTransient
059    private XPathFactory xpathFactory;
060    @XmlAttribute @Metadata(label = "advanced")
061    private Boolean threadSafety;
062
063    public XPathExpression() {
064    }
065
066    public XPathExpression(String expression) {
067        super(expression);
068    }
069
070    public XPathExpression(Expression expression) {
071        setExpressionValue(expression);
072    }
073
074    public String getLanguage() {
075        return "xpath";
076    }
077
078    public Class<?> getDocumentType() {
079        return documentType;
080    }
081
082    /**
083     * Class for document type to use
084     * <p/>
085     * The default value is org.w3c.dom.Document
086     */
087    public void setDocumentType(Class<?> documentType) {
088        this.documentType = documentType;
089    }
090
091    public String getDocumentTypeName() {
092        return documentTypeName;
093    }
094
095    /**
096     * Name of class for document type
097     * <p/>
098     * The default value is org.w3c.dom.Document
099     */
100    public void setDocumentTypeName(String documentTypeName) {
101        this.documentTypeName = documentTypeName;
102    }
103
104    public Class<?> getResultType() {
105        return resultType;
106    }
107
108    /**
109     * Sets the class of the result type (type from output).
110     * <p/>
111     * The default result type is NodeSet
112     */
113    public void setResultType(Class<?> resultType) {
114        this.resultType = resultType;
115    }
116
117    public String getResultTypeName() {
118        return resultTypeName;
119    }
120
121    /**
122     * Sets the class name of the result type (type from output)
123     * <p/>
124     * The default result type is NodeSet
125     */
126    public void setResultTypeName(String resultTypeName) {
127        this.resultTypeName = resultTypeName;
128    }
129
130    /**
131     * Whether to use Saxon.
132     */
133    public void setSaxon(Boolean saxon) {
134        this.saxon = saxon;
135    }
136
137    public Boolean getSaxon() {
138        return saxon;
139    }
140
141    /**
142     * References to a custom XPathFactory to lookup in the registry
143     */
144    public void setFactoryRef(String factoryRef) {
145        this.factoryRef = factoryRef;
146    }
147
148    public String getFactoryRef() {
149        return factoryRef;
150    }
151
152    /**
153     * The XPath object model to use
154     */
155    public void setObjectModel(String objectModel) {
156        this.objectModel = objectModel;
157    }
158
159    public String getObjectModel() {
160        return objectModel;
161    }
162
163    /**
164     * Whether to log namespaces which can assist during trouble shooting
165     */
166    public void setLogNamespaces(Boolean logNamespaces) {
167        this.logNamespaces = logNamespaces;
168    }
169
170    public Boolean getLogNamespaces() {
171        return logNamespaces;
172    }
173
174    public String getHeaderName() {
175        return headerName;
176    }
177
178    /**
179     * Name of header to use as input, instead of the message body
180     */
181    public void setHeaderName(String headerName) {
182        this.headerName = headerName;
183    }
184
185    public Boolean getThreadSafety() {
186        return threadSafety;
187    }
188
189    /**
190     * Whether to enable thread-safety for the returned result of the xpath expression.
191     * This applies to when using NODESET as the result type, and the returned set has
192     * multiple elements. In this situation there can be thread-safety issues if you
193     * process the NODESET concurrently such as from a Camel Splitter EIP in parallel processing mode.
194     * This option prevents concurrency issues by doing defensive copies of the nodes.
195     * <p/>
196     * It is recommended to turn this option on if you are using camel-saxon or Saxon in your application.
197     * Saxon has thread-safety issues which can be prevented by turning this option on.
198     */
199    public void setThreadSafety(Boolean threadSafety) {
200        this.threadSafety = threadSafety;
201    }
202
203    @Override
204    public Expression createExpression(CamelContext camelContext) {
205        if (documentType == null && documentTypeName != null) {
206            try {
207                documentType = camelContext.getClassResolver().resolveMandatoryClass(documentTypeName);
208            } catch (ClassNotFoundException e) {
209                throw ObjectHelper.wrapRuntimeCamelException(e);
210            }
211        }
212        if (resultType == null && resultTypeName != null) {
213            try {
214                resultType = camelContext.getClassResolver().resolveMandatoryClass(resultTypeName);
215            } catch (ClassNotFoundException e) {
216                throw ObjectHelper.wrapRuntimeCamelException(e);
217            }
218        }
219        resolveXPathFactory(camelContext);
220        return super.createExpression(camelContext);
221    }
222
223    @Override
224    public Predicate createPredicate(CamelContext camelContext) {
225        if (documentType == null && documentTypeName != null) {
226            try {
227                documentType = camelContext.getClassResolver().resolveMandatoryClass(documentTypeName);
228            } catch (ClassNotFoundException e) {
229                throw ObjectHelper.wrapRuntimeCamelException(e);
230            }
231        }
232        resolveXPathFactory(camelContext);
233        return super.createPredicate(camelContext);
234    }
235
236    @Override
237    protected void configureExpression(CamelContext camelContext, Expression expression) {
238        boolean isSaxon = getSaxon() != null && getSaxon();
239        boolean isLogNamespaces = getLogNamespaces() != null && getLogNamespaces();
240
241        if (documentType != null) {
242            setProperty(expression, "documentType", documentType);
243        }
244        if (resultType != null) {
245            setProperty(expression, "resultType", resultType);
246        }
247        if (isSaxon) {
248            ObjectHelper.cast(XPathBuilder.class, expression).enableSaxon();
249        }
250        if (xpathFactory != null) {
251            setProperty(expression, "xPathFactory", xpathFactory);
252        }
253        if (objectModel != null) {
254            setProperty(expression, "objectModelUri", objectModel);
255        }
256        if (threadSafety != null) {
257            setProperty(expression, "threadSafety", threadSafety);
258        }
259        if (isLogNamespaces) {
260            ObjectHelper.cast(XPathBuilder.class, expression).setLogNamespaces(true);
261        }
262        if (ObjectHelper.isNotEmpty(getHeaderName())) {
263            ObjectHelper.cast(XPathBuilder.class, expression).setHeaderName(getHeaderName());
264        }
265        // moved the super configuration to the bottom so that the namespace init picks up the newly set XPath Factory
266        super.configureExpression(camelContext, expression);
267    }
268
269    @Override
270    protected void configurePredicate(CamelContext camelContext, Predicate predicate) {
271        boolean isSaxon = getSaxon() != null && getSaxon();
272        boolean isLogNamespaces = getLogNamespaces() != null && getLogNamespaces();
273
274        if (documentType != null) {
275            setProperty(predicate, "documentType", documentType);
276        }
277        if (resultType != null) {
278            setProperty(predicate, "resultType", resultType);
279        }
280        if (isSaxon) {
281            ObjectHelper.cast(XPathBuilder.class, predicate).enableSaxon();
282        }
283        if (xpathFactory != null) {
284            setProperty(predicate, "xPathFactory", xpathFactory);
285        }
286        if (objectModel != null) {
287            setProperty(predicate, "objectModelUri", objectModel);
288        }
289        if (threadSafety != null) {
290            setProperty(predicate, "threadSafety", threadSafety);
291        }
292        if (isLogNamespaces) {
293            ObjectHelper.cast(XPathBuilder.class, predicate).setLogNamespaces(true);
294        }
295        if (ObjectHelper.isNotEmpty(getHeaderName())) {
296            ObjectHelper.cast(XPathBuilder.class, predicate).setHeaderName(getHeaderName());
297        }
298        // moved the super configuration to the bottom so that the namespace init picks up the newly set XPath Factory
299        super.configurePredicate(camelContext, predicate);
300    }
301
302    private void resolveXPathFactory(CamelContext camelContext) {
303        // Factory and Object Model can be set simultaneously. The underlying XPathBuilder allows for setting Saxon too, as it is simply a shortcut for
304        // setting the appropriate Object Model, it is not wise to allow this in XML because the order of invocation of the setters by JAXB may cause undeterministic behaviour 
305        if ((ObjectHelper.isNotEmpty(factoryRef) || ObjectHelper.isNotEmpty(objectModel)) && (saxon != null)) {
306            throw new IllegalArgumentException("The saxon attribute cannot be set on the xpath element if any of the following is also set: factory, objectModel" + this);
307        }
308
309        // Validate the factory class
310        if (ObjectHelper.isNotEmpty(factoryRef)) {
311            xpathFactory = camelContext.getRegistry().lookupByNameAndType(factoryRef, XPathFactory.class);
312            if (xpathFactory == null) {
313                throw new IllegalArgumentException("The provided XPath Factory is invalid; either it cannot be resolved or it is not an XPathFactory instance");
314            }
315        }
316    }
317}