Author: davsclaus
Date: Sat Dec 1 14:50:37 2012
New Revision: 1416006
URL: http://svn.apache.org/viewvc?rev=1416006&view=rev
Log:
CAMEL-5838: XPathBuilder thread safe. A better solution thanks to Babak for
telling about that.
Modified:
camel/trunk/camel-core/src/main/java/org/apache/camel/builder/xml/XPathBuilder.java
camel/trunk/camel-core/src/test/java/org/apache/camel/builder/xml/DefaultNamespaceContextTest.java
camel/trunk/camel-core/src/test/java/org/apache/camel/builder/xml/XPathWithNamespacesFromDomTest.java
Modified:
camel/trunk/camel-core/src/main/java/org/apache/camel/builder/xml/XPathBuilder.java
URL:
http://svn.apache.org/viewvc/camel/trunk/camel-core/src/main/java/org/apache/camel/builder/xml/XPathBuilder.java?rev=1416006&r1=1416005&r2=1416006&view=diff
==============================================================================
---
camel/trunk/camel-core/src/main/java/org/apache/camel/builder/xml/XPathBuilder.java
(original)
+++
camel/trunk/camel-core/src/main/java/org/apache/camel/builder/xml/XPathBuilder.java
Sat Dec 1 14:50:37 2012
@@ -26,6 +26,7 @@ import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Queue;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import javax.xml.namespace.QName;
import javax.xml.transform.dom.DOMSource;
@@ -77,7 +78,10 @@ import static org.apache.camel.builder.x
* sure that one XPath object is not used from more than one thread at any
given time, and while the evaluate method
* is invoked, applications may not recursively call the evaluate method.
* <p/>
- * This implementation is thread safe by using thread locals and pooling to
allow concurrency
+ * This implementation is thread safe by using thread locals and pooling to
allow concurrency.
+ * <p/>
+ * <b>Important:</b> After configuring the {@link XPathBuilder} its adviced to
invoke {@link #start()}
+ * to prepare the builder before using; though the builder will auto-start on
first use.
*
* @see XPathConstants#NODESET
*/
@@ -86,37 +90,37 @@ public class XPathBuilder implements Exp
private static final String SAXON_OBJECT_MODEL_URI =
"http://saxon.sf.net/jaxp/xpath/om";
private static final String OBTAIN_ALL_NS_XPATH = "//*/namespace::*";
- private static XPathFactory defaultXPathFactory;
+ private static volatile XPathFactory defaultXPathFactory;
private final Queue<XPathExpression> pool = new
ConcurrentLinkedQueue<XPathExpression>();
private final Queue<XPathExpression> poolLogNamespaces = new
ConcurrentLinkedQueue<XPathExpression>();
private final String text;
private final ThreadLocal<Exchange> exchange = new ThreadLocal<Exchange>();
private final MessageVariableResolver variableResolver = new
MessageVariableResolver(exchange);
- private XPathFactory xpathFactory;
- private Class<?> documentType = Document.class;
+ private final Map<String, String> namespaces = new
ConcurrentHashMap<String, String>();
+ private volatile XPathFactory xpathFactory;
+ private volatile Class<?> documentType = Document.class;
// For some reason the default expression of "a/b" on a document such as
// <a><b>1</b><b>2</b></a>
// will evaluate as just "1" by default which is bizarre. So by default
// let's assume XPath expressions result in nodesets.
- private Class<?> resultType;
- private QName resultQName = XPathConstants.NODESET;
- private String objectModelUri;
- private DefaultNamespaceContext namespaceContext;
- private boolean logNamespaces;
- private XPathFunctionResolver functionResolver;
- private XPathFunction bodyFunction;
- private XPathFunction headerFunction;
- private XPathFunction outBodyFunction;
- private XPathFunction outHeaderFunction;
- private XPathFunction propertiesFunction;
- private XPathFunction simpleFunction;
-
+ private volatile Class<?> resultType;
+ private volatile QName resultQName = XPathConstants.NODESET;
+ private volatile String objectModelUri;
+ private volatile DefaultNamespaceContext namespaceContext;
+ private volatile boolean logNamespaces;
+ private volatile XPathFunctionResolver functionResolver;
+ private volatile XPathFunction bodyFunction;
+ private volatile XPathFunction headerFunction;
+ private volatile XPathFunction outBodyFunction;
+ private volatile XPathFunction outHeaderFunction;
+ private volatile XPathFunction propertiesFunction;
+ private volatile XPathFunction simpleFunction;
/**
* The name of the header we want to apply the XPath expression to, which
when set will cause
* the xpath to be evaluated on the required header, otherwise it will be
applied to the body
*/
- private String headerName;
+ private volatile String headerName;
/**
* @param text The XPath expression
@@ -306,7 +310,7 @@ public class XPathBuilder implements Exp
* @return the current builder
*/
public XPathBuilder objectModel(String uri) {
- // TODO: Careful! Setting the Object Model URI this way will set the
*Default* XPath Factory, which since is a static field,
+ // Careful! Setting the Object Model URI this way will set the
*Default* XPath Factory, which since is a static field,
// will set the XPath Factory system-wide. Decide what to do, as
changing this behaviour can break compatibility. Provided the setObjectModel
which changes
// this instance's XPath Factory rather than the static field
this.objectModelUri = uri;
@@ -345,7 +349,7 @@ public class XPathBuilder implements Exp
* @return the current builder
*/
public XPathBuilder namespace(String prefix, String uri) {
- getNamespaceContext().add(prefix, uri);
+ namespaces.put(prefix, uri);
return this;
}
@@ -426,21 +430,17 @@ public class XPathBuilder implements Exp
// Properties
//
-------------------------------------------------------------------------
- public XPathFactory getXPathFactory() throws
XPathFactoryConfigurationException {
- if (xpathFactory != null) {
- return xpathFactory;
- }
-
- if (objectModelUri != null) {
- xpathFactory = XPathFactory.newInstance(objectModelUri);
- LOG.info("Using objectModelUri " + objectModelUri + " when created
XPathFactory {}", xpathFactory);
- return xpathFactory;
- }
- if (defaultXPathFactory == null) {
- initDefaultXPathFactory();
- }
- return defaultXPathFactory;
+ /**
+ * Gets the xpath factory, can be <tt>null</tt> if no custom factory has
been assigned.
+ * <p/>
+ * A default factory will be assigned (if no custom assigned) when either
starting this builder
+ * or on first evaluation.
+ *
+ * @return the factory, or <tt>null</tt> if this builder has not been
started/used before.
+ */
+ public XPathFactory getXPathFactory() {
+ return xpathFactory;
}
public void setXPathFactory(XPathFactory xpathFactory) {
@@ -474,17 +474,16 @@ public class XPathBuilder implements Exp
public void setHeaderName(String headerName) {
this.headerName = headerName;
}
-
+
+ /**
+ * Gets the namespace context, can be <tt>null</tt> if no custom context
has been assigned.
+ * <p/>
+ * A default context will be assigned (if no custom assigned) when either
starting this builder
+ * or on first evaluation.
+ *
+ * @return the context, or <tt>null</tt> if this builder has not been
started/used before.
+ */
public DefaultNamespaceContext getNamespaceContext() {
- if (namespaceContext == null) {
- try {
- DefaultNamespaceContext defaultNamespaceContext = new
DefaultNamespaceContext(getXPathFactory());
- populateDefaultNamespaces(defaultNamespaceContext);
- namespaceContext = defaultNamespaceContext;
- } catch (XPathFactoryConfigurationException e) {
- throw new RuntimeExpressionException(e);
- }
- }
return namespaceContext;
}
@@ -501,142 +500,198 @@ public class XPathBuilder implements Exp
}
public void setNamespaces(Map<String, String> namespaces) {
- getNamespaceContext().setNamespaces(namespaces);
+ this.namespaces.clear();
+ this.namespaces.putAll(namespaces);
}
+ /**
+ * Gets the {@link XPathFunction} for getting the input message body.
+ * <p/>
+ * A default function will be assigned (if no custom assigned) when either
starting this builder
+ * or on first evaluation.
+ *
+ * @return the function, or <tt>null</tt> if this builder has not been
started/used before.
+ */
public XPathFunction getBodyFunction() {
- if (bodyFunction == null) {
- bodyFunction = new XPathFunction() {
- @SuppressWarnings("rawtypes")
- public Object evaluate(List list) throws
XPathFunctionException {
- if (exchange == null) {
- return null;
- }
- return exchange.get().getIn().getBody();
- }
- };
- }
return bodyFunction;
}
+ private XPathFunction createBodyFunction() {
+ return new XPathFunction() {
+ @SuppressWarnings("rawtypes")
+ public Object evaluate(List list) throws XPathFunctionException {
+ if (exchange == null) {
+ return null;
+ }
+ return exchange.get().getIn().getBody();
+ }
+ };
+ }
+
public void setBodyFunction(XPathFunction bodyFunction) {
this.bodyFunction = bodyFunction;
}
+ /**
+ * Gets the {@link XPathFunction} for getting the input message header.
+ * <p/>
+ * A default function will be assigned (if no custom assigned) when either
starting this builder
+ * or on first evaluation.
+ *
+ * @return the function, or <tt>null</tt> if this builder has not been
started/used before.
+ */
public XPathFunction getHeaderFunction() {
- if (headerFunction == null) {
- headerFunction = new XPathFunction() {
- @SuppressWarnings("rawtypes")
- public Object evaluate(List list) throws
XPathFunctionException {
- if (exchange != null && !list.isEmpty()) {
- Object value = list.get(0);
- if (value != null) {
- String text =
exchange.get().getContext().getTypeConverter().convertTo(String.class, value);
- return exchange.get().getIn().getHeader(text);
- }
+ return headerFunction;
+ }
+
+ private XPathFunction createHeaderFunction() {
+ return new XPathFunction() {
+ @SuppressWarnings("rawtypes")
+ public Object evaluate(List list) throws XPathFunctionException {
+ if (exchange != null && !list.isEmpty()) {
+ Object value = list.get(0);
+ if (value != null) {
+ String text =
exchange.get().getContext().getTypeConverter().convertTo(String.class, value);
+ return exchange.get().getIn().getHeader(text);
}
- return null;
}
- };
- }
- return headerFunction;
+ return null;
+ }
+ };
}
public void setHeaderFunction(XPathFunction headerFunction) {
this.headerFunction = headerFunction;
}
+ /**
+ * Gets the {@link XPathFunction} for getting the output message body.
+ * <p/>
+ * A default function will be assigned (if no custom assigned) when either
starting this builder
+ * or on first evaluation.
+ *
+ * @return the function, or <tt>null</tt> if this builder has not been
started/used before.
+ */
public XPathFunction getOutBodyFunction() {
- if (outBodyFunction == null) {
- outBodyFunction = new XPathFunction() {
- @SuppressWarnings("rawtypes")
- public Object evaluate(List list) throws
XPathFunctionException {
- if (exchange.get() != null && exchange.get().hasOut()) {
- return exchange.get().getOut().getBody();
- }
- return null;
- }
- };
- }
return outBodyFunction;
}
+ private XPathFunction createOutBodyFunction() {
+ return new XPathFunction() {
+ @SuppressWarnings("rawtypes")
+ public Object evaluate(List list) throws XPathFunctionException {
+ if (exchange.get() != null && exchange.get().hasOut()) {
+ return exchange.get().getOut().getBody();
+ }
+ return null;
+ }
+ };
+ }
+
public void setOutBodyFunction(XPathFunction outBodyFunction) {
this.outBodyFunction = outBodyFunction;
}
+ /**
+ * Gets the {@link XPathFunction} for getting the output message header.
+ * <p/>
+ * A default function will be assigned (if no custom assigned) when either
starting this builder
+ * or on first evaluation.
+ *
+ * @return the function, or <tt>null</tt> if this builder has not been
started/used before.
+ */
public XPathFunction getOutHeaderFunction() {
- if (outHeaderFunction == null) {
- outHeaderFunction = new XPathFunction() {
- @SuppressWarnings("rawtypes")
- public Object evaluate(List list) throws
XPathFunctionException {
- if (exchange.get() != null && !list.isEmpty()) {
- Object value = list.get(0);
- if (value != null) {
- String text =
exchange.get().getContext().getTypeConverter().convertTo(String.class, value);
- return exchange.get().getOut().getHeader(text);
- }
+ return outHeaderFunction;
+ }
+
+ private XPathFunction createOutHeaderFunction() {
+ return new XPathFunction() {
+ @SuppressWarnings("rawtypes")
+ public Object evaluate(List list) throws XPathFunctionException {
+ if (exchange.get() != null && !list.isEmpty()) {
+ Object value = list.get(0);
+ if (value != null) {
+ String text =
exchange.get().getContext().getTypeConverter().convertTo(String.class, value);
+ return exchange.get().getOut().getHeader(text);
}
- return null;
}
- };
- }
- return outHeaderFunction;
+ return null;
+ }
+ };
}
public void setOutHeaderFunction(XPathFunction outHeaderFunction) {
this.outHeaderFunction = outHeaderFunction;
}
+ /**
+ * Gets the {@link XPathFunction} for getting the exchange properties.
+ * <p/>
+ * A default function will be assigned (if no custom assigned) when either
starting this builder
+ * or on first evaluation.
+ *
+ * @return the function, or <tt>null</tt> if this builder has not been
started/used before.
+ */
public XPathFunction getPropertiesFunction() {
- if (propertiesFunction == null) {
- propertiesFunction = new XPathFunction() {
- @SuppressWarnings("rawtypes")
- public Object evaluate(List list) throws
XPathFunctionException {
- if (exchange != null && !list.isEmpty()) {
- Object value = list.get(0);
- if (value != null) {
- String text =
exchange.get().getContext().getTypeConverter().convertTo(String.class, value);
- try {
- // use the property placeholder resolver to
lookup the property for us
- Object answer =
exchange.get().getContext().resolvePropertyPlaceholders("{{" + text + "}}");
- return answer;
- } catch (Exception e) {
- throw new XPathFunctionException(e);
- }
+ return propertiesFunction;
+ }
+
+ private XPathFunction createPropertiesFunction() {
+ return new XPathFunction() {
+ @SuppressWarnings("rawtypes")
+ public Object evaluate(List list) throws XPathFunctionException {
+ if (exchange != null && !list.isEmpty()) {
+ Object value = list.get(0);
+ if (value != null) {
+ String text =
exchange.get().getContext().getTypeConverter().convertTo(String.class, value);
+ try {
+ // use the property placeholder resolver to lookup
the property for us
+ Object answer =
exchange.get().getContext().resolvePropertyPlaceholders("{{" + text + "}}");
+ return answer;
+ } catch (Exception e) {
+ throw new XPathFunctionException(e);
}
}
- return null;
}
- };
- }
- return propertiesFunction;
+ return null;
+ }
+ };
}
public void setPropertiesFunction(XPathFunction propertiesFunction) {
this.propertiesFunction = propertiesFunction;
}
+ /**
+ * Gets the {@link XPathFunction} for executing <a
href="http://camel.apache.org/simple">simple</a>
+ * language as xpath function.
+ * <p/>
+ * A default function will be assigned (if no custom assigned) when either
starting this builder
+ * or on first evaluation.
+ *
+ * @return the function, or <tt>null</tt> if this builder has not been
started/used before.
+ */
public XPathFunction getSimpleFunction() {
- if (simpleFunction == null) {
- simpleFunction = new XPathFunction() {
- @SuppressWarnings("rawtypes")
- public Object evaluate(List list) throws
XPathFunctionException {
- if (exchange != null && !list.isEmpty()) {
- Object value = list.get(0);
- if (value != null) {
- String text =
exchange.get().getContext().getTypeConverter().convertTo(String.class, value);
- Language simple =
exchange.get().getContext().resolveLanguage("simple");
- Expression exp = simple.createExpression(text);
- Object answer = exp.evaluate(exchange.get(),
Object.class);
- return answer;
- }
+ return simpleFunction;
+ }
+
+ private XPathFunction createSimpleFunction() {
+ return new XPathFunction() {
+ @SuppressWarnings("rawtypes")
+ public Object evaluate(List list) throws XPathFunctionException {
+ if (exchange != null && !list.isEmpty()) {
+ Object value = list.get(0);
+ if (value != null) {
+ String text =
exchange.get().getContext().getTypeConverter().convertTo(String.class, value);
+ Language simple =
exchange.get().getContext().resolveLanguage("simple");
+ Expression exp = simple.createExpression(text);
+ Object answer = exp.evaluate(exchange.get(),
Object.class);
+ return answer;
}
- return null;
}
- };
- }
- return simpleFunction;
+ return null;
+ }
+ };
}
public void setSimpleFunction(XPathFunction simpleFunction) {
@@ -868,7 +923,20 @@ public class XPathBuilder implements Exp
return answer;
}
+ /**
+ * Creates a new xpath expression as there we no available in the pool.
+ * <p/>
+ * This implementation must be synchronized to ensure thread safety, as
this XPathBuilder instance may not have been
+ * started prior to being used.
+ */
protected synchronized XPathExpression createXPathExpression() throws
XPathExpressionException, XPathFactoryConfigurationException {
+ // ensure we are started
+ try {
+ start();
+ } catch (Exception e) {
+ throw new RuntimeExpressionException("Error starting
XPathBuilder", e);
+ }
+
// XPathFactory is not thread safe
XPath xPath = getXPathFactory().newXPath();
@@ -894,6 +962,12 @@ public class XPathBuilder implements Exp
return xPath.compile(OBTAIN_ALL_NS_XPATH);
}
+ protected DefaultNamespaceContext createNamespaceContext(XPathFactory
factory) {
+ DefaultNamespaceContext context = new DefaultNamespaceContext(factory);
+ populateDefaultNamespaces(context);
+ return context;
+ }
+
/**
* Populate a number of standard prefixes if they are not already there
*/
@@ -1083,16 +1157,34 @@ public class XPathBuilder implements Exp
public void start() throws Exception {
if (xpathFactory == null) {
- initDefaultXPathFactory();
+ xpathFactory = createXPathFactory();
+ }
+ if (namespaceContext == null) {
+ namespaceContext = createNamespaceContext(xpathFactory);
+ }
+ for (Map.Entry<String, String> entry : namespaces.entrySet()) {
+ namespaceContext.add(entry.getKey(), entry.getValue());
}
- // force lazy creating default functions
- getBodyFunction();
- getHeaderFunction();
- getOutBodyFunction();
- getOutHeaderFunction();
- getPropertiesFunction();
- getSimpleFunction();
+ // create default functions if no custom assigned
+ if (bodyFunction == null) {
+ bodyFunction = createBodyFunction();
+ }
+ if (headerFunction == null) {
+ headerFunction = createHeaderFunction();
+ }
+ if (outBodyFunction == null) {
+ outBodyFunction = createOutBodyFunction();
+ }
+ if (outHeaderFunction == null) {
+ outHeaderFunction = createOutHeaderFunction();
+ }
+ if (propertiesFunction == null) {
+ propertiesFunction = createPropertiesFunction();
+ }
+ if (simpleFunction == null) {
+ simpleFunction = createSimpleFunction();
+ }
}
public void stop() throws Exception {
@@ -1100,7 +1192,20 @@ public class XPathBuilder implements Exp
poolLogNamespaces.clear();
}
- protected synchronized void initDefaultXPathFactory() throws
XPathFactoryConfigurationException {
+ protected synchronized XPathFactory createXPathFactory() throws
XPathFactoryConfigurationException {
+ if (objectModelUri != null) {
+ xpathFactory = XPathFactory.newInstance(objectModelUri);
+ LOG.info("Using objectModelUri " + objectModelUri + " when created
XPathFactory {}", xpathFactory);
+ return xpathFactory;
+ }
+
+ if (defaultXPathFactory == null) {
+ initDefaultXPathFactory();
+ }
+ return defaultXPathFactory;
+ }
+
+ protected void initDefaultXPathFactory() throws
XPathFactoryConfigurationException {
if (defaultXPathFactory == null) {
if (objectModelUri != null) {
defaultXPathFactory = XPathFactory.newInstance(objectModelUri);
Modified:
camel/trunk/camel-core/src/test/java/org/apache/camel/builder/xml/DefaultNamespaceContextTest.java
URL:
http://svn.apache.org/viewvc/camel/trunk/camel-core/src/test/java/org/apache/camel/builder/xml/DefaultNamespaceContextTest.java?rev=1416006&r1=1416005&r2=1416006&view=diff
==============================================================================
---
camel/trunk/camel-core/src/test/java/org/apache/camel/builder/xml/DefaultNamespaceContextTest.java
(original)
+++
camel/trunk/camel-core/src/test/java/org/apache/camel/builder/xml/DefaultNamespaceContextTest.java
Sat Dec 1 14:50:37 2012
@@ -29,6 +29,7 @@ public class DefaultNamespaceContextTest
public void testDefaultNamespaceContextEmpty() throws Exception {
XPathBuilder builder = XPathBuilder.xpath("/foo");
+ builder.start();
DefaultNamespaceContext context = builder.getNamespaceContext();
assertNotNull(context);
@@ -44,6 +45,7 @@ public class DefaultNamespaceContextTest
public void testDefaultNamespaceContextPre() throws Exception {
XPathBuilder builder = XPathBuilder.xpath("/foo").namespace("pre",
"http://acme/cheese");
+ builder.start();
DefaultNamespaceContext context = builder.getNamespaceContext();
assertNotNull(context);
@@ -60,6 +62,7 @@ public class DefaultNamespaceContextTest
public void testDefaultNamespaceContextDualNamespaces() throws Exception {
XPathBuilder builder = XPathBuilder.xpath("/foo").namespace("pre",
"http://acme/cheese").namespace("bar", "http://acme/bar");
+ builder.start();
DefaultNamespaceContext context = builder.getNamespaceContext();
assertNotNull(context);
@@ -84,6 +87,7 @@ public class DefaultNamespaceContextTest
public void testDefaultNamespaceContextParent() throws Exception {
XPathBuilder builder = XPathBuilder.xpath("/foo");
+ builder.start();
DefaultNamespaceContext context = builder.getNamespaceContext();
assertNotNull(context);
Modified:
camel/trunk/camel-core/src/test/java/org/apache/camel/builder/xml/XPathWithNamespacesFromDomTest.java
URL:
http://svn.apache.org/viewvc/camel/trunk/camel-core/src/test/java/org/apache/camel/builder/xml/XPathWithNamespacesFromDomTest.java?rev=1416006&r1=1416005&r2=1416006&view=diff
==============================================================================
---
camel/trunk/camel-core/src/test/java/org/apache/camel/builder/xml/XPathWithNamespacesFromDomTest.java
(original)
+++
camel/trunk/camel-core/src/test/java/org/apache/camel/builder/xml/XPathWithNamespacesFromDomTest.java
Sat Dec 1 14:50:37 2012
@@ -35,6 +35,7 @@ public class XPathWithNamespacesFromDomT
XPathBuilder builder = XPathBuilder.xpath("//y:foo[@id='z']");
Namespaces ns = new Namespaces(element);
ns.configure(builder);
+ builder.start();
DefaultNamespaceContext namespaceContext =
builder.getNamespaceContext();
assertEquals("y namespace", "n3",
namespaceContext.getNamespaceURI("y"));