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