org.mule.module.xml.el.XPath3Function Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of mule-module-xml Show documentation
Show all versions of mule-module-xml Show documentation
Functionality for working with XML
The newest version!
/*
* Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com
* The software in this package is published under the terms of the CPAL v1.0
* license, a copy of which has been included with this distribution in the
* LICENSE.txt file.
*/
package org.mule.module.xml.el;
import static org.mule.util.ClassUtils.isConsumable;
import static org.mule.util.Preconditions.checkArgument;
import static org.mule.util.Preconditions.checkState;
import org.mule.RequestContext;
import org.mule.api.MuleContext;
import org.mule.api.MuleEvent;
import org.mule.api.MuleMessage;
import org.mule.api.MuleRuntimeException;
import org.mule.api.el.ExpressionLanguageContext;
import org.mule.api.el.ExpressionLanguageFunction;
import org.mule.api.registry.RegistrationException;
import org.mule.api.transformer.DataType;
import org.mule.api.transport.OutputHandler;
import org.mule.el.context.MessageContext;
import org.mule.el.mvel.MVELExpressionLanguageContext;
import org.mule.module.xml.transformer.DelayedResult;
import org.mule.module.xml.util.NamespaceManager;
import org.mule.module.xml.util.XMLUtils;
import org.mule.module.xml.xpath.SaxonXpathEvaluator;
import org.mule.module.xml.xpath.XPathEvaluator;
import org.mule.module.xml.xpath.XPathReturnType;
import org.mule.transformer.TransformerUtils;
import org.mule.transformer.types.DataTypeFactory;
import com.google.common.base.Joiner;
import com.google.common.base.Supplier;
import java.io.InputStream;
import javax.xml.stream.XMLStreamReader;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
/**
* A MEL function capable of evaluation XPath expressions by delegating into an instance
* of {@link XPathEvaluator}
*
* The function will now accept the following arguments (in order):
*
*
*
* expression (required String)
* The Xpath expression to be evaluated. Cannot be null or blank.
*
*
* input (optional Object, defaults to the message payload)
* The input data on which the expression is going to be evaluated. This is an optional argument, it defaults to the message payload if not provided.
*
*
* Output type (optional String, defaults to ?STRING?)
* When executing an XPath expression, a developer might have very different intents. Sometimes you want to retrieve actual data,
* sometimes you just want to verify if a node exists. Also, the JAXP API (JSR-206) defines the standard way for a Java application to handle XML, and therefore,
* how to execute XPath expressions. This API accounts for the different intents a developer might have and allows choosing from a list of possible output types.
* We consider this to be a really useful features in JAXP, and we also consider that many Java developers that are familiar with this API would appreciate
* that Mule accounts for this while hiding the rest of the API's complexity. That is why there's a third parameter (optional, String),
* which will allow specifying one of the following:
*
* - BOOLEAN: returns the effective boolean value of the expression, as a java.lang.Boolean. This is the same as wrapping the expression in a call of the XPath boolean() function.
* - STRING: returns the result of the expression converted to a string, as a java.lang.String. This is the same as wrapping the expression in a call of the XPath string() function.
* - NUMBER: returns the result of the expression converted to a double as a java.lang.Double. This is the same as wrapping the expression in a call of the XPath number() function.
* - NODE: returns the result the result as a node object.
* - NODESET: returns a {@link NodeList}.
*
*
*
*
*
* Input types
*
* This function supports the following input types:
*
*
* {@link Document}
* {@link Node}
* {@link InputSource}
* {@link OutputHandler}
* {@code byte[]}
* {@link InputStream}
* {@link XMLStreamReader}
* {@link DelayedResult}
*
*
*
* If the input if not of any of these types, then we'll attempt to use a registered transformer to transform the input into a DOM document or Node.
* If no such transformer can be found, then an {@link IllegalArgumentException} will be thrown.
*
* This function will verify if the input is a consumable type (streams, readers, etc). Because evaluating the expression over a consumable input
* will cause that source to be exhausted, in the cases in which the input value was the actual message payload
* (no matter if it was given explicitly or by default), it will update the output message payload with the result obtained from consuming the input.
*
* @since 3.6.0
*/
public class XPath3Function implements ExpressionLanguageFunction
{
private static final Logger LOGGER = LoggerFactory.getLogger(XPath3Function.class);
private static final XPathReturnType DEFAULT_RETURN_TYPE = XPathReturnType.STRING;
private static final DataType>[] SUPPORTED_TYPES = new DataType>[] {DataTypeFactory.create(Document.class), DataTypeFactory.create(Node.class)};
private static final String SUPPORTED_TYPES_AS_STRING = Joiner.on(',').join(SUPPORTED_TYPES);
private final MuleContext muleContext;
private XPathEvaluator xpathEvaluator;
private Supplier xpathEvaluatorSupplier = new LookupEvaluatorSupplier();
XPath3Function(MuleContext muleContext)
{
this.muleContext = muleContext;
}
@Override
public Object call(Object[] params, ExpressionLanguageContext context)
{
validateParams(params);
final MessageContext ctx = context.getVariable("message");
final String xpathExpression = getXpathExpression(params);
final XPathReturnType returnType = getReturnType(params);
final MuleEvent event = getMuleEvent(context);
final Object input = getInput(params, event);
try
{
Node node = toDOMNode(input, event);
Object result = xpathEvaluatorSupplier.get().evaluate(xpathExpression, node, returnType, event);
MuleMessage message = event.getMessage();
if (input == message.getPayload() && isConsumable(message.getPayload().getClass()))
{
ctx.setPayload(node);
}
return result;
}
catch (Exception e)
{
throw new MuleRuntimeException(e);
}
}
private MuleEvent getMuleEvent(ExpressionLanguageContext context)
{
MuleEvent event = context.getVariable(MVELExpressionLanguageContext.MULE_EVENT_INTERNAL_VARIABLE);
if (event == null)
{
event = RequestContext.getEvent();
}
checkState(event != null, "Could not obtain MuleEvent");
return event;
}
private Node toDOMNode(Object input, MuleEvent event) throws Exception
{
if (input == null)
{
throw new IllegalArgumentException("Can't evaluate an XPath expression over a null input");
}
Node node = XMLUtils.toDOMNode(input, event);
if (node == null)
{
node = (Node) TransformerUtils.transformToAny(input, muleContext, SUPPORTED_TYPES);
}
if (node == null)
{
throw new IllegalArgumentException(
String.format("Could not transform input of type '%s' to a supported one. Supported types are '%s'", input.getClass().getName(), SUPPORTED_TYPES_AS_STRING));
}
return node;
}
private Object getInput(Object[] params, MuleEvent event)
{
if (params.length >= 2)
{
return params[1];
}
return event.getMessage().getPayload();
}
private void validateParams(Object[] params)
{
checkArgument(params.length > 0 && params.length <= 3, String.format("xpath3() function accepts up to 3 arguments, but %s were provided instead", params.length));
}
private String getXpathExpression(Object[] params)
{
checkArgument(params[0] != null, "XPath expression cannot be null");
checkArgument(params[0] instanceof String, "XPath expression must be a String");
String expression = (String) params[0];
checkArgument(!StringUtils.isBlank(expression), "XPath expression cannot be blank");
return expression;
}
private XPathReturnType getReturnType(Object[] params)
{
if (params.length >= 3)
{
Object returnType = params[2];
if (returnType == null)
{
return DEFAULT_RETURN_TYPE;
}
checkArgument(returnType instanceof String, "returnType argument must be of type String");
try
{
return XPathReturnType.valueOf((String) returnType);
}
catch (Exception e)
{
throw new IllegalArgumentException(returnType + " is not a valid XPath return type", e);
}
}
return DEFAULT_RETURN_TYPE;
}
private class LookupEvaluatorSupplier implements Supplier
{
@Override
public synchronized XPathEvaluator get()
{
if (xpathEvaluator == null)
{
xpathEvaluator = new SaxonXpathEvaluator();
try
{
NamespaceManager namespaceManager = muleContext.getRegistry().lookupObject(NamespaceManager.class);
if (namespaceManager != null)
{
xpathEvaluator.registerNamespaces(namespaceManager);
}
}
catch (RegistrationException e)
{
LOGGER.debug("No namespace manager found. Will not register any namespaces");
}
xpathEvaluatorSupplier = new FastEvaluatorSupplier();
}
return xpathEvaluator;
}
}
private class FastEvaluatorSupplier implements Supplier
{
@Override
public XPathEvaluator get()
{
return xpathEvaluator;
}
}
}