2 * Copyright (C) 2012 Openismus GmbH
4 * This file is part of GWT-Glom.
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.
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
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/>.
20 package org.glom.web.server;
22 import java.io.ByteArrayOutputStream;
23 import java.sql.Connection;
24 import java.util.HashMap;
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.JRDesignStyle;
39 import net.sf.jasperreports.engine.design.JRDesignTextField;
40 import net.sf.jasperreports.engine.design.JasperDesign;
41 import net.sf.jasperreports.engine.export.JRHtmlExporterParameter;
42 import net.sf.jasperreports.engine.export.JRXhtmlExporter;
44 import org.apache.commons.lang.StringUtils;
45 import org.glom.libglom.Document;
46 import org.glom.libglom.Glom;
47 import org.glom.libglom.LayoutFieldVector;
48 import org.glom.libglom.LayoutGroup;
49 import org.glom.libglom.LayoutItemVector;
50 import org.glom.libglom.LayoutItem_Field;
51 import org.glom.libglom.LayoutItem_GroupBy;
52 import org.glom.libglom.Relationship;
53 import org.glom.libglom.SortClause;
54 import org.glom.libglom.SortFieldPair;
55 import org.glom.libglom.SqlBuilder;
56 import org.glom.libglom.SqlExpr;
57 import org.glom.libglom.Value;
60 * @author Murray Cumming <murrayc@openimus.com>
63 public class ReportGenerator {
65 final int height = 30; // Points, as specified later.
66 // An arbitrary width, because we must specify _some_ width:
67 final int width = 100; // Points, as specified later.
69 LayoutFieldVector fieldsToGet = new LayoutFieldVector();
70 SortClause sortClause = new SortClause();
73 JRDesignStyle normalStyle = new JRDesignStyle();
74 JRDesignStyle boldStyle = new JRDesignStyle();
76 ReportGenerator(final String localeID) {
77 this.localeID = StringUtils.defaultString(localeID);
83 * @param configuredDoc
87 public String generateReport(final Document document, final String tableName, final String reportName,
88 final Connection connection, final org.glom.libglom.LayoutGroup layout_group) {
90 final JasperDesign design = new JasperDesign();
91 design.setName(reportName); // TODO: Actually, we want the title.
93 normalStyle.setName("Sans_Normal");
94 normalStyle.setDefault(true);
95 normalStyle.setFontName("DejaVu Sans");
96 normalStyle.setFontSize(12);
97 boldStyle.setName("Sans_Bold");
98 boldStyle.setFontName("DejaVu Sans");
99 boldStyle.setFontSize(12);
100 boldStyle.setBold(true);
102 design.addStyle(normalStyle);
103 design.addStyle(boldStyle);
104 } catch (final JRException ex) {
105 // TODO Auto-generated catch block
106 ex.printStackTrace();
109 final JRDesignBand titleBand = new JRDesignBand();
110 titleBand.setHeight(height);
111 final JRDesignStaticText staticTitle = new JRDesignStaticText();
112 staticTitle.setText("debug: test report title text");
113 titleBand.addElement(staticTitle);
114 design.setTitle(titleBand);
116 final JRDesignBand detailBand = new JRDesignBand();
117 detailBand.setHeight(height + 20);
119 final JRDesignBand headerBand = new JRDesignBand();
120 headerBand.setHeight(height + 20);
122 fieldsToGet = new LayoutFieldVector();
124 addToReport(layout_group, design, detailBand, headerBand, x);
126 design.setColumnHeader(headerBand);
127 ((JRDesignSection) design.getDetailSection()).addBand(detailBand);
129 // Later versions of libglom actually return an empty SqlExpr when quickFindValue is empty,
130 // but let's be sure:
131 final String quickFind = ""; // TODO
133 if (StringUtils.isEmpty(quickFind)) {
134 whereClause = new SqlExpr();
136 final Value quickFindValue = new Value(quickFind);
137 whereClause = Glom.get_find_where_clause_quick(document, tableName, quickFindValue);
140 final Relationship extraJoin = new Relationship(); // Ignored.
141 final SqlBuilder builder = Glom.build_sql_select_with_where_clause(tableName, fieldsToGet, whereClause,
142 extraJoin, sortClause);
143 final String sqlQuery = Glom.sqlbuilder_get_full_query(builder);
145 final JRDesignQuery query = new JRDesignQuery();
146 query.setText(sqlQuery); // TODO: Extra sort clause to sort the rows within the groups.
147 design.setQuery(query);
151 report = JasperCompileManager.compileReport(design);
152 } catch (final JRException ex) {
153 ex.printStackTrace();
154 return "Failed to Generate HTML: compileReport() failed.";
159 final HashMap<String, Object> parameters = new HashMap<String, Object>();
160 parameters.put("ReportTitle", reportName); // TODO: Use the title, not the name.
161 print = JasperFillManager.fillReport(report, parameters, connection);
162 } catch (final JRException ex) {
163 ex.printStackTrace();
164 return "Failed to Generate HTML: fillReport() failed.";
167 final ByteArrayOutputStream output = new ByteArrayOutputStream();
169 // We use this because there is no JasperExportManager.exportReportToHtmlStream() method.
170 // JasperExportManager.exportReportToXmlStream(print, output);
172 final JRXhtmlExporter exporter = new JRXhtmlExporter();
173 exporter.setParameter(JRHtmlExporterParameter.JASPER_PRINT, print);
174 exporter.setParameter(JRHtmlExporterParameter.OUTPUT_STREAM, output);
176 // Use points instead of pixels for sizes, because pixels are silly
178 exporter.setParameter(JRHtmlExporterParameter.SIZE_UNIT, "pt");
180 exporter.exportReport();
181 } catch (final JRException ex) {
182 ex.printStackTrace();
183 return "Failed to Generate HTML: exportReport() failed.";
186 // System.out.print(output.toString() + "\n");
187 return output.toString();
191 * @param layout_group
197 private int addToReport(final org.glom.libglom.LayoutGroup layout_group, final JasperDesign design,
198 final JRDesignBand parentBand, final JRDesignBand headerBand, int x) {
199 final LayoutItemVector layoutItemsVec = layout_group.get_items();
200 final int numItems = Utils.safeLongToInt(layoutItemsVec.size());
201 for (int i = 0; i < numItems; i++) {
202 final org.glom.libglom.LayoutItem libglomLayoutItem = layoutItemsVec.get(i);
204 final LayoutGroup libglomLayoutGroup = LayoutGroup.cast_dynamic(libglomLayoutItem);
205 final LayoutItem_Field libglomLayoutItemField = LayoutItem_Field.cast_dynamic(libglomLayoutItem);
206 if (libglomLayoutItemField != null) {
207 x = addFieldToDetailBand(design, parentBand, headerBand, x, libglomLayoutItemField);
208 } else if (libglomLayoutGroup != null) {
209 final LayoutItem_GroupBy libglomGroupBy = LayoutItem_GroupBy.cast_dynamic(libglomLayoutGroup);
210 if (libglomGroupBy != null) {
211 final LayoutItem_Field fieldGroupBy = libglomGroupBy.get_field_group_by();
212 if (fieldGroupBy == null)
215 final String fieldName = addField(design, fieldGroupBy);
217 // We must sort by the group field,
218 // so that JasperReports can start a new group when its value changes.
219 // Note that this is not like a SQL GROUP BY.
220 final SortFieldPair pair = new SortFieldPair();
221 pair.setFirst(fieldGroupBy);
222 pair.setSecond(true); // Ascending.
223 sortClause.add(pair);
225 final JRDesignGroup group = new JRDesignGroup();
226 group.setName(fieldName);
228 // Show the field value:
229 final JRDesignExpression expression = createFieldExpression(fieldName);
230 group.setExpression(expression);
233 design.addGroup(group);
234 } catch (final JRException e) {
235 // TODO Auto-generated catch block
239 // Show the group-by field:
240 final JRDesignBand groupBand = new JRDesignBand();
241 groupBand.setHeight(height);
242 ((JRDesignSection) group.getGroupHeaderSection()).addBand(groupBand);
245 * final JRDesignBand footerBand = new JRDesignBand(); footerBand.setHeight(height);
246 * ((JRDesignSection) group.getGroupFooterSection()).addBand(footerBand);
249 final int groupX = addFieldToGroupBand(design, groupBand, x, fieldGroupBy);
251 // Show the secondary fields:
252 final LayoutGroup groupSecondaries = libglomGroupBy.get_group_secondary_fields();
253 if (groupSecondaries != null)
254 addSecondaryFieldsToGroupBand(groupSecondaries, design, groupBand, groupX);
257 // Recurse into sub-groups:
258 x = addToReport(libglomLayoutGroup, design, parentBand, headerBand, x);
265 private int addSecondaryFieldsToGroupBand(final org.glom.libglom.LayoutGroup layout_group,
266 final JasperDesign design, final JRDesignBand groupBand, int x) {
267 final LayoutItemVector layoutItemsVec = layout_group.get_items();
268 final int numItems = Utils.safeLongToInt(layoutItemsVec.size());
269 for (int i = 0; i < numItems; i++) {
270 final org.glom.libglom.LayoutItem libglomLayoutItem = layoutItemsVec.get(i);
272 final LayoutGroup libglomLayoutGroup = LayoutGroup.cast_dynamic(libglomLayoutItem);
273 final LayoutItem_Field libglomLayoutItemField = LayoutItem_Field.cast_dynamic(libglomLayoutItem);
274 if (libglomLayoutItemField != null) {
275 x = addFieldToGroupBand(design, groupBand, x, libglomLayoutItemField);
276 } else if (libglomLayoutGroup != null) {
277 // We do not expect LayoutItem_GroupBy in the secondary fields:
278 // final LayoutItem_GroupBy libglomGroupBy = LayoutItem_GroupBy.cast_dynamic(libglomLayoutGroup);
280 // Recurse into sub-groups:
281 x = addSecondaryFieldsToGroupBand(libglomLayoutGroup, design, groupBand, x);
292 private JRDesignExpression createFieldExpression(final String fieldName) {
293 final JRDesignExpression expression = new JRDesignExpression();
295 // TODO: Where is this format documented?
296 expression.setText("$F{" + fieldName + "}");
304 * @param libglomLayoutItemField
307 private int addFieldToDetailBand(final JasperDesign design, final JRDesignBand parentBand,
308 final JRDesignBand headerBand, int x, final LayoutItem_Field libglomLayoutItemField) {
309 final String fieldName = addField(design, libglomLayoutItemField);
311 // Show the field title:
312 final JRDesignStaticText textFieldColumn = createFieldTitleElement(x, libglomLayoutItemField, false);
313 textFieldColumn.setStyle(boldStyle);
314 headerBand.addElement(textFieldColumn);
316 // Show an instance of the field (the field value):
317 final JRDesignTextField textField = createFieldValueElement(x, fieldName);
318 textField.setStyle(normalStyle);
319 parentBand.addElement(textField);
325 private int addFieldToGroupBand(final JasperDesign design, final JRDesignBand parentBand, int x,
326 final LayoutItem_Field libglomLayoutItemField) {
327 final String fieldName = addField(design, libglomLayoutItemField);
329 // Show the field title:
330 final JRDesignStaticText textFieldColumn = createFieldTitleElement(x, libglomLayoutItemField, true);
332 // Instead, the field value will be bold, because here it is like a title.
333 textFieldColumn.setStyle(normalStyle);
335 parentBand.addElement(textFieldColumn);
338 // Show an instance of the field (the field value):
339 final JRDesignTextField textField = createFieldValueElement(x, fieldName);
340 parentBand.addElement(textField);
341 textField.setStyle(boldStyle);
352 private JRDesignTextField createFieldValueElement(final int x, final String fieldName) {
353 final JRDesignTextField textField = new JRDesignTextField();
355 // Make sure this field starts at the right of the previous field,
356 // because JasperReports uses absolute positioning.
359 textField.setWidth(width); // No data will be shown without this.
361 // This only stretches vertically, but that is better than
363 textField.setStretchWithOverflow(true);
364 textField.setHeight(height); // We must specify _some_ height.
366 final JRDesignExpression expression = createFieldExpression(fieldName);
368 textField.setExpression(expression);
374 * @param libglomLayoutItemField
377 private JRDesignStaticText createFieldTitleElement(final int x, final LayoutItem_Field libglomLayoutItemField,
378 final boolean withColon) {
379 final JRDesignStaticText textFieldColumn = new JRDesignStaticText();
381 String title = StringUtils.defaultString(libglomLayoutItemField.get_title(this.localeID));
383 // If the title is at the left, instead of above, we need a : to show that it's a title.
387 textFieldColumn.setText(title);
388 textFieldColumn.setY(0);
389 textFieldColumn.setX(x);
390 textFieldColumn.setWidth(width); // No data will be shown without this.
391 // textFieldColumn.setStretchWithOverflow(true);
392 textFieldColumn.setHeight(height); // We must specify _some_ height.
393 return textFieldColumn;
398 * @param libglomLayoutItemField
401 private String addField(final JasperDesign design, final LayoutItem_Field libglomLayoutItemField) {
402 fieldsToGet.add(libglomLayoutItemField);
404 final String fieldName = libglomLayoutItemField.get_name();
405 // System.out.print("fieldName=" + fieldName + "\n");
407 // Tell the JasperDesign about the database field that will be in the SQL query,
409 final JRDesignField field = new JRDesignField();
410 field.setName(fieldName); // TODO: Related fields.
411 field.setValueClass(getClassTypeForGlomType(libglomLayoutItemField));
414 design.addField(field);
415 } catch (final JRException e2) {
416 // TODO Auto-generated catch block
417 e2.printStackTrace();
423 * @param libglomLayoutItemField
426 private Class<?> getClassTypeForGlomType(final LayoutItem_Field libglomLayoutItemField) {
427 // Choose a suitable java class type for the SQL field:
428 Class<?> klass = null;
429 switch (libglomLayoutItemField.get_glom_type()) {
431 klass = java.lang.String.class;
434 klass = java.lang.Boolean.class;
437 klass = java.lang.Double.class;
440 klass = java.util.Date.class;
443 klass = java.sql.Time.class;
446 klass = java.sql.Blob.class; // TODO: This does not work.