blob: 260eb5af3ef863c0602d24cec3f37bbae241186a [file] [log] [blame]
package net.sf.taverna.t2.activities.xpath;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import net.sf.taverna.t2.invocation.InvocationContext;
import net.sf.taverna.t2.reference.ErrorDocumentService;
import net.sf.taverna.t2.reference.ReferenceService;
import net.sf.taverna.t2.reference.T2Reference;
import net.sf.taverna.t2.workflowmodel.processor.activity.AbstractAsynchronousActivity;
import net.sf.taverna.t2.workflowmodel.processor.activity.ActivityConfigurationException;
import net.sf.taverna.t2.workflowmodel.processor.activity.AsynchronousActivityCallback;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.InvalidXPathException;
import org.dom4j.Node;
import org.dom4j.XPath;
import org.dom4j.XPathException;
import com.fasterxml.jackson.databind.JsonNode;
/**
* Enhanced XPath activity.
*
* @author Sergejs Aleksejevs
*/
public class XPathActivity extends AbstractAsynchronousActivity<JsonNode> {
public static final String URI = "http://ns.taverna.org.uk/2010/activity/xpath";
// These ports are default ones (and only ones - XPath activity will not have dynamic ports)
public static final String IN_XML = "xml_text";
public static final String OUT_TEXT = "nodelist";
public static final String OUT_XML = "nodelistAsXML";
private static final String SINGLE_VALUE_TEXT = "firstNode";
private static final String SINGLE_VALUE_XML = "firstNodeAsXML";
// Configuration bean for this activity - essentially defines a particular instance
// of the activity through the values of its parameters
private JsonNode json;
@Override
public JsonNode getConfiguration() {
return this.json;
}
@Override
public void configure(JsonNode json) throws ActivityConfigurationException {
// Check configBean is valid
if (!XPathUtils.isValid(json)) {
throw new ActivityConfigurationException("Invalid configuration of XPath activity...");
// TODO - check this
}
// Store for getConfiguration()
this.json = json;
}
protected void configurePorts() {
// ---- REMOVE OLD PORTS ----
// In case we are being reconfigured - remove existing ports first to avoid duplicates
removeInputs();
removeOutputs();
// ---- CREATE NEW INPUTS AND OUTPUTS ----
// all ports in this activity are static, so no dependency on the values in config bean
// single input port: the input XML text will be treated as String for now
addInput(IN_XML, 0, true, null, String.class);
addOutput(SINGLE_VALUE_TEXT, 0);
addOutput(SINGLE_VALUE_XML, 0);
addOutput(OUT_TEXT, 1);
addOutput(OUT_XML, 1);
}
/**
* This method executes pre-configured instance of XPath activity.
*/
@Override
public void executeAsynch(final Map<String, T2Reference> inputs,
final AsynchronousActivityCallback callback) {
// Don't execute service directly now, request to be run asynchronously
callback.requestRun(new Runnable() {
@Override
@SuppressWarnings("unchecked")
public void run() {
InvocationContext context = callback.getContext();
ReferenceService referenceService = context.getReferenceService();
// ---- RESOLVE INPUT ----
String xmlInput = (String) referenceService.renderIdentifier(inputs.get(IN_XML),
String.class, context);
// ---- DO THE ACTUAL SERVICE INVOCATION ----
List<Node> matchingNodes = new ArrayList<Node>();
// only attempt to execute XPath expression if there is some input data
if (xmlInput != null && xmlInput.length() > 0) {
// XPath configuration is taken from the config bean
try {
XPath expr = DocumentHelper.createXPath(json.get("xpathExpression").textValue());
Map<String, String> xpathNamespaceMap = new HashMap<>();
for (JsonNode namespaceMapping : json.get("xpathNamespaceMap")) {
xpathNamespaceMap.put(namespaceMapping.get("prefix").textValue(),
namespaceMapping.get("uri").textValue());
}
expr.setNamespaceURIs(xpathNamespaceMap);
Document doc = DocumentHelper.parseText(xmlInput);
matchingNodes = expr.selectNodes(doc);
} catch (InvalidXPathException e) {
callback.fail("Incorrect XPath Expression -- XPath processing library "
+ "reported the following error: " + e.getMessage(), e);
// make sure we don't call callback.receiveResult later
return;
} catch (DocumentException e) {
callback.fail("XML document was not valid -- XPath processing library "
+ "reported the following error: " + e.getMessage(), e);
// make sure we don't call callback.receiveResult later
return;
} catch (XPathException e) {
callback.fail(
"Unexpected error has occurred while executing the XPath expression. "
+ "-- XPath processing library reported the following error:\n"
+ e.getMessage(), e);
// make sure we don't call callback.receiveResult later
return;
}
}
// --- PREPARE OUTPUTS ---
List<String> outNodesText = new ArrayList<String>();
List<String> outNodesXML = new ArrayList<String>();
Object textValue = null;
Object xmlValue = null;
for (Object o : matchingNodes) {
if (o instanceof Node) {
Node n = (Node) o;
if (n.getStringValue() != null
&& n.getStringValue().length() > 0) {
outNodesText.add(n.getStringValue());
if (textValue == null)
textValue = n.getStringValue();
}
outNodesXML.add(n.asXML());
if (xmlValue == null)
xmlValue = n.asXML();
} else {
outNodesText.add(o.toString());
if (textValue == null)
textValue = o.toString();
}
}
// ---- REGISTER OUTPUTS ----
Map<String, T2Reference> outputs = new HashMap<String, T2Reference>();
if (textValue == null) {
ErrorDocumentService errorDocService = referenceService
.getErrorDocumentService();
textValue = errorDocService.registerError(
"No value produced", 0, callback.getContext());
}
if (xmlValue == null) {
ErrorDocumentService errorDocService = referenceService
.getErrorDocumentService();
xmlValue = errorDocService.registerError(
"No value produced", 0, callback.getContext());
}
T2Reference firstNodeAsText = referenceService.register(
textValue, 0, true, context);
outputs.put(SINGLE_VALUE_TEXT, firstNodeAsText);
T2Reference firstNodeAsXml = referenceService.register(
xmlValue, 0, true, context);
outputs.put(SINGLE_VALUE_XML, firstNodeAsXml);
T2Reference outNodesAsText = referenceService.register(
outNodesText, 1, true, context);
outputs.put(OUT_TEXT, outNodesAsText);
T2Reference outNodesAsXML = referenceService.register(
outNodesXML, 1, true, context);
outputs.put(OUT_XML, outNodesAsXML);
// return map of output data, with empty index array as this is
// the only and final result (this index parameter is used if
// pipelining output)
callback.receiveResult(outputs, new int[0]);
}
});
}
}