| 1 | /** |
| 2 | * BSD-style license; for more info see http://xradar.sourceforge.net/license.html |
| 3 | */ |
| 4 | package org.sourceforge.xradar.results; |
| 5 | |
| 6 | import java.io.IOException; |
| 7 | import java.io.InputStream; |
| 8 | import java.util.ArrayList; |
| 9 | import java.util.List; |
| 10 | import java.util.logging.Logger; |
| 11 | |
| 12 | import javax.xml.parsers.DocumentBuilder; |
| 13 | import javax.xml.parsers.DocumentBuilderFactory; |
| 14 | import javax.xml.parsers.ParserConfigurationException; |
| 15 | import javax.xml.xpath.XPath; |
| 16 | import javax.xml.xpath.XPathConstants; |
| 17 | import javax.xml.xpath.XPathExpression; |
| 18 | import javax.xml.xpath.XPathExpressionException; |
| 19 | import javax.xml.xpath.XPathFactory; |
| 20 | |
| 21 | import org.w3c.dom.Document; |
| 22 | import org.w3c.dom.Node; |
| 23 | import org.w3c.dom.NodeList; |
| 24 | import org.xml.sax.SAXException; |
| 25 | |
| 26 | /** |
| 27 | * <p>Service to execute XPath query on the provided input stream.</p> |
| 28 | * |
| 29 | * @author Romain PELISSE, <belaran@gmail.com> |
| 30 | */ |
| 31 | public class XPathQueryAnalyser implements Analyser { |
| 32 | |
| 33 | private final static Logger logger = Logger.getLogger(XPathQueryAnalyser.class.getSimpleName()); |
| 34 | |
| 35 | private DocumentBuilder builder; |
| 36 | private XPath xpath; |
| 37 | private InputStream inputStream; |
| 38 | private Document doc; |
| 39 | |
| 40 | |
| 41 | /** |
| 42 | * @return the inputstream |
| 43 | */ |
| 44 | public InputStream getInputStream() { |
| 45 | return inputStream; |
| 46 | } |
| 47 | |
| 48 | /** |
| 49 | * |
| 50 | * @param inputStream |
| 51 | */ |
| 52 | public void setInputStream(InputStream inputStream) { |
| 53 | this.inputStream = inputStream; |
| 54 | this.doc = null; // any new inputstream invalids the existing doc, if any. |
| 55 | } |
| 56 | |
| 57 | public DocumentBuilder getBuilder() { |
| 58 | return builder; |
| 59 | } |
| 60 | |
| 61 | |
| 62 | public void setBuilder(DocumentBuilder builder) { |
| 63 | this.builder = builder; |
| 64 | } |
| 65 | |
| 66 | |
| 67 | /** |
| 68 | * Provided for commodity, not use if app's client wants to use an other implementation |
| 69 | * of document builder. |
| 70 | * |
| 71 | * @return a DocumentBuilder instance or null if failed to create it. |
| 72 | */ |
| 73 | public DocumentBuilder createDefaultBuilder() { |
| 74 | DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance(); |
| 75 | domFactory.setNamespaceAware(true); |
| 76 | try { |
| 77 | return domFactory.newDocumentBuilder(); |
| 78 | } catch (ParserConfigurationException e) { |
| 79 | // This should never happen ? |
| 80 | logger.severe(e.getMessage()); |
| 81 | } |
| 82 | return null; |
| 83 | } |
| 84 | |
| 85 | /** |
| 86 | * @return the xpath |
| 87 | */ |
| 88 | public XPath getXpath() { |
| 89 | return xpath; |
| 90 | } |
| 91 | |
| 92 | /** |
| 93 | * @param xpath the xpath to set |
| 94 | */ |
| 95 | public void setXpath(XPath xpath) { |
| 96 | this.xpath = xpath; |
| 97 | } |
| 98 | |
| 99 | /* |
| 100 | * Return the Document instance, associated to the current InputStream |
| 101 | * @return |
| 102 | * @throws XRadarResultsAnalyserException |
| 103 | */ |
| 104 | private Document getCurrentDocument() throws XRadarResultsAnalyserException { |
| 105 | if ( doc == null ) { |
| 106 | if ( inputStream == null ) |
| 107 | throw new XRadarResultsAnalyserException("No input stream toward XML data provided !"); |
| 108 | else |
| 109 | try { |
| 110 | doc = builder.parse(this.inputStream); |
| 111 | } catch (SAXException e) { |
| 112 | new XRadarResultsAnalyserException(e); |
| 113 | } catch (IOException e) { |
| 114 | new XRadarResultsAnalyserException(e); |
| 115 | } |
| 116 | } |
| 117 | return doc; |
| 118 | } |
| 119 | |
| 120 | /** |
| 121 | * <p>Execute the provided query on the configured InputStream. Check |
| 122 | * |
| 123 | * @param src |
| 124 | * @param query |
| 125 | * @return |
| 126 | */ |
| 127 | public List<Violation> analyse(QueryData data) throws XRadarResultsAnalyserException { |
| 128 | List<Violation> results = new ArrayList<Violation>(0); |
| 129 | String query = initialize(data); |
| 130 | |
| 131 | if ( query != null && ! "".equals(query) ) { |
| 132 | try { |
| 133 | doc = getCurrentDocument(); |
| 134 | List<Node> nodes = executeXPathQuery(doc, query); |
| 135 | for (Node node : nodes) { |
| 136 | results.add(this.nodeToViolation(node)); |
| 137 | } |
| 138 | } catch (XPathExpressionException e) { |
| 139 | new XRadarResultsAnalyserException(e); |
| 140 | } |
| 141 | } |
| 142 | return results; |
| 143 | } |
| 144 | |
| 145 | private String initialize(QueryData data) { |
| 146 | if ( this.builder == null ) |
| 147 | this.builder = createDefaultBuilder(); |
| 148 | if ( this.xpath == null ) |
| 149 | this.xpath = XPathFactory.newInstance().newXPath(); |
| 150 | this.setInputStream(data.getResultsData()); |
| 151 | return data.getQuery(); |
| 152 | } |
| 153 | |
| 154 | private Violation nodeToViolation(Node node) { |
| 155 | Violation violation = new Violation(); |
| 156 | violation.setName(node.getNodeName()); |
| 157 | return violation; |
| 158 | } |
| 159 | |
| 160 | /* |
| 161 | * |
| 162 | */ |
| 163 | public List<Node> executeXPathQuery(Document doc,String query) throws XPathExpressionException { |
| 164 | List<Node> results = new ArrayList<Node>(0); |
| 165 | XPathExpression expr = xpath.compile(query); |
| 166 | Object result = expr.evaluate(doc, XPathConstants.NODESET); |
| 167 | NodeList nodes = (NodeList) result; |
| 168 | logger.finest("Query:" + query + " returned " + nodes.getLength() + " items."); |
| 169 | results = new ArrayList<Node>(nodes.getLength()); |
| 170 | for (int indexItem = 0; indexItem < nodes.getLength(); indexItem++) { |
| 171 | results.add(nodes.item(indexItem)); |
| 172 | } |
| 173 | return results; |
| 174 | } |
| 175 | } |