/*! * 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-2016 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.ClassicEngineBoot; import org.pentaho.reporting.engine.classic.core.DataRow; import org.pentaho.reporting.engine.classic.core.ReportDataFactoryException; import org.pentaho.reporting.engine.classic.core.util.IntegerCache; import org.pentaho.reporting.libraries.base.config.Configuration; import org.pentaho.reporting.libraries.base.util.GenericObjectTable; 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 org.xml.sax.SAXException; import javax.swing.table.AbstractTableModel; import javax.xml.namespace.QName; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpression; 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.sql.Date; import java.sql.Timestamp; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.StringTokenizer; public class LegacyXPathTableModel extends AbstractTableModel { private static final Log logger = LogFactory.getLog( LegacyXPathTableModel.class ); public static final String DISALLOW_DOCTYPE_DECL = "http://apache.org/xml/features/disallow-doctype-decl"; public static final String XPATH_ENABLE_DTDS = "org.pentaho.reporting.engine.classic.extensions.datasources.xpath.EnableDTDs"; 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 GenericObjectTable data; private ArrayList<Class> columnTypes; private ArrayList<String> columnNames; public LegacyXPathTableModel( final ResourceData xmlResource, final ResourceManager resourceManager, final String xPathExpression, final DataRow parameters, final int maxRowsToProcess ) throws ReportDataFactoryException { try { columnTypes = new ArrayList<Class>(); columnNames = new ArrayList<String>(); DocumentBuilderFactory dbf = calculateDocumentBuilderFactory( ClassicEngineBoot.getInstance().getGlobalConfig() ); 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, dbf.newDocumentBuilder() ); 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 ); } } } // 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 ); final HashMap<String, Integer> columnNamesToPositionMap = new HashMap<String, Integer>(); final int rowCount = rows.getLength(); data = new GenericObjectTable( Math.max( 1, rowCount ), Math.max( 1, columnTypes.size() ) ); logger.debug( "Processing " + rowCount + " rows" ); for ( int row = 0; row < rowCount; row++ ) { if ( maxRowsToProcess >= 0 ) { // query at least one row, so that we get the column names ... final int count = data.getRowCount(); if ( count > 0 && count >= maxRowsToProcess ) { break; } } final Node node = rows.item( row ); if ( node.getNodeType() != Node.ELEMENT_NODE ) { continue; } logger.debug( "Processing row " + row ); final NodeList childNodes = node.getChildNodes(); for ( int column = 0; column < childNodes.getLength(); column++ ) { final Node child = childNodes.item( column ); if ( child.getNodeType() != Node.ELEMENT_NODE ) { continue; } final String columnName = child.getNodeName(); final String textContent = extractText( child ); final int columnPosition; final Integer rawPos = columnNamesToPositionMap.get( columnName ); if ( rawPos == null ) { // a new one columnPosition = columnNames.size(); columnNames.add( columnName ); columnNamesToPositionMap.put( columnName, IntegerCache.getInteger( columnPosition ) ); } else { columnPosition = rawPos.intValue(); } logger.debug( "Processing column " + columnPosition + " Name=" + columnName + " value=" + textContent ); final Class columnClass; if ( columnPosition < columnTypes.size() ) { columnClass = columnTypes.get( columnPosition ); } else { columnClass = String.class; } if ( String.class.equals( columnClass ) ) { data.setObject( row, columnPosition, textContent ); continue; } if ( columnClass == Date.class ) { data.setObject( row, columnPosition, new Date( Long.parseLong( textContent ) ) ); } else if ( columnClass == BigDecimal.class ) { data.setObject( row, columnPosition, new BigDecimal( textContent ) ); } else if ( columnClass == Timestamp.class ) { data.setObject( row, columnPosition, new Timestamp( Long.parseLong( textContent ) ) ); } else if ( columnClass == Integer.class ) { data.setObject( row, columnPosition, Integer.valueOf( textContent ) ); } else if ( columnClass == Double.class ) { data.setObject( row, columnPosition, Double.valueOf( textContent ) ); } else if ( columnClass == Long.class ) { data.setObject( row, columnPosition, Long.valueOf( textContent ) ); } else { data.setObject( row, columnPosition, textContent ); } } } } catch ( Exception e ) { throw new ReportDataFactoryException( "Failed to query XPath datasource", e ); } } private DocumentBuilderFactory calculateDocumentBuilderFactory( final Configuration configuration ) throws ParserConfigurationException { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); dbf.setXIncludeAware( false ); if ( !"true".equals( configuration .getConfigProperty( XPATH_ENABLE_DTDS ) ) ) { dbf.setFeature( DISALLOW_DOCTYPE_DECL, true ); } return dbf; } private String computeColDeclaration( final ResourceData xmlResource, final ResourceManager resourceManager, final XPath xPath, final DocumentBuilder builder ) throws XPathExpressionException, ResourceLoadingException, IOException, ParserConfigurationException, SAXException { final Node pi = evaluateNode( xPath.compile( "/processing-instruction('pentaho-dataset')" ), xmlResource, resourceManager, builder ); if ( pi != null ) { final String text = pi.getNodeValue(); if ( text.length() > 0 ) { return text; } } final Node types = evaluateNode( xPath.compile( "/comment()" ), xmlResource, resourceManager, builder ); if ( types != null ) { final String text = types.getNodeValue(); if ( text.length() > 0 ) { return text; } } final Node resultsetComment = evaluateNode( xPath.compile( "/result-set/comment()" ), xmlResource, resourceManager, builder ); if ( resultsetComment != null ) { final String text = resultsetComment.getNodeValue(); if ( text.length() > 0 ) { return text; } } return null; } private String extractText( final Node child ) { final NodeList contentNodes = child.getChildNodes(); final StringBuilder textContent = new StringBuilder( 32 ); for ( int k = 0; k < contentNodes.getLength(); k++ ) { final Node t = contentNodes.item( k ); if ( t.getNodeType() == Node.TEXT_NODE ) { textContent.append( t.getNodeValue() ); } } return textContent.toString(); } 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 XPathExpression xPathExpression, final ResourceData xmlResourceData, final ResourceManager resourceManager, final DocumentBuilder builder ) throws XPathExpressionException, ResourceLoadingException, IOException, SAXException { final InputStream stream = xmlResourceData.getResourceAsStream( resourceManager ); try { return (Node) xPathExpression.evaluate( builder.parse( 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 data.getRowCount(); } public int getColumnCount() { return data.getColumnCount(); } public String getColumnName( final int column ) { return columnNames.get( column ); } public Object getValueAt( final int rowIndex, final int columnIndex ) { return data.getObject( rowIndex, columnIndex ); } }