1 /*
2 * JasperReports - Free Java Reporting Library.
3 * Copyright (C) 2001 - 2019 TIBCO Software Inc. All rights reserved.
4 * http://www.jaspersoft.com
5 *
6 * Unless you have purchased a commercial license agreement from Jaspersoft,
7 * the following license terms apply:
8 *
9 * This program is part of JasperReports.
10 *
11 * JasperReports is free software: you can redistribute it and/or modify
12 * it under the terms of the GNU Lesser General Public License as published by
13 * the Free Software Foundation, either version 3 of the License, or
14 * (at your option) any later version.
15 *
16 * JasperReports is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU Lesser General Public License for more details.
20 *
21 * You should have received a copy of the GNU Lesser General Public License
22 * along with JasperReports. If not, see <http://www.gnu.org/licenses/>.
23 */
24
25 /*
26 * Contributors:
27 * Tim Thomas - tthomas48@users.sourceforge.net
28 */
29 package net.sf.jasperreports.engine.data;
30
31 import java.io.File;
32 import java.io.IOException;
33 import java.io.InputStream;
34
35 import org.w3c.dom.Document;
36 import org.w3c.dom.Node;
37 import org.w3c.dom.NodeList;
38 import org.xml.sax.InputSource;
39
40 import net.sf.jasperreports.engine.DefaultJasperReportsContext;
41 import net.sf.jasperreports.engine.JRException;
42 import net.sf.jasperreports.engine.JasperReportsContext;
43 import net.sf.jasperreports.engine.util.JRXmlUtils;
44 import net.sf.jasperreports.engine.util.xml.JRXPathExecuter;
45 import net.sf.jasperreports.engine.util.xml.JRXPathExecuterUtils;
46 import net.sf.jasperreports.repo.RepositoryContext;
47 import net.sf.jasperreports.repo.RepositoryUtil;
48 import net.sf.jasperreports.repo.SimpleRepositoryContext;
49
50 /**
51 * XML data source implementation that allows to access the data from a xml
52 * document using XPath expressions.
53 * <p>
54 * The data source is constructed around a node set (record set) selected
55 * by an XPath expression from the XML document.
56 * </p>
57 * <p>
58 * Each field can provide an additional XPath expression that will be used to
59 * select its value. This expression must be specified using the {@link #PROPERTY_FIELD_EXPRESSION}
60 * custom property at field level. The use of the {@link net.sf.jasperreports.engine.JRField#getDescription() field description} to specify the XPath expression
61 * is still supported, but is now discouraged, the above mentioned custom property taking precedence
62 * over the field description.
63 * The expression is evaluated in the context of the current
64 * node thus the expression should be relative to the current node.
65 * </p>
66 * <p>
67 * To support subreports, sub data sources can be created. There are two different methods
68 * for creating sub data sources. The first one allows to create a sub data source rooted
69 * at the current node. The current node can be seen as a new document around which the
70 * sub data source is created. The second method allows to create a sub data source that
71 * is rooted at the same document that is used by the data source but uses a different
72 * XPath select expression.
73 * </p>
74 * <p>
75 * Example:
76 * <pre>
77 * <A>
78 * <B id="0">
79 * <C>
80 * <C>
81 * </B>
82 * <B id="1">
83 * <C>
84 * <C>
85 * </B>
86 * <D id="3">
87 * <E>
88 * <E>
89 * </D>
90 * </A>
91 * </pre>
92 * <p>
93 * Data source creation
94 * <ul>
95 * <li>new JRXmlDataSource(document, "/A/B") - creates a data source with two nodes of type /A/B
96 * <li>new JRXmlDataSource(document, "/A/D") - creates a data source with two nodes of type /A/D
97 * </ul>
98 * Field selection
99 * <ul>
100 * <li>@id - will select the "id" attribute from the current node
101 * <li>C - will select the value of the first node of type C under the current node.
102 * </ul>
103 * Sub data source creation
104 * <ul>
105 * <li>"((net.sf.jasperreports.engine.data.JRXmlDataSource)$P{REPORT_DATA_SOURCE}).subDataSource("/B/C")
106 * - in the context of the node B, creates a data source with elements of type /B/C
107 * <li>"((net.sf.jasperreports.engine.data.JRXmlDataSource)$P{REPORT_DATA_SOURCE}).dataSource("/A/D")
108 * - creates a data source with elements of type /A/D
109 * </ul>
110 * </p>
111 * <p>
112 * Generally the full power of XPath expression is available. As an example, "/A/B[@id > 0"] will select all the
113 * nodes of type /A/B having the id greater than 0.
114 * You'll find a short XPath tutorial <a href="http://www.zvon.org/xxl/XPathTutorial/General/examples.html" target="_blank">here</a>.
115 *
116 * </p>
117 * <p>
118 * Note on performance. Due to the fact that all the XPath expression are interpreted the
119 * data source performance is not great. For the cases where more speed is required,
120 * consider implementing a custom data source that directly accesses the Document through the DOM API.
121 * </p>
122 * @author Peter Severin (peter_p_s@sourceforge.net, contact@jasperassistant.com)
123 * @see JRXPathExecuterUtils
124 */
125 public class JRXmlDataSource extends AbstractXmlDataSource<JRXmlDataSource>
126 {
127
128 // the xml document
129 private Document document;
130
131 // the XPath select expression that gives the nodes to iterate
132 private String selectExpression;
133
134 // the node list
135 private NodeList nodeList;
136
137 // the node list length
138 private int nodeListLength;
139
140 // the current node
141 private Node currentNode;
142
143 // current node index
144 private int currentNodeIndex = - 1;
145
146 private final JRXPathExecuter xPathExecuter;
147
148 private InputStream inputStream;
149 private boolean closeInputStream;
150
151 // -----------------------------------------------------------------
152 // Constructors
153
154 /**
155 * Creates the data source by parsing the xml document from the given file.
156 * The data source will contain exactly one record consisting of the document node itself.
157 *
158 * @param document the document
159 * @throws JRException if the data source cannot be created
160 */
161 public JRXmlDataSource(JasperReportsContext jasperReportsContext, Document document) throws JRException {
162 this(jasperReportsContext, document, ".");
163 }
164
165 /**
166 * @see #JRXmlDataSource(JasperReportsContext, Document)
167 */
168 public JRXmlDataSource(Document document) throws JRException {
169 this(DefaultJasperReportsContext.getInstance(), document);
170 }
171
172 /**
173 * Creates the data source by parsing the xml document from the given file.
174 * An additional XPath expression specifies the select criteria that produces the
175 * nodes (records) for the data source.
176 *
177 * @param document the document
178 * @param selectExpression the XPath select expression
179 * @throws JRException if the data source cannot be created
180 */
181 public JRXmlDataSource(
182 JasperReportsContext jasperReportsContext,
183 Document document,
184 String selectExpression
185 ) throws JRException
186 {
187 this.document = document;
188 this.selectExpression = selectExpression;
189
190 this.xPathExecuter = JRXPathExecuterUtils.getXPathExecuter(jasperReportsContext);
191
192 moveFirst();
193 }
194
195
196 /**
197 * @see #JRXmlDataSource(JasperReportsContext, Document, String)
198 */
199 public JRXmlDataSource(Document document, String selectExpression) throws JRException
200 {
201 this(DefaultJasperReportsContext.getInstance(), document, selectExpression);
202 }
203
204
205 /**
206 * Creates the data source by parsing the xml document from the given input stream.
207 *
208 * @param in the input stream
209 * @see JRXmlDataSource#JRXmlDataSource(Document)
210 */
211 public JRXmlDataSource(JasperReportsContext jasperReportsContext, InputStream in) throws JRException
212 {
213 this(jasperReportsContext, in, false);
214 }
215
216 public JRXmlDataSource(JasperReportsContext jasperReportsContext, InputStream in, boolean isNamespaceAware) throws JRException
217 {
218 this(jasperReportsContext, in, ".", isNamespaceAware);
219 }
220
221 /**
222 * @see #JRXmlDataSource(JasperReportsContext, InputStream)
223 */
224 public JRXmlDataSource(InputStream in) throws JRException {
225 this(in, false);
226 }
227
228 public JRXmlDataSource(InputStream in, boolean isNamespaceAware) throws JRException {
229 this(DefaultJasperReportsContext.getInstance(), in, isNamespaceAware);
230 }
231
232 /**
233 * Creates the data source by parsing the xml document from the given input stream.
234 *
235 * @see JRXmlDataSource#JRXmlDataSource(InputStream)
236 * @see JRXmlDataSource#JRXmlDataSource(Document, String)
237 */
238 public JRXmlDataSource(
239 JasperReportsContext jasperReportsContext,
240 InputStream in,
241 String selectExpression
242 ) throws JRException
243 {
244 this(jasperReportsContext, in, selectExpression, false);
245 }
246
247 public JRXmlDataSource(
248 JasperReportsContext jasperReportsContext,
249 InputStream in,
250 String selectExpression,
251 boolean isNamespaceAware
252 ) throws JRException
253 {
254 this(jasperReportsContext, JRXmlUtils.parse(new InputSource(in), isNamespaceAware), selectExpression);
255
256 this.inputStream = in;
257 this.closeInputStream = false;
258 }
259
260 /**
261 * @see #JRXmlDataSource(JasperReportsContext, InputStream, String)
262 */
263 public JRXmlDataSource(InputStream in, String selectExpression)
264 throws JRException {
265 this(in, selectExpression, false);
266 }
267
268 public JRXmlDataSource(InputStream in, String selectExpression, boolean isNamespaceAware)
269 throws JRException {
270 this(DefaultJasperReportsContext.getInstance(), in, selectExpression, isNamespaceAware);
271 }
272
273 /**
274 * Creates the data source by parsing the xml document from the given system identifier (URI).
275 * <p>If the system identifier is a URL, it must be full resolved.</p>
276 *
277 * @param uri the system identifier
278 * @see JRXmlDataSource#JRXmlDataSource(Document)
279 */
280 public JRXmlDataSource(JasperReportsContext jasperReportsContext, String uri) throws JRException {
281 this(jasperReportsContext, uri, false);
282 }
283
284 public JRXmlDataSource(JasperReportsContext jasperReportsContext, String uri, boolean isNamespaceAware) throws JRException {
285 this(jasperReportsContext, uri, ".", isNamespaceAware);
286 }
287
288 /**
289 * @see #JRXmlDataSource(JasperReportsContext, String)
290 */
291 public JRXmlDataSource(String uri) throws JRException {
292 this(uri, false);
293 }
294
295 public JRXmlDataSource(String uri, boolean isNamespaceAware) throws JRException {
296 this(DefaultJasperReportsContext.getInstance(), uri, isNamespaceAware);
297 }
298
299 /**
300 * Creates the data source by parsing the xml document from the given system identifier (URI).
301 *
302 * @see JRXmlDataSource#JRXmlDataSource(String)
303 * @see JRXmlDataSource#JRXmlDataSource(Document, String)
304 */
305 public JRXmlDataSource(
306 JasperReportsContext jasperReportsContext,
307 String uri,
308 String selectExpression
309 ) throws JRException
310 {
311 this(jasperReportsContext, uri, selectExpression, false);
312 }
313
314 public JRXmlDataSource(
315 JasperReportsContext jasperReportsContext,
316 String uri,
317 String selectExpression,
318 boolean isNamespaceAware
319 ) throws JRException
320 {
321 this(SimpleRepositoryContext.of(jasperReportsContext), uri, selectExpression, isNamespaceAware);
322 }
323
324 public JRXmlDataSource(
325 RepositoryContext context,
326 String uri,
327 String selectExpression,
328 boolean isNamespaceAware
329 ) throws JRException
330 {
331 this(
332 context.getJasperReportsContext(),
333 RepositoryUtil.getInstance(context).getInputStreamFromLocation(uri),
334 selectExpression,
335 isNamespaceAware
336 );
337 this.closeInputStream = true;//FIXME close the stream immediately
338 }
339
340 /**
341 * @see #JRXmlDataSource(JasperReportsContext, String, String)
342 */
343 public JRXmlDataSource(String uri, String selectExpression)
344 throws JRException {
345 this(uri, selectExpression, false);
346 }
347
348 public JRXmlDataSource(String uri, String selectExpression, boolean isNamespaceAware)
349 throws JRException {
350 this(DefaultJasperReportsContext.getInstance(), uri, selectExpression, isNamespaceAware);
351 }
352
353 /**
354 * Creates the data source by parsing the xml document from the given file.
355 *
356 * @param file the file
357 * @see JRXmlDataSource#JRXmlDataSource(Document)
358 */
359 public JRXmlDataSource(JasperReportsContext jasperReportsContext, File file) throws JRException {
360 this(jasperReportsContext, file, false);
361 }
362
363 public JRXmlDataSource(JasperReportsContext jasperReportsContext, File file, boolean isNamespaceAware) throws JRException {
364 this(jasperReportsContext, file, ".", isNamespaceAware);
365 }
366
367 /**
368 * @see #JRXmlDataSource(JasperReportsContext, File)
369 */
370 public JRXmlDataSource(File file) throws JRException {
371 this(file, false);
372 }
373
374 public JRXmlDataSource(File file, boolean isNamespaceAware) throws JRException {
375 this(DefaultJasperReportsContext.getInstance(), file, isNamespaceAware);
376 }
377
378 /**
379 * Creates the data source by parsing the xml document from the given file.
380 *
381 * @see JRXmlDataSource#JRXmlDataSource(File)
382 * @see JRXmlDataSource#JRXmlDataSource(Document, String)
383 */
384 public JRXmlDataSource(JasperReportsContext jasperReportsContext, File file, String selectExpression)
385 throws JRException {
386 this(jasperReportsContext, file, selectExpression, false);
387 }
388
389 public JRXmlDataSource(JasperReportsContext jasperReportsContext, File file, String selectExpression, boolean isNamespaceAware)
390 throws JRException {
391 this(jasperReportsContext, JRXmlUtils.parse(file, isNamespaceAware), selectExpression);
392 }
393
394 /**
395 * @see #JRXmlDataSource(JasperReportsContext, File, String)
396 */
397 public JRXmlDataSource(File file, String selectExpression)
398 throws JRException {
399 this(file, selectExpression, false);
400 }
401
402 public JRXmlDataSource(File file, String selectExpression, boolean isNamespaceAware)
403 throws JRException {
404 this(DefaultJasperReportsContext.getInstance(), file, selectExpression, isNamespaceAware);
405 }
406
407 // -----------------------------------------------------------------
408 // Implementation
409
410 /*
411 * (non-Javadoc)
412 *
413 * @see net.sf.jasperreports.engine.JRRewindableDataSource#moveFirst()
414 */
415 @Override
416 public void moveFirst() throws JRException {
417 if (document == null)
418 {
419 throw
420 new JRException(
421 EXCEPTION_MESSAGE_KEY_NULL_DOCUMENT,
422 (Object[])null);
423 }
424 if (selectExpression == null)
425 {
426 throw
427 new JRException(
428 EXCEPTION_MESSAGE_KEY_NULL_SELECT_EXPRESSION,
429 (Object[])null);
430 }
431
432 currentNode = null;
433 currentNodeIndex = -1;
434 nodeListLength = 0;
435 nodeList = xPathExecuter.selectNodeList(document,
436 selectExpression);
437 nodeListLength = nodeList.getLength();
438 }
439
440 /*
441 * (non-Javadoc)
442 *
443 * @see net.sf.jasperreports.engine.JRDataSource#next()
444 */
445 @Override
446 public boolean next()
447 {
448 if(currentNodeIndex == nodeListLength - 1)
449 {
450 return false;
451 }
452 currentNode = nodeList.item(++ currentNodeIndex);
453 return true;
454 }
455
456 @Override
457 public int recordCount()
458 {
459 return nodeListLength;
460 }
461
462 @Override
463 public int currentIndex()
464 {
465 return currentNodeIndex;
466 }
467
468 @Override
469 public void moveToRecord(int index) throws NoRecordAtIndexException
470 {
471 if (index >= 0 && index < nodeListLength)
472 {
473 currentNodeIndex = index;
474 currentNode = nodeList.item(index);
475 }
476 else
477 {
478 throw new NoRecordAtIndexException(index);
479 }
480 }
481
482 @Override
483 public Node getCurrentNode()
484 {
485 return currentNode;
486 }
487
488 @Override
489 public Object getSelectObject(Node currentNode, String expression) throws JRException
490 {
491 return xPathExecuter.selectObject(currentNode, expression);
492 }
493
494 /**
495 * Creates a sub data source using the current node (record) as the root
496 * of the document. An additional XPath expression specifies the select criteria applied to
497 * this new document and that produces the nodes (records) for the data source.
498 *
499 * @param selectExpr the XPath select expression
500 * @return the xml sub data source
501 * @throws JRException if the sub data source couldn't be created
502 * @see JRXmlDataSource#JRXmlDataSource(Document, String)
503 */
504 @Override
505 public JRXmlDataSource subDataSource(String selectExpr)
506 throws JRException {
507 // create a new document from the current node
508 Document doc = subDocument();
509 JRXmlDataSource subDataSource = new JRXmlDataSource(doc, selectExpr);
510 subDataSource.setTextAttributes(this);
511 return subDataSource;
512 }
513
514 @Override
515 public JRXmlDataSource subDataSource() throws JRException // need to override this method here to keep binary compatibility with older releases
516 {
517 return super.subDataSource();
518 }
519
520
521 /**
522 * Creates a document using the current node as root.
523 *
524 * @return a document having the current node as root
525 * @throws JRException
526 */
527 @Override
528 public Document subDocument() throws JRException
529 {
530 if(currentNode == null)
531 {
532 throw
533 new JRException(
534 EXCEPTION_MESSAGE_KEY_NODE_NOT_AVAILABLE,
535 (Object[])null);
536 }
537
538 // create a new document from the current node
539 return JRXmlUtils.createDocument(currentNode);
540 }
541
542
543 /**
544 * Creates a sub data source using as root document the document used by "this" data source.
545 * An additional XPath expression specifies the select criteria applied to
546 * this document and that produces the nodes (records) for the data source.
547 *
548 * @param selectExpr the XPath select expression
549 * @return the xml sub data source
550 * @throws JRException if the sub data source couldn't be created
551 * @see JRXmlDataSource#JRXmlDataSource(Document, String)
552 */
553 @Override
554 public JRXmlDataSource dataSource(String selectExpr)
555 throws JRException {
556 JRXmlDataSource subDataSource = new JRXmlDataSource(document, selectExpr);
557 subDataSource.setTextAttributes(this);
558 return subDataSource;
559 }
560
561 @Override
562 public JRXmlDataSource dataSource() throws JRException // need to override this method here to keep binary compatibility with older releases
563 {
564 return super.dataSource();
565 }
566
567 /**
568 * Closes the reader. Users of this data source should close it after usage.
569 */
570 public void close()
571 {
572 try
573 {
574 if (closeInputStream)
575 {
576 inputStream.close();
577 }
578 }
579 catch(IOException e)
580 {
581 //nothing to do
582 }
583 }
584
585 }
586