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.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;
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;
59 * @author Murray Cumming <murrayc@openimus.com>
62 public class ReportGenerator {
64 final int height = 30;
65 LayoutFieldVector fieldsToGet = new LayoutFieldVector();
66 SortClause sortClause = new SortClause();
69 ReportGenerator(final String localeID) {
70 this.localeID = StringUtils.defaultString(localeID);
76 * @param configuredDoc
80 public String generateReport(final Document document, final String tableName, final String reportName,
81 final Connection connection, final org.glom.libglom.LayoutGroup layout_group) {
83 final JasperDesign design = new JasperDesign();
84 design.setName(reportName); // TODO: Actually, we want the title.
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);
93 final JRDesignBand detailBand = new JRDesignBand();
94 detailBand.setHeight(height + 20);
96 final JRDesignBand headerBand = new JRDesignBand();
97 headerBand.setHeight(height + 20);
99 fieldsToGet = new LayoutFieldVector();
101 addToReport(layout_group, design, detailBand, headerBand, x);
103 design.setColumnHeader(headerBand);
104 ((JRDesignSection) design.getDetailSection()).addBand(detailBand);
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
110 if (StringUtils.isEmpty(quickFind)) {
111 whereClause = new SqlExpr();
113 final Value quickFindValue = new Value(quickFind);
114 whereClause = Glom.get_find_where_clause_quick(document, tableName, quickFindValue);
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);
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);
128 report = JasperCompileManager.compileReport(design);
129 } catch (final JRException ex) {
130 ex.printStackTrace();
131 return "Failed to Generate HTML: compileReport() failed.";
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.";
144 final ByteArrayOutputStream output = new ByteArrayOutputStream();
146 // We use this because there is no JasperExportManager.exportReportToHtmlStream() method.
147 // JasperExportManager.exportReportToXmlStream(print, output);
149 final JRXhtmlExporter exporter = new JRXhtmlExporter();
150 exporter.setParameter(JRHtmlExporterParameter.JASPER_PRINT, print);
151 exporter.setParameter(JRHtmlExporterParameter.OUTPUT_STREAM, output);
153 // Use points instead of pixels for sizes, because pixels are silly
155 exporter.setParameter(JRHtmlExporterParameter.SIZE_UNIT, "pt");
157 exporter.exportReport();
158 } catch (final JRException ex) {
159 ex.printStackTrace();
160 return "Failed to Generate HTML: exportReport() failed.";
163 // System.out.print(output.toString() + "\n");
164 return output.toString();
168 * @param layout_group
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);
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)
192 final String fieldName = addField(design, fieldGroupBy);
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);
202 final JRDesignGroup group = new JRDesignGroup();
203 group.setName(fieldName);
205 // Show the field value:
206 final JRDesignExpression expression = new JRDesignExpression();
207 expression.setText("$F{" + fieldName + "}");
208 group.setExpression(expression);
210 final JRDesignBand groupBand = new JRDesignBand();
211 groupBand.setHeight(height);
212 ((JRDesignSection) group.getGroupHeaderSection()).addBand(groupBand);
214 final JRDesignBand footerBand = new JRDesignBand();
215 footerBand.setHeight(height);
216 ((JRDesignSection) group.getGroupFooterSection()).addBand(footerBand);
219 design.addGroup(group);
220 } catch (final JRException e) {
221 // TODO Auto-generated catch block
225 // Show the group-by field:
226 final int groupX = addFieldToBand(design, groupBand, headerBand, x, fieldGroupBy);
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);
234 // Recurse into sub-groups:
235 x = addToReport(libglomLayoutGroup, design, parentBand, headerBand, x);
246 * @param libglomLayoutItemField
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);
253 // An arbitrary width, because we must specify _some_ width:
254 final int width = 100; // Points, as specified later.
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);
266 // Show an instance of the field (the field value):
267 final JRDesignTextField textField = new JRDesignTextField();
269 // Make sure this field starts at the right of the previous field,
270 // because JasperReports uses absolute positioning.
273 textField.setWidth(width); // No data will be shown without this.
276 // This only stretches vertically, but that is better than
278 textField.setStretchWithOverflow(true);
279 textField.setHeight(height); // We must specify _some_ height.
281 // TODO: Where is this format documented?
282 final JRDesignExpression expression = new JRDesignExpression();
283 expression.setText("$F{" + fieldName + "}");
285 textField.setExpression(expression);
286 parentBand.addElement(textField);
292 * @param libglomLayoutItemField
295 private String addField(final JasperDesign design, final LayoutItem_Field libglomLayoutItemField) {
296 fieldsToGet.add(libglomLayoutItemField);
298 final String fieldName = libglomLayoutItemField.get_name();
299 // System.out.print("fieldName=" + fieldName + "\n");
301 // Tell the JasperDesign about the database field that will be in the SQL query,
303 final JRDesignField field = new JRDesignField();
304 field.setName(fieldName); // TODO: Related fields.
305 field.setValueClass(getClassTypeForGlomType(libglomLayoutItemField));
308 design.addField(field);
309 } catch (final JRException e2) {
310 // TODO Auto-generated catch block
311 e2.printStackTrace();
317 * @param libglomLayoutItemField
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()) {
325 klass = java.lang.String.class;
328 klass = java.lang.Boolean.class;
331 klass = java.lang.Double.class;
334 klass = java.util.Date.class;
337 klass = java.sql.Time.class;
340 klass = java.sql.Blob.class; // TODO: This does not work.