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}