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.JRDesignLine;
36 import net.sf.jasperreports.engine.design.JRDesignQuery;
37 import net.sf.jasperreports.engine.design.JRDesignSection;
38 import net.sf.jasperreports.engine.design.JRDesignStaticText;
39 import net.sf.jasperreports.engine.design.JRDesignStyle;
40 import net.sf.jasperreports.engine.design.JRDesignTextField;
41 import net.sf.jasperreports.engine.design.JasperDesign;
42 import net.sf.jasperreports.engine.export.JRHtmlExporterParameter;
43 import net.sf.jasperreports.engine.export.JRXhtmlExporter;
45 import org.apache.commons.lang.StringUtils;
46 import org.glom.libglom.Document;
47 import org.glom.libglom.Glom;
48 import org.glom.libglom.LayoutFieldVector;
49 import org.glom.libglom.LayoutGroup;
50 import org.glom.libglom.LayoutItemVector;
51 import org.glom.libglom.LayoutItem_Field;
52 import org.glom.libglom.LayoutItem_GroupBy;
53 import org.glom.libglom.Relationship;
54 import org.glom.libglom.SortClause;
55 import org.glom.libglom.SortFieldPair;
56 import org.glom.libglom.SqlBuilder;
57 import org.glom.libglom.SqlExpr;
58 import org.glom.libglom.Value;
61 * @author Murray Cumming <murrayc@openimus.com>
64 public class ReportGenerator {
66 final int height = 30; // Points, as specified later.
67 // An arbitrary width, because we must specify _some_ width:
68 final int width = 100; // Points, as specified later.
70 LayoutFieldVector fieldsToGet = new LayoutFieldVector();
71 SortClause sortClause = new SortClause();
74 JRDesignStyle normalStyle = new JRDesignStyle();
75 JRDesignStyle boldStyle = new JRDesignStyle();
77 ReportGenerator(final String localeID) {
78 this.localeID = StringUtils.defaultString(localeID);
84 * @param configuredDoc
88 public String generateReport(final Document document, final String tableName, final String reportName,
89 final Connection connection, final org.glom.libglom.LayoutGroup layout_group) {
91 final JasperDesign design = new JasperDesign();
92 design.setName(reportName); // TODO: Actually, we want the title.
94 normalStyle.setName("Sans_Normal");
95 normalStyle.setDefault(true);
96 normalStyle.setFontName("DejaVu Sans");
97 normalStyle.setFontSize(12);
98 boldStyle.setName("Sans_Bold");
99 boldStyle.setFontName("DejaVu Sans");
100 boldStyle.setFontSize(12);
101 boldStyle.setBold(true);
103 design.addStyle(normalStyle);
104 design.addStyle(boldStyle);
105 } catch (final JRException ex) {
106 // TODO Auto-generated catch block
107 ex.printStackTrace();
110 final JRDesignBand titleBand = new JRDesignBand();
111 titleBand.setHeight(height);
112 final JRDesignStaticText staticTitle = new JRDesignStaticText();
113 staticTitle.setText("debug: test report title text");
114 titleBand.addElement(staticTitle);
115 design.setTitle(titleBand);
117 final JRDesignBand detailBand = new JRDesignBand();
118 detailBand.setHeight(height + 20);
120 final JRDesignBand headerBand = new JRDesignBand();
121 headerBand.setHeight(height + 20);
123 fieldsToGet = new LayoutFieldVector();
125 addToReport(layout_group, design, detailBand, headerBand, x);
127 design.setColumnHeader(headerBand);
128 ((JRDesignSection) design.getDetailSection()).addBand(detailBand);
130 // Later versions of libglom actually return an empty SqlExpr when quickFindValue is empty,
131 // but let's be sure:
132 final String quickFind = ""; // TODO
134 if (StringUtils.isEmpty(quickFind)) {
135 whereClause = new SqlExpr();
137 final Value quickFindValue = new Value(quickFind);
138 whereClause = Glom.get_find_where_clause_quick(document, tableName, quickFindValue);
141 final Relationship extraJoin = new Relationship(); // Ignored.
142 final SqlBuilder builder = Glom.build_sql_select_with_where_clause(tableName, fieldsToGet, whereClause,
143 extraJoin, sortClause);
144 final String sqlQuery = Glom.sqlbuilder_get_full_query(builder);
146 final JRDesignQuery query = new JRDesignQuery();
147 query.setText(sqlQuery); // TODO: Extra sort clause to sort the rows within the groups.
148 design.setQuery(query);
152 report = JasperCompileManager.compileReport(design);
153 } catch (final JRException ex) {
154 ex.printStackTrace();
155 return "Failed to Generate HTML: compileReport() failed.";
160 final HashMap<String, Object> parameters = new HashMap<String, Object>();
161 parameters.put("ReportTitle", reportName); // TODO: Use the title, not the name.
162 print = JasperFillManager.fillReport(report, parameters, connection);
163 } catch (final JRException ex) {
164 ex.printStackTrace();
165 return "Failed to Generate HTML: fillReport() failed.";
168 final ByteArrayOutputStream output = new ByteArrayOutputStream();
170 // We use this because there is no JasperExportManager.exportReportToHtmlStream() method.
171 // JasperExportManager.exportReportToXmlStream(print, output);
173 final JRXhtmlExporter exporter = new JRXhtmlExporter();
174 exporter.setParameter(JRHtmlExporterParameter.JASPER_PRINT, print);
175 exporter.setParameter(JRHtmlExporterParameter.OUTPUT_STREAM, output);
177 // Use points instead of pixels for sizes, because pixels are silly
179 exporter.setParameter(JRHtmlExporterParameter.SIZE_UNIT, "pt");
181 exporter.exportReport();
182 } catch (final JRException ex) {
183 ex.printStackTrace();
184 return "Failed to Generate HTML: exportReport() failed.";
187 // System.out.print(output.toString() + "\n");
188 return output.toString();
192 * @param layout_group
198 private int addToReport(final org.glom.libglom.LayoutGroup layout_group, final JasperDesign design,
199 final JRDesignBand parentBand, final JRDesignBand headerBand, int x) {
200 final LayoutItemVector layoutItemsVec = layout_group.get_items();
201 final int numItems = Utils.safeLongToInt(layoutItemsVec.size());
202 for (int i = 0; i < numItems; i++) {
203 final org.glom.libglom.LayoutItem libglomLayoutItem = layoutItemsVec.get(i);
205 final LayoutGroup libglomLayoutGroup = LayoutGroup.cast_dynamic(libglomLayoutItem);
206 final LayoutItem_Field libglomLayoutItemField = LayoutItem_Field.cast_dynamic(libglomLayoutItem);
207 if (libglomLayoutItemField != null) {
208 x = addFieldToDetailBand(design, parentBand, headerBand, x, libglomLayoutItemField);
209 } else if (libglomLayoutGroup != null) {
210 final LayoutItem_GroupBy libglomGroupBy = LayoutItem_GroupBy.cast_dynamic(libglomLayoutGroup);
211 if (libglomGroupBy != null) {
212 final LayoutItem_Field fieldGroupBy = libglomGroupBy.get_field_group_by();
213 if (fieldGroupBy == null)
216 final String fieldName = addField(design, fieldGroupBy);
218 // We must sort by the group field,
219 // so that JasperReports can start a new group when its value changes.
220 // Note that this is not like a SQL GROUP BY.
221 final SortFieldPair pair = new SortFieldPair();
222 pair.setFirst(fieldGroupBy);
223 pair.setSecond(true); // Ascending.
224 sortClause.add(pair);
226 final JRDesignGroup group = new JRDesignGroup();
227 group.setName(fieldName);
229 // Show the field value:
230 final JRDesignExpression expression = createFieldExpression(fieldName);
231 group.setExpression(expression);
234 design.addGroup(group);
235 } catch (final JRException e) {
236 // TODO Auto-generated catch block
240 // Show the group-by field:
241 final JRDesignBand groupBand = new JRDesignBand();
242 groupBand.setHeight(height);
243 ((JRDesignSection) group.getGroupHeaderSection()).addBand(groupBand);
246 * final JRDesignBand footerBand = new JRDesignBand(); footerBand.setHeight(height);
247 * ((JRDesignSection) group.getGroupFooterSection()).addBand(footerBand);
250 int groupX = addFieldToGroupBand(design, groupBand, x, fieldGroupBy);
252 // Show the secondary fields:
253 final LayoutGroup groupSecondaries = libglomGroupBy.get_group_secondary_fields();
254 if (groupSecondaries != null)
255 groupX = addSecondaryFieldsToGroupBand(groupSecondaries, design, groupBand, groupX);
257 final JRDesignLine line = new JRDesignLine();
258 final int lineheight = 1;
261 // TODO: Automatically place it below the text, though that needs us to know how much height the
262 // text really needs.
263 line.setY(height - lineheight - 10);
265 // TODO: Make it as wide as needed by the details band.
266 line.setWidth(groupX);
267 line.setHeight(lineheight);
268 groupBand.addElement(line);
271 // Recurse into sub-groups:
272 x = addToReport(libglomLayoutGroup, design, parentBand, headerBand, x);
279 private int addSecondaryFieldsToGroupBand(final org.glom.libglom.LayoutGroup layout_group,
280 final JasperDesign design, final JRDesignBand groupBand, int x) {
281 final LayoutItemVector layoutItemsVec = layout_group.get_items();
282 final int numItems = Utils.safeLongToInt(layoutItemsVec.size());
283 for (int i = 0; i < numItems; i++) {
284 final org.glom.libglom.LayoutItem libglomLayoutItem = layoutItemsVec.get(i);
286 final LayoutGroup libglomLayoutGroup = LayoutGroup.cast_dynamic(libglomLayoutItem);
287 final LayoutItem_Field libglomLayoutItemField = LayoutItem_Field.cast_dynamic(libglomLayoutItem);
288 if (libglomLayoutItemField != null) {
289 x = addFieldToGroupBand(design, groupBand, x, libglomLayoutItemField);
290 } else if (libglomLayoutGroup != null) {
291 // We do not expect LayoutItem_GroupBy in the secondary fields:
292 // final LayoutItem_GroupBy libglomGroupBy = LayoutItem_GroupBy.cast_dynamic(libglomLayoutGroup);
294 // Recurse into sub-groups:
295 x = addSecondaryFieldsToGroupBand(libglomLayoutGroup, design, groupBand, x);
306 private JRDesignExpression createFieldExpression(final String fieldName) {
307 final JRDesignExpression expression = new JRDesignExpression();
309 // TODO: Where is this format documented?
310 expression.setText("$F{" + fieldName + "}");
318 * @param libglomLayoutItemField
321 private int addFieldToDetailBand(final JasperDesign design, final JRDesignBand parentBand,
322 final JRDesignBand headerBand, int x, final LayoutItem_Field libglomLayoutItemField) {
323 final String fieldName = addField(design, libglomLayoutItemField);
325 // Show the field title:
326 final JRDesignStaticText textFieldColumn = createFieldTitleElement(x, libglomLayoutItemField, false);
327 textFieldColumn.setStyle(boldStyle);
328 headerBand.addElement(textFieldColumn);
330 // Show an instance of the field (the field value):
331 final JRDesignTextField textField = createFieldValueElement(x, fieldName);
332 textField.setStyle(normalStyle);
333 parentBand.addElement(textField);
339 private int addFieldToGroupBand(final JasperDesign design, final JRDesignBand parentBand, int x,
340 final LayoutItem_Field libglomLayoutItemField) {
341 final String fieldName = addField(design, libglomLayoutItemField);
343 // Show the field title:
344 final JRDesignStaticText textFieldColumn = createFieldTitleElement(x, libglomLayoutItemField, true);
346 // Instead, the field value will be bold, because here it is like a title.
347 textFieldColumn.setStyle(normalStyle);
349 parentBand.addElement(textFieldColumn);
352 // Show an instance of the field (the field value):
353 final JRDesignTextField textField = createFieldValueElement(x, fieldName);
354 parentBand.addElement(textField);
355 textField.setStyle(boldStyle);
366 private JRDesignTextField createFieldValueElement(final int x, final String fieldName) {
367 final JRDesignTextField textField = new JRDesignTextField();
369 // Make sure this field starts at the right of the previous field,
370 // because JasperReports uses absolute positioning.
373 textField.setWidth(width); // No data will be shown without this.
375 // This only stretches vertically, but that is better than
377 textField.setStretchWithOverflow(true);
378 textField.setHeight(height); // We must specify _some_ height.
380 final JRDesignExpression expression = createFieldExpression(fieldName);
382 textField.setExpression(expression);
388 * @param libglomLayoutItemField
391 private JRDesignStaticText createFieldTitleElement(final int x, final LayoutItem_Field libglomLayoutItemField,
392 final boolean withColon) {
393 final JRDesignStaticText textFieldColumn = new JRDesignStaticText();
395 String title = StringUtils.defaultString(libglomLayoutItemField.get_title(this.localeID));
397 // If the title is at the left, instead of above, we need a : to show that it's a title.
401 textFieldColumn.setText(title);
402 textFieldColumn.setY(0);
403 textFieldColumn.setX(x);
404 textFieldColumn.setWidth(width); // No data will be shown without this.
405 // textFieldColumn.setStretchWithOverflow(true);
406 textFieldColumn.setHeight(height); // We must specify _some_ height.
407 return textFieldColumn;
412 * @param libglomLayoutItemField
415 private String addField(final JasperDesign design, final LayoutItem_Field libglomLayoutItemField) {
416 fieldsToGet.add(libglomLayoutItemField);
418 final String fieldName = libglomLayoutItemField.get_name();
419 // System.out.print("fieldName=" + fieldName + "\n");
421 // Tell the JasperDesign about the database field that will be in the SQL query,
423 final JRDesignField field = new JRDesignField();
424 field.setName(fieldName); // TODO: Related fields.
425 field.setValueClass(getClassTypeForGlomType(libglomLayoutItemField));
428 design.addField(field);
429 } catch (final JRException e2) {
430 // TODO Auto-generated catch block
431 e2.printStackTrace();
437 * @param libglomLayoutItemField
440 private Class<?> getClassTypeForGlomType(final LayoutItem_Field libglomLayoutItemField) {
441 // Choose a suitable java class type for the SQL field:
442 Class<?> klass = null;
443 switch (libglomLayoutItemField.get_glom_type()) {
445 klass = java.lang.String.class;
448 klass = java.lang.Boolean.class;
451 klass = java.lang.Double.class;
454 klass = java.util.Date.class;
457 klass = java.sql.Time.class;
460 klass = java.sql.Blob.class; // TODO: This does not work.