package com.yahoo.dtf.range; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import javax.xml.parsers.DocumentBuilder; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import org.apache.commons.jxpath.JXPathContext; import org.apache.xerces.dom.AttrImpl; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.xml.sax.SAXException; import com.yahoo.dtf.exception.RangeException; import com.yahoo.dtf.xml.JXPathHelper; import com.yahoo.dtf.xml.XMLTransformerCache; import com.yahoo.dtf.xml.XMLUtil; public class XMLDataRange extends Range { private String expression = null; private String xpath = null; private long nextcalled = 0; private String xml = null; private List<Node> _nodes = null; private ArrayList<Node> _copy = null; public static boolean matches(String expression) throws RangeException { if ( expression.startsWith("xpath(") ) { if ( expression.endsWith(")") ) { return true; } throw new RangeException("XPath range is invalid [" + expression + "]"); } return false; } /* * Used when transferring ranges between runner and agents. */ public XMLDataRange() { } public XMLDataRange(String expression) throws RangeException { this.expression = expression; init(); } private void init() throws RangeException { String arguments = expression.substring("xpath".length()); String[] args = arguments.split(",/"); xml = args[0].substring(1); if ( !args[1].endsWith(")") ) { throw new RangeException("XPath expression is invalid [" + expression + "]"); } ByteArrayInputStream bais = new ByteArrayInputStream(xml.getBytes()); try { DocumentBuilder db = XMLUtil.checkOut(); try { Document doc = db.parse(bais); JXPathContext ctx = JXPathContext.newContext(doc); String aux = args[1].substring(0,args[1].length()-1); if ( aux.contains(",[") ) { String[] parts = aux.split(",\\["); xpath = "/" + parts[0]; JXPathHelper.registerNamespaces(ctx, parts[1].replace("]", "")); } else { xpath = "/" + aux; } _nodes = ctx.selectNodes(xpath); } finally { XMLUtil.checkIn(db); } } catch (SAXException e) { throw new RangeException("Unable to parse xml.",e); } catch (IOException e) { throw new RangeException("Unable to parse xml.",e); } _copy = new ArrayList<Node>(); for (int i = 0; i < _nodes.size(); i++) { _copy.add(_nodes.get(i)); } } public boolean hasMoreElements() { return (_copy.size() != 0); } public String nextElement() throws RangeException { nextcalled++; Node item = _copy.remove(0); if (item instanceof AttrImpl) { return item.getNodeValue(); } else { ByteArrayOutputStream baos = new ByteArrayOutputStream(); StreamResult result = new StreamResult(baos); DOMSource source = new DOMSource(item); Transformer transformer = XMLTransformerCache.checkOut(); try { transformer.transform(source, result); } catch (TransformerException e) { throw new RangeException("Unable to handle XML.",e); } finally { XMLTransformerCache.checkIn(transformer); } return new String(baos.toByteArray()); } } public void reset() throws RangeException { init(); nextcalled = 0; } public int size() { return _copy.size(); } @Override public void restoreState(DataInputStream dis) throws RangeException { try { expression = dis.readUTF(); init(); long calls = dis.readLong(); // move this range into the same position. for(long i = 0; i < calls ; i++) nextElement(); } catch (IOException e) { throw new RangeException("Error suspending state.",e); } } @Override public void suspendState(DataOutputStream dos) throws RangeException { try { dos.writeUTF(expression); dos.writeLong(nextcalled); } catch (IOException e) { throw new RangeException("Error suspending state.",e); } } }