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 * For XPath expressions and predicates
035 */
036@Metadata(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")
043    private String resultTypeName;
044    @XmlAttribute
045    private Boolean saxon;
046    @XmlAttribute
047    private String factoryRef;
048    @XmlAttribute
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    
061    public XPathExpression() {
062    }
063
064    public XPathExpression(String expression) {
065        super(expression);
066    }
067
068    public XPathExpression(Expression expression) {
069        setExpressionValue(expression);
070    }
071
072    public String getLanguage() {
073        return "xpath";
074    }
075
076    public Class<?> getDocumentType() {
077        return documentType;
078    }
079
080    /**
081     * Class for document type to use
082     * <p/>
083     * The default value is org.w3c.dom.Document
084     */
085    public void setDocumentType(Class<?> documentType) {
086        this.documentType = documentType;
087    }
088
089    public String getDocumentTypeName() {
090        return documentTypeName;
091    }
092
093    /**
094     * Name of class for document type
095     * <p/>
096     * The default value is org.w3c.dom.Document
097     */
098    public void setDocumentTypeName(String documentTypeName) {
099        this.documentTypeName = documentTypeName;
100    }
101
102    public Class<?> getResultType() {
103        return resultType;
104    }
105
106    /**
107     * Sets the class of the result type (type from output).
108     * <p/>
109     * The default result type is NodeSet
110     */
111    public void setResultType(Class<?> resultType) {
112        this.resultType = resultType;
113    }
114
115    public String getResultTypeName() {
116        return resultTypeName;
117    }
118
119    /**
120     * Sets the class name of the result type (type from output)
121     * <p/>
122     * The default result type is NodeSet
123     */
124    public void setResultTypeName(String resultTypeName) {
125        this.resultTypeName = resultTypeName;
126    }
127
128    /**
129     * Whether to use Saxon.
130     */
131    public void setSaxon(Boolean saxon) {
132        this.saxon = saxon;
133    }
134
135    public Boolean getSaxon() {
136        return saxon;
137    }
138
139    /**
140     * References to a custom XPathFactory to lookup in the registry
141     */
142    public void setFactoryRef(String factoryRef) {
143        this.factoryRef = factoryRef;
144    }
145
146    public String getFactoryRef() {
147        return factoryRef;
148    }
149
150    /**
151     * The XPath object model to use
152     */
153    public void setObjectModel(String objectModel) {
154        this.objectModel = objectModel;
155    }
156
157    public String getObjectModel() {
158        return objectModel;
159    }
160
161    /**
162     * Whether to log namespaces which can assist during trouble shooting
163     */
164    public void setLogNamespaces(Boolean logNamespaces) {
165        this.logNamespaces = logNamespaces;
166    }
167
168    public Boolean getLogNamespaces() {
169        return logNamespaces;
170    }
171
172    public String getHeaderName() {
173        return headerName;
174    }
175
176    /**
177     * Name of header to use as input, instead of the message body
178     */
179    public void setHeaderName(String headerName) {
180        this.headerName = headerName;
181    }
182
183    @Override
184    public Expression createExpression(CamelContext camelContext) {
185        if (documentType == null && documentTypeName != null) {
186            try {
187                documentType = camelContext.getClassResolver().resolveMandatoryClass(documentTypeName);
188            } catch (ClassNotFoundException e) {
189                throw ObjectHelper.wrapRuntimeCamelException(e);
190            }
191        }
192        if (resultType == null && resultTypeName != null) {
193            try {
194                resultType = camelContext.getClassResolver().resolveMandatoryClass(resultTypeName);
195            } catch (ClassNotFoundException e) {
196                throw ObjectHelper.wrapRuntimeCamelException(e);
197            }
198        }
199        resolveXPathFactory(camelContext);
200        return super.createExpression(camelContext);
201    }
202
203    @Override
204    public Predicate createPredicate(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        resolveXPathFactory(camelContext);
213        return super.createPredicate(camelContext);
214    }
215
216    @Override
217    protected void configureExpression(CamelContext camelContext, Expression expression) {
218        boolean isSaxon = getSaxon() != null && getSaxon();
219        boolean isLogNamespaces = getLogNamespaces() != null && getLogNamespaces();
220
221        if (documentType != null) {
222            setProperty(expression, "documentType", documentType);
223        }
224        if (resultType != null) {
225            setProperty(expression, "resultType", resultType);
226        }
227        if (isSaxon) {
228            ObjectHelper.cast(XPathBuilder.class, expression).enableSaxon();
229        }
230        if (xpathFactory != null) {
231            setProperty(expression, "xPathFactory", xpathFactory);
232        }
233        if (objectModel != null) {
234            setProperty(expression, "objectModelUri", objectModel);
235        }
236        if (isLogNamespaces) {
237            ObjectHelper.cast(XPathBuilder.class, expression).setLogNamespaces(true);
238        }
239        if (ObjectHelper.isNotEmpty(getHeaderName())) {
240            ObjectHelper.cast(XPathBuilder.class, expression).setHeaderName(getHeaderName());
241        }
242        // moved the super configuration to the bottom so that the namespace init picks up the newly set XPath Factory
243        super.configureExpression(camelContext, expression);
244    }
245
246    @Override
247    protected void configurePredicate(CamelContext camelContext, Predicate predicate) {
248        boolean isSaxon = getSaxon() != null && getSaxon();
249        boolean isLogNamespaces = getLogNamespaces() != null && getLogNamespaces();
250
251        if (documentType != null) {
252            setProperty(predicate, "documentType", documentType);
253        }
254        if (resultType != null) {
255            setProperty(predicate, "resultType", resultType);
256        }
257        if (isSaxon) {
258            ObjectHelper.cast(XPathBuilder.class, predicate).enableSaxon();
259        }
260        if (xpathFactory != null) {
261            setProperty(predicate, "xPathFactory", xpathFactory);
262        }
263        if (objectModel != null) {
264            setProperty(predicate, "objectModelUri", objectModel);
265        }
266        if (isLogNamespaces) {
267            ObjectHelper.cast(XPathBuilder.class, predicate).setLogNamespaces(true);
268        }
269        if (ObjectHelper.isNotEmpty(getHeaderName())) {
270            ObjectHelper.cast(XPathBuilder.class, predicate).setHeaderName(getHeaderName());
271        }
272        // moved the super configuration to the bottom so that the namespace init picks up the newly set XPath Factory
273        super.configurePredicate(camelContext, predicate);
274    }
275
276    private void resolveXPathFactory(CamelContext camelContext) {
277        // Factory and Object Model can be set simultaneously. The underlying XPathBuilder allows for setting Saxon too, as it is simply a shortcut for
278        // 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 
279        if ((ObjectHelper.isNotEmpty(factoryRef) || ObjectHelper.isNotEmpty(objectModel)) && (saxon != null)) {
280            throw new IllegalArgumentException("The saxon attribute cannot be set on the xpath element if any of the following is also set: factory, objectModel" + this);
281        }
282
283        // Validate the factory class
284        if (ObjectHelper.isNotEmpty(factoryRef)) {
285            xpathFactory = camelContext.getRegistry().lookupByNameAndType(factoryRef, XPathFactory.class);
286            if (xpathFactory == null) {
287                throw new IllegalArgumentException("The provided XPath Factory is invalid; either it cannot be resolved or it is not an XPathFactory instance");
288            }
289        }
290    }
291}