/*! * This program is free software; you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software * Foundation. * * You should have received a copy of the GNU Lesser General Public License along with this * program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html * or from the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Lesser General Public License for more details. * * Copyright (c) 2002-2013 Pentaho Corporation.. All rights reserved. */ package org.pentaho.reporting.engine.classic.extensions.datasources.xpath; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.pentaho.reporting.engine.classic.core.DataRow; import org.pentaho.reporting.engine.classic.core.ReportDataFactoryException; import org.pentaho.reporting.engine.classic.core.util.TypedTableModel; import org.pentaho.reporting.libraries.base.util.StringUtils; import org.pentaho.reporting.libraries.resourceloader.ResourceData; import org.pentaho.reporting.libraries.resourceloader.ResourceLoadingException; import org.pentaho.reporting.libraries.resourceloader.ResourceManager; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; import javax.swing.table.AbstractTableModel; import javax.xml.namespace.QName; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import javax.xml.xpath.XPathVariableResolver; import java.io.IOException; import java.io.InputStream; import java.math.BigDecimal; import java.math.BigInteger; import java.sql.Date; import java.sql.Timestamp; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; import java.util.StringTokenizer; public class XPathTableModel extends AbstractTableModel { private static final Log logger = LogFactory.getLog( XPathTableModel.class ); private static class InternalXPathVariableResolver implements XPathVariableResolver { private final DataRow parameters; private InternalXPathVariableResolver( final DataRow parameters ) { this.parameters = parameters; } public Object resolveVariable( final QName variableName ) { if ( parameters != null ) { final String var = variableName.getLocalPart(); return parameters.get( var ); } return null; } } private static final Map<String, Class> SUPPORTED_TYPES; static { final HashMap<String, Class> types = new HashMap<String, Class>(); types.put( "java.lang.String", String.class ); types.put( "java.sql.Date", Date.class ); types.put( "java.math.BigDecimal", BigDecimal.class ); types.put( "java.sql.Timestamp", Timestamp.class ); types.put( "java.lang.Integer", Integer.class ); types.put( "java.lang.Double", Double.class ); types.put( "java.lang.Long", Long.class ); SUPPORTED_TYPES = Collections.unmodifiableMap( types ); } private ArrayList<Class> columnTypes; private TypedTableModel backend; private int dataLimit; private static final String PROCESSING_INSTRUCTION_PENTAHO_DATASET = "/processing-instruction('pentaho-dataset')"; private static final String COMMENT_XPATH = "/comment()"; private static final String RESULT_SET_COMMENT_XPATH = "/result-set/comment()"; public XPathTableModel( final ResourceData xmlResource, final ResourceManager resourceManager, final String xPathExpression, final DataRow parameters, final int maxRowsToProcess ) throws ReportDataFactoryException { try { columnTypes = new ArrayList<Class>(); final XPath xPath = XPathFactory.newInstance().newXPath(); xPath.setXPathVariableResolver( new InternalXPathVariableResolver( parameters ) ); // load metadata (number of rows, row names, row types) final String nodeValue = computeColDeclaration( xmlResource, resourceManager, xPath ); if ( nodeValue != null ) { final StringTokenizer stringTokenizer = new StringTokenizer( nodeValue, "," ); while ( stringTokenizer.hasMoreTokens() ) { final String className = stringTokenizer.nextToken(); if ( SUPPORTED_TYPES.containsKey( className ) ) { columnTypes.add( SUPPORTED_TYPES.get( className ) ); } else { columnTypes.add( String.class ); } } } if ( maxRowsToProcess == -1 ) { dataLimit = Integer.MAX_VALUE; } else { this.dataLimit = Math.min( 1, maxRowsToProcess ); } backend = new TypedTableModel(); final LinkedHashMap<String, String> results = new LinkedHashMap<String, String>(); // try to find all valid column names // visit all entries and add the names as we find them final NodeList rows = evaluateNodeList( xPath, xPathExpression, xmlResource, resourceManager ); for ( int r = 0; r < rows.getLength(); r++ ) { // Get the next value from the result sequence final Node rowValue = rows.item( r ); final short nodeType = rowValue.getNodeType(); // Print this value if ( nodeType == Node.ELEMENT_NODE ) { // explodes into columns .. if ( processNode( rowValue, results, backend ) == false ) { return; } } else { final String columnName = rowValue.getNodeValue(); results.put( columnName, rowValue.toString() ); if ( addRow( results, backend ) == false ) { return; } results.clear(); } // System.out.println("NodeType: " + nodeType + "\n" + value.toString()); } } catch ( Exception e ) { throw new ReportDataFactoryException( "Failed to query XPath datasource", e ); } } private Object convertFromString( final int columnPosition, final String textContent ) throws ReportDataFactoryException { if ( textContent == null ) { return null; } try { final Class columnClass; if ( columnPosition < columnTypes.size() ) { columnClass = columnTypes.get( columnPosition ); } else { columnClass = String.class; } if ( String.class.equals( columnClass ) ) { return textContent; } if ( columnClass == Date.class ) { return ( new Date( Long.parseLong( textContent ) ) ); } else if ( columnClass == BigDecimal.class || columnClass == Number.class ) { return ( new BigDecimal( textContent ) ); } else if ( columnClass == BigInteger.class ) { return ( new BigInteger( textContent ) ); } else if ( columnClass == Timestamp.class ) { return ( new Timestamp( Long.parseLong( textContent ) ) ); } else if ( columnClass == Integer.class ) { return ( Integer.valueOf( textContent ) ); } else if ( columnClass == Double.class ) { return ( Double.valueOf( textContent ) ); } else if ( columnClass == Long.class ) { return ( Long.valueOf( textContent ) ); } else { return ( textContent ); } } catch ( Exception e ) { throw new ReportDataFactoryException( "Unable to convert data to the declared type", e ); } } private boolean addRow( final HashMap<String, String> results, final TypedTableModel tableModel ) throws ReportDataFactoryException { final int row = tableModel.getRowCount(); if ( row >= dataLimit ) { return false; } final Set<Map.Entry<String, String>> entries = results.entrySet(); for ( final Map.Entry<String, String> entry : entries ) { final String colName = entry.getKey(); final int colIdx = tableModel.findColumn( colName ); if ( colIdx == -1 ) { tableModel.addColumn( colName, Object.class ); final int colum = tableModel.getColumnCount() - 1; final Object value = convertFromString( colum, entry.getValue() ); tableModel.setValueAt( value, row, colum ); } else { final Object value = convertFromString( colIdx, entry.getValue() ); tableModel.setValueAt( value, row, colIdx ); } } return true; } private boolean processNode( final Node node, final LinkedHashMap<String, String> results, final TypedTableModel typedTableModel ) throws ReportDataFactoryException { final LinkedHashMap<String, String> innerResults = new LinkedHashMap<String, String>( results ); boolean isLeaf = true; // System.out.println("<" + node.getQName() + ">"); final NodeList childList = node.getChildNodes(); for ( int i = 0; i < childList.getLength(); i++ ) { final Node nodeIf = childList.item( i ); final short type = nodeIf.getNodeType(); if ( type == Node.COMMENT_NODE ) { continue; } if ( type == Node.ELEMENT_NODE ) { final NodeList anIf = nodeIf.getChildNodes(); final int size = anIf.getLength(); // check if either a empty node or a if ( size == 0 ) { // a empty node ... innerResults.put( nodeIf.getNodeName(), null ); } else if ( size == 1 ) { final Node subNode = anIf.item( 0 ); if ( subNode.getNodeType() == Node.TEXT_NODE || subNode.getNodeType() == Node.CDATA_SECTION_NODE ) { // a single text node .. innerResults.put( nodeIf.getNodeName(), nodeIf.getTextContent() ); } else if ( subNode.getNodeType() == Node.ELEMENT_NODE ) { isLeaf = false; } else { innerResults.put( nodeIf.getNodeName(), nodeIf.getTextContent() ); } } else { isLeaf = false; } } else { final String content = nodeIf.getTextContent(); if ( StringUtils.isEmpty( content, true ) == false ) { innerResults.put( nodeIf.getNodeName(), content ); } } } if ( isLeaf == false ) { for ( int i = 0; i < childList.getLength(); i++ ) { final Node deepNode = childList.item( i ); if ( deepNode.getNodeType() == Node.ELEMENT_NODE ) { final NodeList childNodes = deepNode.getChildNodes(); if ( childNodes.getLength() > 1 || ( childNodes.getLength() == 1 && childNodes.item( 0 ).getNodeType() == Node.ELEMENT_NODE ) ) { if ( processNode( deepNode, innerResults, typedTableModel ) == false ) { return false; } } } } return true; } else { return addRow( innerResults, typedTableModel ); } } private String computeColDeclaration( final ResourceData xmlResource, final ResourceManager resourceManager, final XPath xPath ) throws XPathExpressionException, ResourceLoadingException, IOException { final Node pi = evaluateNode( xPath, PROCESSING_INSTRUCTION_PENTAHO_DATASET, xmlResource, resourceManager ); if ( pi != null ) { final String text = pi.getNodeValue(); if ( text.length() > 0 ) { return text; } } final Node types = evaluateNode( xPath, COMMENT_XPATH, xmlResource, resourceManager ); if ( types != null ) { final String text = types.getNodeValue(); if ( text.length() > 0 ) { return text; } } final Node resultsetComment = evaluateNode( xPath, RESULT_SET_COMMENT_XPATH, xmlResource, resourceManager ); if ( resultsetComment != null ) { final String text = resultsetComment.getNodeValue(); if ( text.length() > 0 ) { return text; } } return null; } private NodeList evaluateNodeList( final XPath xpath, final String xpathQuery, final ResourceData xmlResourceData, final ResourceManager resourceManager ) throws XPathExpressionException, ResourceLoadingException, IOException { final InputStream stream = xmlResourceData.getResourceAsStream( resourceManager ); try { return (NodeList) xpath.evaluate( xpathQuery, new InputSource( stream ), XPathConstants.NODESET ); } finally { stream.close(); } } private Node evaluateNode( final XPath xpath, final String xpathQuery, final ResourceData xmlResourceData, final ResourceManager resourceManager ) throws XPathExpressionException, ResourceLoadingException, IOException { final InputStream stream = xmlResourceData.getResourceAsStream( resourceManager ); try { return (Node) xpath.evaluate( xpathQuery, new InputSource( stream ), XPathConstants.NODE ); } finally { stream.close(); } } public Class getColumnClass( final int columnIndex ) { if ( columnIndex < columnTypes.size() ) { return columnTypes.get( columnIndex ); } return String.class; } public int getRowCount() { return backend.getRowCount(); } public int getColumnCount() { return backend.getColumnCount(); } public String getColumnName( final int column ) { return backend.getColumnName( column ); } public Object getValueAt( final int rowIndex, final int columnIndex ) { return backend.getValueAt( rowIndex, columnIndex ); } }