/*
* $Id: JXPathExtractor.java 19191 2010-08-25 21:05:23Z tcarlson $
* --------------------------------------------------------------------------------------
* 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.transformer;
import org.mule.api.MuleContext;
import org.mule.api.expression.ExpressionRuntimeException;
import org.mule.api.lifecycle.InitialisationException;
import org.mule.api.registry.RegistrationException;
import org.mule.api.transformer.TransformerException;
import org.mule.config.i18n.CoreMessages;
import org.mule.module.xml.util.NamespaceManager;
import org.mule.transformer.AbstractTransformer;
import org.mule.util.StringUtils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.jxpath.JXPathContext;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Node;
import org.dom4j.XPath;
/**
* The JXPathExtractor is a simple transformer that evaluates an xpath expression
* against the given bean and that returns the result. <p/> By default, a single
* result will be returned. If multiple values are expected, set the
* {@link #singleResult} property to <code>false</code>. In this case a
* {@link List} of values will be returned. Note the property is currently ignored
* for non-String/XML payloads.
*/
public class JXPathExtractor extends AbstractTransformer
{
public static final String OUTPUT_TYPE_NODE = "NODE";
public static final String OUTPUT_TYPE_XML = "XML";
public static final String OUTPUT_TYPE_VALUE = "VALUE";
private volatile String expression;
private volatile String outputType;
private volatile Map namespaces;
private volatile boolean singleResult = true;
private NamespaceManager namespaceManager;
public void setMuleContext(MuleContext context)
{
this.muleContext = context;
try
{
namespaceManager = muleContext.getRegistry().lookupObject(NamespaceManager.class);
}
catch (RegistrationException e)
{
throw new ExpressionRuntimeException(CoreMessages.failedToLoad("NamespaceManager"), e);
}
}
/**
* Template method where deriving classes can do any initialisation after the
* properties have been set on this transformer
*
* @throws org.mule.api.lifecycle.InitialisationException
*
*/
@Override
public void initialise() throws InitialisationException
{
super.initialise();
if (namespaceManager != null)
{
if (namespaces == null)
{
namespaces = new HashMap(namespaceManager.getNamespaces());
}
else
{
namespaces.putAll(namespaceManager.getNamespaces());
}
}
}
/**
* Evaluate the expression in the context of the given object and returns the
* result. If the given object is a string, it assumes it is an valid xml and
* parses it before evaluating the xpath expression.
*/
public Object doTransform(Object src, String encoding) throws TransformerException
{
try
{
Object result = null;
if (src instanceof String)
{
Document doc = DocumentHelper.parseText((String) src);
XPath xpath = doc.createXPath(expression);
if (namespaces != null)
{
xpath.setNamespaceURIs(namespaces);
}
// This is the way we always did it before, so keep doing it that way
// as xpath.evaluate() will return non-string results (like Doubles)
// for some scenarios.
if (outputType == null && singleResult)
{
return xpath.valueOf(doc);
}
// TODO handle non-list cases, see
//http://www.dom4j.org/apidocs/org/dom4j/XPath.html#evaluate(java.lang.Object)
Object obj = xpath.evaluate(doc);
if (obj instanceof List)
{
for (int i = 0; i < ((List) obj).size(); i++)
{
final Node node = (Node) ((List) obj).get(i);
result = add(result, node);
if (singleResult)
{
break;
}
}
}
else
{
result = add(result, obj);
}
}
else
{
JXPathContext context = JXPathContext.newContext(src);
result = context.getValue(expression);
}
return result;
}
catch (Exception e)
{
throw new TransformerException(this, e);
}
}
private Object add(Object result, Object value)
{
Object formattedResult = getResult(value);
if (singleResult)
{
return formattedResult;
}
else
{
if (result == null)
{
result = new ArrayList();
}
((List) result).add(formattedResult);
}
return result;
}
private Object getResult(Object value)
{
Object result = null;
if (StringUtils.contains(OUTPUT_TYPE_VALUE, outputType) || outputType == null)
{
if (value instanceof Node)
{
result = ((Node) value).getText();
}
else
{
// this maintains backward compat with previous 2.1.x versions.
result = value.toString();
}
}
else if (StringUtils.contains(OUTPUT_TYPE_XML, outputType))
{
if (value instanceof Node)
{
result = ((Node) value).asXML();
}
else
{
throw new IllegalStateException("XPath expression output must be a Node to output as XML. Expression type was: " + value.getClass());
}
}
else if (StringUtils.contains(OUTPUT_TYPE_NODE, outputType))
{
result = value;
}
return result;
}
/**
* @return Returns the expression.
*/
public String getExpression()
{
return expression;
}
/**
* @param expression The expression to set.
*/
public void setExpression(String expression)
{
this.expression = expression;
}
/**
* Should a single value be returned.
*
* @return value
*/
public boolean isSingleResult()
{
return singleResult;
}
/**
* If multiple results are expected from the {@link #expression} evaluation, set
* this to false.
*
* @param singleResult flag
*/
public void setSingleResult(boolean singleResult)
{
this.singleResult = singleResult;
}
public String getOutputType()
{
return outputType;
}
public void setOutputType(String outputEncoding)
{
this.outputType = outputEncoding;
}
public Map getNamespaces()
{
return namespaces;
}
public void setNamespaces(Map namespaceURIs)
{
this.namespaces = namespaceURIs;
}
}