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", 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        resolveXPathFactory(camelContext);
206        return super.createPredicate(camelContext);
207    }
208
209    @Override
210    protected void configureExpression(CamelContext camelContext, Expression expression) {
211        boolean isSaxon = getSaxon() != null && getSaxon();
212        boolean isLogNamespaces = getLogNamespaces() != null && getLogNamespaces();
213
214        if (documentType != null) {
215            setProperty(expression, "documentType", documentType);
216        }
217        if (resultType != null) {
218            setProperty(expression, "resultType", resultType);
219        }
220        if (isSaxon) {
221            ObjectHelper.cast(XPathBuilder.class, expression).enableSaxon();
222        }
223        if (xpathFactory != null) {
224            setProperty(expression, "xPathFactory", xpathFactory);
225        }
226        if (objectModel != null) {
227            setProperty(expression, "objectModelUri", objectModel);
228        }
229        if (isLogNamespaces) {
230            ObjectHelper.cast(XPathBuilder.class, expression).setLogNamespaces(true);
231        }
232        if (ObjectHelper.isNotEmpty(getHeaderName())) {
233            ObjectHelper.cast(XPathBuilder.class, expression).setHeaderName(getHeaderName());
234        }
235        // moved the super configuration to the bottom so that the namespace init picks up the newly set XPath Factory
236        super.configureExpression(camelContext, expression);
237
238    }
239
240    @Override
241    protected void configurePredicate(CamelContext camelContext, Predicate predicate) {
242        boolean isSaxon = getSaxon() != null && getSaxon();
243        boolean isLogNamespaces = getLogNamespaces() != null && getLogNamespaces();
244
245        if (documentType != null) {
246            setProperty(predicate, "documentType", documentType);
247        }
248        if (resultType != null) {
249            setProperty(predicate, "resultType", resultType);
250        }
251        if (isSaxon) {
252            ObjectHelper.cast(XPathBuilder.class, predicate).enableSaxon();
253        }
254        if (xpathFactory != null) {
255            setProperty(predicate, "xPathFactory", xpathFactory);
256        }
257        if (objectModel != null) {
258            setProperty(predicate, "objectModelUri", objectModel);
259        }
260        if (isLogNamespaces) {
261            ObjectHelper.cast(XPathBuilder.class, predicate).setLogNamespaces(true);
262        }
263        if (ObjectHelper.isNotEmpty(getHeaderName())) {
264            ObjectHelper.cast(XPathBuilder.class, predicate).setHeaderName(getHeaderName());
265        }
266        // moved the super configuration to the bottom so that the namespace init picks up the newly set XPath Factory
267        super.configurePredicate(camelContext, predicate);
268    }
269
270    private void resolveXPathFactory(CamelContext camelContext) {
271        // Factory and Object Model can be set simultaneously. The underlying XPathBuilder allows for setting Saxon too, as it is simply a shortcut for
272        // 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 
273        if ((ObjectHelper.isNotEmpty(factoryRef) || ObjectHelper.isNotEmpty(objectModel)) && (saxon != null)) {
274            throw new IllegalArgumentException("The saxon attribute cannot be set on the xpath element if any of the following is also set: factory, objectModel" + this);
275        }
276
277        // Validate the factory class
278        if (ObjectHelper.isNotEmpty(factoryRef)) {
279            xpathFactory = camelContext.getRegistry().lookupByNameAndType(factoryRef, XPathFactory.class);
280            if (xpathFactory == null) {
281                throw new IllegalArgumentException("The provided XPath Factory is invalid; either it cannot be resolved or it is not an XPathFactory instance");
282            }
283        }
284    }
285}