KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > net > sf > jasperreports > engine > data > JRXmlDataSource


1 /*
2  * ============================================================================
3  * GNU Lesser General Public License
4  * ============================================================================
5  *
6  * JasperReports - Free Java report-generating library.
7  * Copyright (C) 2001-2006 JasperSoft Corporation http://www.jaspersoft.com
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public
11  * License as published by the Free Software Foundation; either
12  * version 2.1 of the License, or (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with this library; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
22  *
23  * JasperSoft Corporation
24  * 303 Second Street, Suite 450 North
25  * San Francisco, CA 94107
26  * http://www.jaspersoft.com
27  */

28
29 /*
30  * Contributors:
31  * Tim Thomas - tthomas48@users.sourceforge.net
32  */

33 package net.sf.jasperreports.engine.data;
34
35 import java.io.File JavaDoc;
36 import java.io.FileInputStream JavaDoc;
37 import java.io.InputStream JavaDoc;
38 import java.util.Date JavaDoc;
39 import java.util.Locale JavaDoc;
40 import java.util.TimeZone JavaDoc;
41
42 import javax.xml.transform.TransformerException JavaDoc;
43
44 import net.sf.jasperreports.engine.JRException;
45 import net.sf.jasperreports.engine.JRField;
46 import net.sf.jasperreports.engine.JRRewindableDataSource;
47 import net.sf.jasperreports.engine.design.JRDesignField;
48 import net.sf.jasperreports.engine.util.JRDateLocaleConverter;
49 import net.sf.jasperreports.engine.util.JRXmlUtils;
50
51 import org.apache.commons.beanutils.locale.LocaleConvertUtilsBean;
52 import org.apache.xpath.CachedXPathAPI;
53 import org.apache.xpath.objects.XObject;
54 import org.w3c.dom.Document JavaDoc;
55 import org.w3c.dom.Node JavaDoc;
56 import org.w3c.dom.NodeList JavaDoc;
57 import org.xml.sax.InputSource JavaDoc;
58
59 /**
60  * XML data source implementation that allows to access the data from a xml
61  * document using XPath expressions.
62  * <p>
63  * The data source is constructed around a node set (record set) selected
64  * by an XPath expression from the xml document.
65  * </p>
66  * <p>
67  * Each field can provide an additional XPath expresion that will be used to
68  * select its value. This expression must be specified using the "fieldDescription"
69  * element of the field. The expression is evaluated in the context of the current
70  * node thus the expression should be relative to the current node.
71  * </p>
72  * <p>
73  * To support subreports, sub data sources can be created. There are two different methods
74  * for creating sub data sources. The first one allows to create a sub data source rooted
75  * at the current node. The current node can be seen as a new document around which the
76  * sub data source is created. The second method allows to create a sub data source that
77  * is rooted at the same document that is used by the data source but uses a different
78  * XPath select expression.
79  * </p>
80  * <p>
81  * Example:
82  * <pre>
83  * &lt;A&gt;
84  * &lt;B id="0"&gt;
85  * &lt;C&gt;
86  * &lt;C&gt;
87  * &lt;/B&gt;
88  * &lt;B id="1"&gt;
89  * &lt;C&gt;
90  * &lt;C&gt;
91  * &lt;/B&gt;
92  * &lt;D id="3"&gt;
93  * &lt;E&gt;
94  * &lt;E&gt;
95  * &lt;/D&gt;
96  * &lt;/A&gt;
97  * </pre>
98  * <p>
99  * Data source creation
100  * <ul>
101  * <li>new JRXmlDataSource(document, "/A/B") - creates a data source with two nodes of type /A/B
102  * <li>new JRXmlDataSource(document, "/A/D") - creates a data source with two nodes of type /A/D
103  * </ul>
104  * Field selection
105  * <ul>
106  * <li>@id - will select the "id" attribute from the current node
107  * <li>C - will select the value of the first node of type C under the current node.
108  * </ul>
109  * Sub data source creation
110  * <ul>
111  * <li>"((net.sf.jasperreports.engine.data.JRXmlDataSource)$P{REPORT_DATA_SOURCE}).subDataSource("/B/C")
112  * - in the context of the node B, creates a data source with elements of type /B/C
113  * <li>"((net.sf.jasperreports.engine.data.JRXmlDataSource)$P{REPORT_DATA_SOURCE}).dataSource("/A/D")
114  * - creates a data source with elements of type /A/D
115  * </ul>
116  * </p>
117  * <p>
118  * Generally the full power of XPath expression is available. As an example, "/A/B[@id > 0"] will select all the
119  * nodes of type /A/B having the id greater than 0.
120  * You'll find a short XPath tutorial <a HREF="http://www.zvon.org/xxl/XPathTutorial/General/examples.html" target="_blank">here</a>.
121  *
122  * </p>
123  * <p>
124  * Note on performance. Due to the fact that all the XPath expression are interpreted the
125  * data source performance is not great. For the cases where more speed is required,
126  * consider implementing a custom data source that directly accesses the Document through the DOM API.
127  * </p>
128  * @author Peter Severin (peter_p_s@sourceforge.net, contact@jasperassistant.com)
129  * @version $Id: JRXmlDataSource.java 1538 2006-12-22 17:04:25 +0200 (Fri, 22 Dec 2006) teodord $
130  */

