ReportGenerator: Add a header band to show the field titles.
[online-glom:gwt-glom.git] / src / main / java / org / glom / web / server / ReportGenerator.java
1 /*
2  * Copyright (C) 2012 Openismus GmbH
3  *
4  * This file is part of GWT-Glom.
5  *
6  * GWT-Glom is free software: you can redistribute it and/or modify it
7  * under the terms of the GNU Lesser General Public License as published by the
8  * Free Software Foundation, either version 3 of the License, or (at your
9  * option) any later version.
10  *
11  * GWT-Glom is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
14  * for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with GWT-Glom.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 package org.glom.web.server;
21
22 import java.io.ByteArrayOutputStream;
23 import java.sql.Connection;
24 import java.util.HashMap;
25
26 import net.sf.jasperreports.engine.JRException;
27 import net.sf.jasperreports.engine.JasperCompileManager;
28 import net.sf.jasperreports.engine.JasperFillManager;
29 import net.sf.jasperreports.engine.JasperPrint;
30 import net.sf.jasperreports.engine.JasperReport;
31 import net.sf.jasperreports.engine.design.JRDesignBand;
32 import net.sf.jasperreports.engine.design.JRDesignExpression;
33 import net.sf.jasperreports.engine.design.JRDesignField;
34 import net.sf.jasperreports.engine.design.JRDesignGroup;
35 import net.sf.jasperreports.engine.design.JRDesignQuery;
36 import net.sf.jasperreports.engine.design.JRDesignSection;
37 import net.sf.jasperreports.engine.design.JRDesignStaticText;
38 import net.sf.jasperreports.engine.design.JRDesignTextField;
39 import net.sf.jasperreports.engine.design.JasperDesign;
40 import net.sf.jasperreports.engine.export.JRHtmlExporterParameter;
41 import net.sf.jasperreports.engine.export.JRXhtmlExporter;
42
43 import org.apache.commons.lang.StringUtils;
44 import org.glom.libglom.Document;
45 import org.glom.libglom.Glom;
46 import org.glom.libglom.LayoutFieldVector;
47 import org.glom.libglom.LayoutGroup;
48 import org.glom.libglom.LayoutItemVector;
49 import org.glom.libglom.LayoutItem_Field;
50 import org.glom.libglom.LayoutItem_GroupBy;
51 import org.glom.libglom.Relationship;
52 import org.glom.libglom.SortClause;
53 import org.glom.libglom.SortFieldPair;
54 import org.glom.libglom.SqlBuilder;
55 import org.glom.libglom.SqlExpr;
56 import org.glom.libglom.Value;
57
58 /**
59  * @author Murray Cumming <murrayc@openimus.com>
60  * 
61  */
62 public class ReportGenerator {
63
64         final int height = 30;
65         LayoutFieldVector fieldsToGet = new LayoutFieldVector();
66         SortClause sortClause = new SortClause();
67         String localeID;
68
69         ReportGenerator(final String localeID) {
70                 this.localeID = StringUtils.defaultString(localeID);
71         }
72
73         /**
74          * @param tableName
75          * @param reportName
76          * @param configuredDoc
77          * @param layout_group
78          * @return
79          */
80         public String generateReport(final Document document, final String tableName, final String reportName,
81                         final Connection connection, final org.glom.libglom.LayoutGroup layout_group) {
82
83                 final JasperDesign design = new JasperDesign();
84                 design.setName(reportName); // TODO: Actually, we want the title.
85
86                 final JRDesignBand titleBand = new JRDesignBand();
87                 titleBand.setHeight(height);
88                 final JRDesignStaticText staticTitle = new JRDesignStaticText();
89                 staticTitle.setText("debug: test report title text");
90                 titleBand.addElement(staticTitle);
91                 design.setTitle(titleBand);
92
93                 final JRDesignBand detailBand = new JRDesignBand();
94                 detailBand.setHeight(height + 20);
95
96                 final JRDesignBand headerBand = new JRDesignBand();
97                 headerBand.setHeight(height + 20);
98
99                 fieldsToGet = new LayoutFieldVector();
100                 final int x = 0;
101                 addToReport(layout_group, design, detailBand, headerBand, x);
102
103                 design.setColumnHeader(headerBand);
104                 ((JRDesignSection) design.getDetailSection()).addBand(detailBand);
105
106                 // Later versions of libglom actually return an empty SqlExpr when quickFindValue is empty,
107                 // but let's be sure:
108                 final String quickFind = ""; // TODO
109                 SqlExpr whereClause;
110                 if (StringUtils.isEmpty(quickFind)) {
111                         whereClause = new SqlExpr();
112                 } else {
113                         final Value quickFindValue = new Value(quickFind);
114                         whereClause = Glom.get_find_where_clause_quick(document, tableName, quickFindValue);
115                 }
116
117                 final Relationship extraJoin = new Relationship(); // Ignored.
118                 final SqlBuilder builder = Glom.build_sql_select_with_where_clause(tableName, fieldsToGet, whereClause,
119                                 extraJoin, sortClause);
120                 final String sqlQuery = Glom.sqlbuilder_get_full_query(builder);
121
122                 final JRDesignQuery query = new JRDesignQuery();
123                 query.setText(sqlQuery); // TODO: Extra sort clause to sort the rows within the groups.
124                 design.setQuery(query);
125
126                 JasperReport report;
127                 try {
128                         report = JasperCompileManager.compileReport(design);
129                 } catch (final JRException ex) {
130                         ex.printStackTrace();
131                         return "Failed to Generate HTML: compileReport() failed.";
132                 }
133
134                 JasperPrint print;
135                 try {
136                         final HashMap<String, Object> parameters = new HashMap<String, Object>();
137                         parameters.put("ReportTitle", reportName); // TODO: Use the title, not the name.
138                         print = JasperFillManager.fillReport(report, parameters, connection);
139                 } catch (final JRException ex) {
140                         ex.printStackTrace();
141                         return "Failed to Generate HTML: fillReport() failed.";
142                 }
143
144                 final ByteArrayOutputStream output = new ByteArrayOutputStream();
145
146                 // We use this because there is no JasperExportManager.exportReportToHtmlStream() method.
147                 // JasperExportManager.exportReportToXmlStream(print, output);
148                 try {
149                         final JRXhtmlExporter exporter = new JRXhtmlExporter();
150                         exporter.setParameter(JRHtmlExporterParameter.JASPER_PRINT, print);
151                         exporter.setParameter(JRHtmlExporterParameter.OUTPUT_STREAM, output);
152
153                         // Use points instead of pixels for sizes, because pixels are silly
154                         // in HTML:
155                         exporter.setParameter(JRHtmlExporterParameter.SIZE_UNIT, "pt");
156
157                         exporter.exportReport();
158                 } catch (final JRException ex) {
159                         ex.printStackTrace();
160                         return "Failed to Generate HTML: exportReport() failed.";
161                 }
162
163                 // System.out.print(output.toString() + "\n");
164                 return output.toString();
165         }
166
167         /**
168          * @param layout_group
169          * @param design
170          * @param height
171          * @param parentBand
172          * @param x
173          */
174         private int addToReport(final org.glom.libglom.LayoutGroup layout_group, final JasperDesign design,
175                         final JRDesignBand parentBand, final JRDesignBand headerBand, int x) {
176                 final LayoutItemVector layoutItemsVec = layout_group.get_items();
177                 final int numItems = Utils.safeLongToInt(layoutItemsVec.size());
178                 for (int i = 0; i < numItems; i++) {
179                         final org.glom.libglom.LayoutItem libglomLayoutItem = layoutItemsVec.get(i);
180
181                         final LayoutGroup libglomLayoutGroup = LayoutGroup.cast_dynamic(libglomLayoutItem);
182                         final LayoutItem_Field libglomLayoutItemField = LayoutItem_Field.cast_dynamic(libglomLayoutItem);
183                         if (libglomLayoutItemField != null) {
184                                 x = addFieldToBand(design, parentBand, headerBand, x, libglomLayoutItemField);
185                         } else if (libglomLayoutGroup != null) {
186                                 final LayoutItem_GroupBy libglomGroupBy = LayoutItem_GroupBy.cast_dynamic(libglomLayoutGroup);
187                                 if (libglomGroupBy != null) {
188                                         final LayoutItem_Field fieldGroupBy = libglomGroupBy.get_field_group_by();
189                                         if (fieldGroupBy == null)
190                                                 continue;
191
192                                         final String fieldName = addField(design, fieldGroupBy);
193
194                                         // We must sort by the group field,
195                                         // so that JasperReports can start a new group when its value changes.
196                                         // Note that this is not like a SQL GROUP BY.
197                                         final SortFieldPair pair = new SortFieldPair();
198                                         pair.setFirst(fieldGroupBy);
199                                         pair.setSecond(true); // Ascending.
200                                         sortClause.add(pair);
201
202                                         final JRDesignGroup group = new JRDesignGroup();
203                                         group.setName(fieldName);
204
205                                         // Show the field value:
206                                         final JRDesignExpression expression = new JRDesignExpression();
207                                         expression.setText("$F{" + fieldName + "}");
208                                         group.setExpression(expression);
209
210                                         final JRDesignBand groupBand = new JRDesignBand();
211                                         groupBand.setHeight(height);
212                                         ((JRDesignSection) group.getGroupHeaderSection()).addBand(groupBand);
213
214                                         final JRDesignBand footerBand = new JRDesignBand();
215                                         footerBand.setHeight(height);
216                                         ((JRDesignSection) group.getGroupFooterSection()).addBand(footerBand);
217
218                                         try {
219                                                 design.addGroup(group);
220                                         } catch (final JRException e) {
221                                                 // TODO Auto-generated catch block
222                                                 e.printStackTrace();
223                                         }
224
225                                         // Show the group-by field:
226                                         final int groupX = addFieldToBand(design, groupBand, headerBand, x, fieldGroupBy);
227
228                                         // Show the secondary fields:
229                                         final LayoutGroup groupSecondaries = libglomGroupBy.get_group_secondary_fields();
230                                         if (groupSecondaries != null)
231                                                 addToReport(groupSecondaries, design, groupBand, headerBand, groupX);
232                                 }
233
234                                 // Recurse into sub-groups:
235                                 x = addToReport(libglomLayoutGroup, design, parentBand, headerBand, x);
236                         }
237                 }
238
239                 return x;
240         }
241
242         /**
243          * @param design
244          * @param parentBand
245          * @param x
246          * @param libglomLayoutItemField
247          * @return
248          */
249         private int addFieldToBand(final JasperDesign design, final JRDesignBand parentBand, final JRDesignBand headerBand,
250                         int x, final LayoutItem_Field libglomLayoutItemField) {
251                 final String fieldName = addField(design, libglomLayoutItemField);
252
253                 // An arbitrary width, because we must specify _some_ width:
254                 final int width = 100; // Points, as specified later.
255
256                 // Show the field title:
257                 final JRDesignStaticText textFieldColumn = new JRDesignStaticText();
258                 textFieldColumn.setText(libglomLayoutItemField.get_title(this.localeID));
259                 textFieldColumn.setY(0);
260                 textFieldColumn.setX(x);
261                 textFieldColumn.setWidth(width); // No data will be shown without this.
262                 // textFieldColumn.setStretchWithOverflow(true);
263                 textFieldColumn.setHeight(height); // We must specify _some_ height.
264                 headerBand.addElement(textFieldColumn);
265
266                 // Show an instance of the field (the field value):
267                 final JRDesignTextField textField = new JRDesignTextField();
268
269                 // Make sure this field starts at the right of the previous field,
270                 // because JasperReports uses absolute positioning.
271                 textField.setY(0);
272                 textField.setX(x);
273                 textField.setWidth(width); // No data will be shown without this.
274                 x += width;
275
276                 // This only stretches vertically, but that is better than
277                 // nothing.
278                 textField.setStretchWithOverflow(true);
279                 textField.setHeight(height); // We must specify _some_ height.
280
281                 // TODO: Where is this format documented?
282                 final JRDesignExpression expression = new JRDesignExpression();
283                 expression.setText("$F{" + fieldName + "}");
284
285                 textField.setExpression(expression);
286                 parentBand.addElement(textField);
287                 return x;
288         }
289
290         /**
291          * @param design
292          * @param libglomLayoutItemField
293          * @return
294          */
295         private String addField(final JasperDesign design, final LayoutItem_Field libglomLayoutItemField) {
296                 fieldsToGet.add(libglomLayoutItemField);
297
298                 final String fieldName = libglomLayoutItemField.get_name();
299                 // System.out.print("fieldName=" + fieldName + "\n");
300
301                 // Tell the JasperDesign about the database field that will be in the SQL query,
302                 // specified later:
303                 final JRDesignField field = new JRDesignField();
304                 field.setName(fieldName); // TODO: Related fields.
305                 field.setValueClass(getClassTypeForGlomType(libglomLayoutItemField));
306
307                 try {
308                         design.addField(field);
309                 } catch (final JRException e2) {
310                         // TODO Auto-generated catch block
311                         e2.printStackTrace();
312                 }
313                 return fieldName;
314         }
315
316         /**
317          * @param libglomLayoutItemField
318          * @return
319          */
320         private Class<?> getClassTypeForGlomType(final LayoutItem_Field libglomLayoutItemField) {
321                 // Choose a suitable java class type for the SQL field:
322                 Class<?> klass = null;
323                 switch (libglomLayoutItemField.get_glom_type()) {
324                 case TYPE_TEXT:
325                         klass = java.lang.String.class;
326                         break;
327                 case TYPE_BOOLEAN:
328                         klass = java.lang.Boolean.class;
329                         break;
330                 case TYPE_NUMERIC:
331                         klass = java.lang.Double.class;
332                         break;
333                 case TYPE_DATE:
334                         klass = java.util.Date.class;
335                         break;
336                 case TYPE_TIME:
337                         klass = java.sql.Time.class;
338                         break;
339                 case TYPE_IMAGE:
340                         klass = java.sql.Blob.class; // TODO: This does not work.
341                         break;
342                 }
343                 return klass;
344         }
345 }