/*!
* 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 );
}
}