131 public class JRXmlDataSource implements JRRewindableDataSource {
132
133     // the xml document
134
private Document JavaDoc document;
135
136     // the XPath select expression that gives the nodes to iterate
137
private String JavaDoc selectExpression;
138
139     // the node list
140
private NodeList JavaDoc nodeList;
141
142     // the node list length
143
private int nodeListLength;
144     
145     // the current node
146
private Node JavaDoc currentNode;
147
148     // current node index
149
private int currentNodeIndex = - 1;
150
151     // XPath API fa?ade
152
private CachedXPathAPI xpathAPI = new CachedXPathAPI();
153     
154     private LocaleConvertUtilsBean convertBean = null;
155     
156     private Locale JavaDoc locale = null;
157     private String JavaDoc datePattern = null;
158     private String JavaDoc numberPattern = null;
159     private TimeZone JavaDoc timeZone = null;
160     
161     // -----------------------------------------------------------------
162
// Constructors
163

164     /**
165      * Creates the data source by parsing the xml document from the given file.
166      * The data source will contain exactly one record consisting of the document node itself.
167      *
168      * @param document the document
169      * @throws JRException if the data source cannot be created
170      */

171     public JRXmlDataSource(Document JavaDoc document) throws JRException {
172         this(document, ".");
173     }
174
175     /**
176      * Creates the data source by parsing the xml document from the given file.
177      * An additional XPath expression specifies the select criteria that produces the
178      * nodes (records) for the data source.
179      *
180      * @param document the document
181      * @param selectExpression the XPath select expression
182      * @throws JRException if the data source cannot be created
183      */

184     public JRXmlDataSource(Document JavaDoc document, String JavaDoc selectExpression)
185             throws JRException {
186         this.document = document;
187         this.selectExpression = selectExpression;
188         moveFirst();
189     }
190
191
192     /**
193      * Creates the data source by parsing the xml document from the given input stream.
194      *
195      * @param in the input stream
196      * @see JRXmlDataSource#JRXmlDataSource(Document)
197      */

198     public JRXmlDataSource(InputStream JavaDoc in) throws JRException {
199         this(in, ".");
200     }
201
202     /**
203      * Creates the data source by parsing the xml document from the given input stream.
204      *
205      * @see JRXmlDataSource#JRXmlDataSource(InputStream)
206      * @see JRXmlDataSource#JRXmlDataSource(Document, String)
207      */

208     public JRXmlDataSource(InputStream JavaDoc in, String JavaDoc selectExpression)
209             throws JRException {
210         this(JRXmlUtils.parse(new InputSource JavaDoc(in)), selectExpression);
211     }
212
213     /**
214      * Creates the data source by parsing the xml document from the given system identifier (URI).
215      * <p>If the system identifier is a URL, it must be full resolved.</p>
216      *
217      * @param uri the system identifier
218      * @see JRXmlDataSource#JRXmlDataSource(Document)
219      */

220     public JRXmlDataSource(String JavaDoc uri) throws JRException {
221         this(uri, ".");
222     }
223
224     /**
225      * Creates the data source by parsing the xml document from the given system identifier (URI).
226      *
227      * @see JRXmlDataSource#JRXmlDataSource(String)
228      * @see JRXmlDataSource#JRXmlDataSource(Document, String)
229      */

230     public JRXmlDataSource(String JavaDoc uri, String JavaDoc selectExpression)
231             throws JRException {
232         this(JRXmlUtils.parse(uri), selectExpression);
233     }
234
235     /**
236      * Creates the data source by parsing the xml document from the given file.
237      *
238      * @param file the file
239      * @see JRXmlDataSource#JRXmlDataSource(Document)
240      */

241     public JRXmlDataSource(File JavaDoc file) throws JRException {
242         this(file, ".");
243     }
244
245     /**
246      * Creates the data source by parsing the xml document from the given file.
247      *
248      * @see JRXmlDataSource#JRXmlDataSource(File)
249      * @see JRXmlDataSource#JRXmlDataSource(Document, String)
250      */

251     public JRXmlDataSource(File JavaDoc file, String JavaDoc selectExpression)
252             throws JRException {
253         this(JRXmlUtils.parse(file), selectExpression);
254     }
255     
256     // -----------------------------------------------------------------
257
// Implementation
258

259     /*
260      * (non-Javadoc)
261      *
262      * @see net.sf.jasperreports.engine.JRRewindableDataSource#moveFirst()
263      */

264     public void moveFirst() throws JRException {
265         if (document == null)
266             throw new JRException("document cannot be null");
267         if (selectExpression == null)
268             throw new JRException("selectExpression cannot be null");
269
270         try {
271             currentNode = null;
272             currentNodeIndex = -1;
273             nodeListLength = 0;
274             nodeList = xpathAPI.selectNodeList(document,
275                     selectExpression);
276             nodeListLength = nodeList.getLength();
277         } catch (TransformerException JavaDoc e) {
278             throw new JRException("XPath selection failed. Expression: "
279                     + selectExpression, e);
280         }
281     }
282
283     /*
284      * (non-Javadoc)
285      *
286      * @see net.sf.jasperreports.engine.JRDataSource#next()
287      */

288     public boolean next() {
289         if(currentNodeIndex == nodeListLength - 1)
290             return false;
291
292         currentNode = nodeList.item(++ currentNodeIndex);
293         return true;
294     }
295
296     /*
297      * (non-Javadoc)
298      *
299      * @see net.sf.jasperreports.engine.JRDataSource#getFieldValue(net.sf.jasperreports.engine.JRField)
300      */

301     public Object JavaDoc getFieldValue(JRField jrField) throws JRException {
302         if(currentNode == null)
303             return null;
304         
305         String JavaDoc expression = jrField.getDescription();
306         if (expression == null || expression.length() == 0)
307             return null;
308
309         Object JavaDoc value = null;
310         
311         Class JavaDoc valueClass = jrField.getValueClass();
312         
313         if(Object JavaDoc.class != valueClass) {
314             String JavaDoc text = null;
315             
316             try {
317                 XObject list = xpathAPI.eval(currentNode, expression);
318                 if (list.getType() == XObject.CLASS_NODESET) {
319                     Node JavaDoc node = list.nodeset().nextNode();
320                     if (node != null) {
321                         text = getText(node);
322                     }
323                 } else {
324                     text = list.str();
325                 }
326             } catch (TransformerException JavaDoc e) {
327                 throw new JRException("XPath selection failed. Expression: "
328                         + expression, e);
329             }
330             
331             if(text != null) {
332                 if (String JavaDoc.class.equals(valueClass))
333                 {
334                     value = text;
335                 }
336                 else if (Number JavaDoc.class.isAssignableFrom(valueClass))
337                 {
338                     value = getConvertBean().convert(text.trim(), valueClass, locale, numberPattern);
339                 }
340                 else if (Date JavaDoc.class.isAssignableFrom(valueClass))
341                 {
342                     value = getConvertBean().convert(text.trim(), valueClass, locale, datePattern);
343                 }
344                     
345             }
346         }
347         return value;
348     }
349
350     /**
351      * Creates a sub data source using the current node (record) as the root
352      * of the document. An additional XPath expression specifies the select criteria applied to
353      * this new document and that produces the nodes (records) for the data source.
354      *
355      * @param selectExpr the XPath select expression
356      * @return the xml sub data source
357      * @throws JRException if the sub data source couldn't be created
358      * @see JRXmlDataSource#JRXmlDataSource(Document, String)
359      */

360     public JRXmlDataSource subDataSource(String JavaDoc selectExpr)
361             throws JRException {
362         // create a new document from the current node
363
Document JavaDoc doc = subDocument();
364         JRXmlDataSource subDataSource = new JRXmlDataSource(doc, selectExpr);
365         subDataSource.setLocale(locale);
366         subDataSource.setDatePattern(datePattern);
367         subDataSource.setNumberPattern(numberPattern);
368         subDataSource.setTimeZone(timeZone);
369         return subDataSource;
370     }
371
372     /**
373      * Creates a sub data source using the current node (record) as the root
374      * of the document. The data source will contain exactly one record consisting
375      * of the document node itself.
376      *
377      * @return the xml sub data source
378      * @throws JRException if the data source cannot be created
379      * @see JRXmlDataSource#subDataSource(String)
380      * @see JRXmlDataSource#JRXmlDataSource(Document)
381      */

382     public JRXmlDataSource subDataSource() throws JRException {
383         return subDataSource(".");
384     }
385
386     
387     /**
388      * Creates a document using the current node as root.
389      *
390      * @return a document having the current node as root
391      * @throws JRException
392      */

393     public Document JavaDoc subDocument() throws JRException
394     {
395         if(currentNode == null)
396         {
397             throw new JRException("No node available. Iterate or rewind the data source.");
398         }
399         
400         // create a new document from the current node
401
Document JavaDoc doc = JRXmlUtils.createDocument(currentNode);
402         return doc;
403     }
404     
405     
406     /**
407      * Creates a sub data source using as root document the document used by "this" data source.
408      * An additional XPath expression specifies the select criteria applied to
409      * this document and that produces the nodes (records) for the data source.
410      *
411      * @param selectExpr the XPath select expression
412      * @return the xml sub data source
413      * @throws JRException if the sub data source couldn't be created
414      * @see JRXmlDataSource#JRXmlDataSource(Document, String)
415      */

416     public JRXmlDataSource dataSource(String JavaDoc selectExpr)
417             throws JRException {
418         JRXmlDataSource subDataSource = new JRXmlDataSource(document, selectExpr);
419         subDataSource.setLocale(locale);
420         subDataSource.setDatePattern(datePattern);
421         subDataSource.setNumberPattern(numberPattern);
422         subDataSource.setTimeZone(timeZone);
423         return subDataSource;
424     }
425
426     /**
427      * Creates a sub data source using as root document the document used by "this" data source.
428      * The data source will contain exactly one record consisting of the document node itself.
429      *
430      * @return the xml sub data source
431      * @throws JRException if the data source cannot be created
432      * @see JRXmlDataSource#dataSource(String)
433      * @see JRXmlDataSource#JRXmlDataSource(Document)
434      */

435     public JRXmlDataSource dataSource() throws JRException {
436         return dataSource(".");
437     }
438
439     /**
440      * Return the text that a node contains. This routine:
441      * <ul>
442      * <li>Ignores comments and processing instructions.
443      * <li>Concatenates TEXT nodes, CDATA nodes, and the results of recursively
444      * processing EntityRef nodes.
445      * <li>Ignores any element nodes in the sublist. (Other possible options
446      * are to recurse into element sublists or throw an exception.)
447      * </ul>
448      *
449      * @param node a DOM node
450      * @return a String representing node contents or null
451      */

452     public String JavaDoc getText(Node JavaDoc node) {
453         if (!node.hasChildNodes())
454             return node.getNodeValue();
455
456         StringBuffer JavaDoc result = new StringBuffer JavaDoc();
457
458         NodeList JavaDoc list = node.getChildNodes();
459         for (int i = 0; i < list.getLength(); i++) {
460             Node JavaDoc subnode = list.item(i);
461             if (subnode.getNodeType() == Node.TEXT_NODE) {
462                 String JavaDoc value = subnode.getNodeValue();
463                 if(value != null)
464                     result.append(value);
465             } else if (subnode.getNodeType() == Node.CDATA_SECTION_NODE) {
466                 String JavaDoc value = subnode.getNodeValue();
467                 if(value != null)
468                     result.append(value);
469             } else if (subnode.getNodeType() == Node.ENTITY_REFERENCE_NODE) {
470                 // Recurse into the subtree for text
471
// (and ignore comments)
472
String JavaDoc value = getText(subnode);
473                 if(value != null)
474                     result.append(value);
475             }
476         }
477
478         return result.toString();
479     }
480     
481     public static void main(String JavaDoc[] args) throws Exception JavaDoc {
482         JRXmlDataSource ds = new JRXmlDataSource(new FileInputStream JavaDoc("northwind.xml"), "/Northwind/Customers");
483         JRDesignField field = new JRDesignField();
484         field.setDescription("CustomerID");
485         field.setValueClass(String JavaDoc.class);
486         
487         ds.next();
488         String JavaDoc v = (String JavaDoc) ds.getFieldValue(field);
489         System.out.println(field.getDescription() + "=" + v);
490         
491         JRXmlDataSource subDs = ds.dataSource("/Northwind/Orders");
492
493         JRDesignField field1 = new JRDesignField();
494         field1.setDescription("OrderID");
495         field1.setValueClass(String JavaDoc.class);
496         
497         subDs.next();
498         String JavaDoc v1 = (String JavaDoc) subDs.getFieldValue(field1);
499         System.out.println(field1.getDescription() + "=" + v1);
500         
501     }
502
503     protected LocaleConvertUtilsBean getConvertBean()
504     {
505         if (convertBean == null)
506         {
507             convertBean = new LocaleConvertUtilsBean();
508             if (locale != null)
509             {
510                 convertBean.setDefaultLocale(locale);
511                 convertBean.deregister();
512                 //convertBean.lookup();
513
}
514             convertBean.register(
515                 new JRDateLocaleConverter(timeZone),
516                 java.util.Date JavaDoc.class,
517                 locale
518                 );
519         }
520         return convertBean;
521     }
522
523     public Locale JavaDoc getLocale() {
524         return locale;
525     }
526
527     public void setLocale(Locale JavaDoc locale) {
528         this.locale = locale;
529         convertBean = null;
530     }
531
532     public String JavaDoc getDatePattern() {
533         return datePattern;
534     }
535
536     public void setDatePattern(String JavaDoc datePattern) {
537         this.datePattern = datePattern;
538         convertBean = null;
539     }
540
541     public String JavaDoc getNumberPattern() {
542         return numberPattern;
543     }
544
545     public void setNumberPattern(String JavaDoc numberPattern) {
546         this.numberPattern = numberPattern;
547         convertBean = null;
548     }
549
550     public TimeZone JavaDoc getTimeZone() {
551         return timeZone;
552     }
553
554     public void setTimeZone(TimeZone JavaDoc timeZone) {
555         this.timeZone = timeZone;
556         convertBean = null;
557     }
558
559 }
560
Popular Tags