net.sf.jasperreports.engine.data.JRXmlDataSource Maven / Gradle / Ivy
Show all versions of jasperreports Show documentation
/*
* JasperReports - Free Java Reporting Library.
* Copyright (C) 2001 - 2019 TIBCO Software Inc. All rights reserved.
* http://www.jaspersoft.com
*
* Unless you have purchased a commercial license agreement from Jaspersoft,
* the following license terms apply:
*
* This program is part of JasperReports.
*
* JasperReports is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* JasperReports 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.
*
* You should have received a copy of the GNU Lesser General Public License
* along with JasperReports. If not, see .
*/
/*
* Contributors:
* Tim Thomas - [email protected]
*/
package net.sf.jasperreports.engine.data;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import net.sf.jasperreports.engine.DefaultJasperReportsContext;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JasperReportsContext;
import net.sf.jasperreports.engine.util.JRXmlUtils;
import net.sf.jasperreports.engine.util.xml.JRXPathExecuter;
import net.sf.jasperreports.engine.util.xml.JRXPathExecuterUtils;
import net.sf.jasperreports.repo.RepositoryContext;
import net.sf.jasperreports.repo.RepositoryUtil;
import net.sf.jasperreports.repo.SimpleRepositoryContext;
/**
* XML data source implementation that allows to access the data from a xml
* document using XPath expressions.
*
* The data source is constructed around a node set (record set) selected
* by an XPath expression from the XML document.
*
*
* Each field can provide an additional XPath expression that will be used to
* select its value. This expression must be specified using the {@link #PROPERTY_FIELD_EXPRESSION}
* custom property at field level. The use of the {@link net.sf.jasperreports.engine.JRField#getDescription() field description} to specify the XPath expression
* is still supported, but is now discouraged, the above mentioned custom property taking precedence
* over the field description.
* The expression is evaluated in the context of the current
* node thus the expression should be relative to the current node.
*
*
* To support subreports, sub data sources can be created. There are two different methods
* for creating sub data sources. The first one allows to create a sub data source rooted
* at the current node. The current node can be seen as a new document around which the
* sub data source is created. The second method allows to create a sub data source that
* is rooted at the same document that is used by the data source but uses a different
* XPath select expression.
*
*
* Example:
*
* <A>
* <B id="0">
* <C>
* <C>
* </B>
* <B id="1">
* <C>
* <C>
* </B>
* <D id="3">
* <E>
* <E>
* </D>
* </A>
*
*
* Data source creation
*
* - new JRXmlDataSource(document, "/A/B") - creates a data source with two nodes of type /A/B
*
- new JRXmlDataSource(document, "/A/D") - creates a data source with two nodes of type /A/D
*
* Field selection
*
* - @id - will select the "id" attribute from the current node
*
- C - will select the value of the first node of type C under the current node.
*
* Sub data source creation
*
* - "((net.sf.jasperreports.engine.data.JRXmlDataSource)$P{REPORT_DATA_SOURCE}).subDataSource("/B/C")
* - in the context of the node B, creates a data source with elements of type /B/C
*
- "((net.sf.jasperreports.engine.data.JRXmlDataSource)$P{REPORT_DATA_SOURCE}).dataSource("/A/D")
* - creates a data source with elements of type /A/D
*
*
*
* Generally the full power of XPath expression is available. As an example, "/A/B[@id > 0"] will select all the
* nodes of type /A/B having the id greater than 0.
* You'll find a short XPath tutorial here.
*
*
*
* Note on performance. Due to the fact that all the XPath expression are interpreted the
* data source performance is not great. For the cases where more speed is required,
* consider implementing a custom data source that directly accesses the Document through the DOM API.
*
* @author Peter Severin ([email protected], [email protected])
* @see JRXPathExecuterUtils
*/
public class JRXmlDataSource extends AbstractXmlDataSource
{
// the xml document
private Document document;
// the XPath select expression that gives the nodes to iterate
private String selectExpression;
// the node list
private NodeList nodeList;
// the node list length
private int nodeListLength;
// the current node
private Node currentNode;
// current node index
private int currentNodeIndex = - 1;
private final JRXPathExecuter xPathExecuter;
private InputStream inputStream;
private boolean closeInputStream;
// -----------------------------------------------------------------
// Constructors
/**
* Creates the data source by parsing the xml document from the given file.
* The data source will contain exactly one record consisting of the document node itself.
*
* @param document the document
* @throws JRException if the data source cannot be created
*/
public JRXmlDataSource(JasperReportsContext jasperReportsContext, Document document) throws JRException {
this(jasperReportsContext, document, ".");
}
/**
* @see #JRXmlDataSource(JasperReportsContext, Document)
*/
public JRXmlDataSource(Document document) throws JRException {
this(DefaultJasperReportsContext.getInstance(), document);
}
/**
* Creates the data source by parsing the xml document from the given file.
* An additional XPath expression specifies the select criteria that produces the
* nodes (records) for the data source.
*
* @param document the document
* @param selectExpression the XPath select expression
* @throws JRException if the data source cannot be created
*/
public JRXmlDataSource(
JasperReportsContext jasperReportsContext,
Document document,
String selectExpression
) throws JRException
{
this.document = document;
this.selectExpression = selectExpression;
this.xPathExecuter = JRXPathExecuterUtils.getXPathExecuter(jasperReportsContext);
moveFirst();
}
/**
* @see #JRXmlDataSource(JasperReportsContext, Document, String)
*/
public JRXmlDataSource(Document document, String selectExpression) throws JRException
{
this(DefaultJasperReportsContext.getInstance(), document, selectExpression);
}
/**
* Creates the data source by parsing the xml document from the given input stream.
*
* @param in the input stream
* @see JRXmlDataSource#JRXmlDataSource(Document)
*/
public JRXmlDataSource(JasperReportsContext jasperReportsContext, InputStream in) throws JRException
{
this(jasperReportsContext, in, false);
}
public JRXmlDataSource(JasperReportsContext jasperReportsContext, InputStream in, boolean isNamespaceAware) throws JRException
{
this(jasperReportsContext, in, ".", isNamespaceAware);
}
/**
* @see #JRXmlDataSource(JasperReportsContext, InputStream)
*/
public JRXmlDataSource(InputStream in) throws JRException {
this(in, false);
}
public JRXmlDataSource(InputStream in, boolean isNamespaceAware) throws JRException {
this(DefaultJasperReportsContext.getInstance(), in, isNamespaceAware);
}
/**
* Creates the data source by parsing the xml document from the given input stream.
*
* @see JRXmlDataSource#JRXmlDataSource(InputStream)
* @see JRXmlDataSource#JRXmlDataSource(Document, String)
*/
public JRXmlDataSource(
JasperReportsContext jasperReportsContext,
InputStream in,
String selectExpression
) throws JRException
{
this(jasperReportsContext, in, selectExpression, false);
}
public JRXmlDataSource(
JasperReportsContext jasperReportsContext,
InputStream in,
String selectExpression,
boolean isNamespaceAware
) throws JRException
{
this(jasperReportsContext, JRXmlUtils.parse(new InputSource(in), isNamespaceAware), selectExpression);
this.inputStream = in;
this.closeInputStream = false;
}
/**
* @see #JRXmlDataSource(JasperReportsContext, InputStream, String)
*/
public JRXmlDataSource(InputStream in, String selectExpression)
throws JRException {
this(in, selectExpression, false);
}
public JRXmlDataSource(InputStream in, String selectExpression, boolean isNamespaceAware)
throws JRException {
this(DefaultJasperReportsContext.getInstance(), in, selectExpression, isNamespaceAware);
}
/**
* Creates the data source by parsing the xml document from the given system identifier (URI).
* If the system identifier is a URL, it must be full resolved.
*
* @param uri the system identifier
* @see JRXmlDataSource#JRXmlDataSource(Document)
*/
public JRXmlDataSource(JasperReportsContext jasperReportsContext, String uri) throws JRException {
this(jasperReportsContext, uri, false);
}
public JRXmlDataSource(JasperReportsContext jasperReportsContext, String uri, boolean isNamespaceAware) throws JRException {
this(jasperReportsContext, uri, ".", isNamespaceAware);
}
/**
* @see #JRXmlDataSource(JasperReportsContext, String)
*/
public JRXmlDataSource(String uri) throws JRException {
this(uri, false);
}
public JRXmlDataSource(String uri, boolean isNamespaceAware) throws JRException {
this(DefaultJasperReportsContext.getInstance(), uri, isNamespaceAware);
}
/**
* Creates the data source by parsing the xml document from the given system identifier (URI).
*
* @see JRXmlDataSource#JRXmlDataSource(String)
* @see JRXmlDataSource#JRXmlDataSource(Document, String)
*/
public JRXmlDataSource(
JasperReportsContext jasperReportsContext,
String uri,
String selectExpression
) throws JRException
{
this(jasperReportsContext, uri, selectExpression, false);
}
public JRXmlDataSource(
JasperReportsContext jasperReportsContext,
String uri,
String selectExpression,
boolean isNamespaceAware
) throws JRException
{
this(SimpleRepositoryContext.of(jasperReportsContext), uri, selectExpression, isNamespaceAware);
}
public JRXmlDataSource(
RepositoryContext context,
String uri,
String selectExpression,
boolean isNamespaceAware
) throws JRException
{
this(
context.getJasperReportsContext(),
RepositoryUtil.getInstance(context).getInputStreamFromLocation(uri),
selectExpression,
isNamespaceAware
);
this.closeInputStream = true;//FIXME close the stream immediately
}
/**
* @see #JRXmlDataSource(JasperReportsContext, String, String)
*/
public JRXmlDataSource(String uri, String selectExpression)
throws JRException {
this(uri, selectExpression, false);
}
public JRXmlDataSource(String uri, String selectExpression, boolean isNamespaceAware)
throws JRException {
this(DefaultJasperReportsContext.getInstance(), uri, selectExpression, isNamespaceAware);
}
/**
* Creates the data source by parsing the xml document from the given file.
*
* @param file the file
* @see JRXmlDataSource#JRXmlDataSource(Document)
*/
public JRXmlDataSource(JasperReportsContext jasperReportsContext, File file) throws JRException {
this(jasperReportsContext, file, false);
}
public JRXmlDataSource(JasperReportsContext jasperReportsContext, File file, boolean isNamespaceAware) throws JRException {
this(jasperReportsContext, file, ".", isNamespaceAware);
}
/**
* @see #JRXmlDataSource(JasperReportsContext, File)
*/
public JRXmlDataSource(File file) throws JRException {
this(file, false);
}
public JRXmlDataSource(File file, boolean isNamespaceAware) throws JRException {
this(DefaultJasperReportsContext.getInstance(), file, isNamespaceAware);
}
/**
* Creates the data source by parsing the xml document from the given file.
*
* @see JRXmlDataSource#JRXmlDataSource(File)
* @see JRXmlDataSource#JRXmlDataSource(Document, String)
*/
public JRXmlDataSource(JasperReportsContext jasperReportsContext, File file, String selectExpression)
throws JRException {
this(jasperReportsContext, file, selectExpression, false);
}
public JRXmlDataSource(JasperReportsContext jasperReportsContext, File file, String selectExpression, boolean isNamespaceAware)
throws JRException {
this(jasperReportsContext, JRXmlUtils.parse(file, isNamespaceAware), selectExpression);
}
/**
* @see #JRXmlDataSource(JasperReportsContext, File, String)
*/
public JRXmlDataSource(File file, String selectExpression)
throws JRException {
this(file, selectExpression, false);
}
public JRXmlDataSource(File file, String selectExpression, boolean isNamespaceAware)
throws JRException {
this(DefaultJasperReportsContext.getInstance(), file, selectExpression, isNamespaceAware);
}
// -----------------------------------------------------------------
// Implementation
/*
* (non-Javadoc)
*
* @see net.sf.jasperreports.engine.JRRewindableDataSource#moveFirst()
*/
@Override
public void moveFirst() throws JRException {
if (document == null)
{
throw
new JRException(
EXCEPTION_MESSAGE_KEY_NULL_DOCUMENT,
(Object[])null);
}
if (selectExpression == null)
{
throw
new JRException(
EXCEPTION_MESSAGE_KEY_NULL_SELECT_EXPRESSION,
(Object[])null);
}
currentNode = null;
currentNodeIndex = -1;
nodeListLength = 0;
nodeList = xPathExecuter.selectNodeList(document,
selectExpression);
nodeListLength = nodeList.getLength();
}
/*
* (non-Javadoc)
*
* @see net.sf.jasperreports.engine.JRDataSource#next()
*/
@Override
public boolean next()
{
if(currentNodeIndex == nodeListLength - 1)
{
return false;
}
currentNode = nodeList.item(++ currentNodeIndex);
return true;
}
@Override
public int recordCount()
{
return nodeListLength;
}
@Override
public int currentIndex()
{
return currentNodeIndex;
}
@Override
public void moveToRecord(int index) throws NoRecordAtIndexException
{
if (index >= 0 && index < nodeListLength)
{
currentNodeIndex = index;
currentNode = nodeList.item(index);
}
else
{
throw new NoRecordAtIndexException(index);
}
}
@Override
public Node getCurrentNode()
{
return currentNode;
}
@Override
public Object getSelectObject(Node currentNode, String expression) throws JRException
{
return xPathExecuter.selectObject(currentNode, expression);
}
/**
* Creates a sub data source using the current node (record) as the root
* of the document. An additional XPath expression specifies the select criteria applied to
* this new document and that produces the nodes (records) for the data source.
*
* @param selectExpr the XPath select expression
* @return the xml sub data source
* @throws JRException if the sub data source couldn't be created
* @see JRXmlDataSource#JRXmlDataSource(Document, String)
*/
@Override
public JRXmlDataSource subDataSource(String selectExpr)
throws JRException {
// create a new document from the current node
Document doc = subDocument();
JRXmlDataSource subDataSource = new JRXmlDataSource(doc, selectExpr);
subDataSource.setTextAttributes(this);
return subDataSource;
}
@Override
public JRXmlDataSource subDataSource() throws JRException // need to override this method here to keep binary compatibility with older releases
{
return super.subDataSource();
}
/**
* Creates a document using the current node as root.
*
* @return a document having the current node as root
* @throws JRException
*/
@Override
public Document subDocument() throws JRException
{
if(currentNode == null)
{
throw
new JRException(
EXCEPTION_MESSAGE_KEY_NODE_NOT_AVAILABLE,
(Object[])null);
}
// create a new document from the current node
return JRXmlUtils.createDocument(currentNode);
}
/**
* Creates a sub data source using as root document the document used by "this" data source.
* An additional XPath expression specifies the select criteria applied to
* this document and that produces the nodes (records) for the data source.
*
* @param selectExpr the XPath select expression
* @return the xml sub data source
* @throws JRException if the sub data source couldn't be created
* @see JRXmlDataSource#JRXmlDataSource(Document, String)
*/
@Override
public JRXmlDataSource dataSource(String selectExpr)
throws JRException {
JRXmlDataSource subDataSource = new JRXmlDataSource(document, selectExpr);
subDataSource.setTextAttributes(this);
return subDataSource;
}
@Override
public JRXmlDataSource dataSource() throws JRException // need to override this method here to keep binary compatibility with older releases
{
return super.dataSource();
}
/**
* Closes the reader. Users of this data source should close it after usage.
*/
public void close()
{
try
{
if (closeInputStream)
{
inputStream.close();
}
}
catch(IOException e)
{
//nothing to do
}
}
}