Динамічний репорт за допомогою JasperReports

На проекті, над яким я працював десь рік тому, постала задача виконувати динамічну генерацію друкованої версії звіту. На проекті для генерації звітів використовувався JasperReports.
Отже, постали наступні питання
1. Як виконувати динамічну генерацію звіту:
- чи є для цього API,
- чи все-таки генерувати XML, а потім його компілювати?
2. Як передавати дані?

При виборі способу генерації динамічного звіту я вирішив використати перший варіант - працювати із API JasperReports для формування представлення звіту. Ідея генерувати XML в коді мені не дуже подобалася, тому що в цьому випадку код виглядав для мене заплутаним. Крім того, зміна версії бібліотеки могла привести до помилки, яку би було тяжко визначити. З API ситуація була інакшою: досягалася непогана чистота коду із можливістю абстрагування для різних видів звітів. Та й при компіляції можна було визначити сумісність використовуваного API із версією бібліотеки.

Дивно, але на той час мене чомусь не осягнула ідея використовувати JSP, Velocity чи FreeMarker для генерації шаблону звіту. Мені здається, що на даний момент я би використав саме цей спосіб.


Тут дуже знадобився такий клас як JasperDesign та набір класів сімейства Design. До класів сімейства Design відносяться:
  • JRDesignField
  • JRDesignStyle
  • JRDesignStaticText
  • JRDesignTextField
  • JRDesignExpression
  • JRDesignBand
  • і багато інших.
Перелічені вище класи є найбільш використовуваними. І якщо ви працювали колись із JasperReports, створюючи звіти за допомогою JRXML файла, то вам можуть бути знайомі такі елементи, як field, style, staticText, textField, band тощо. Суть використання Design класів полягає в тому, що вони представляють собою ті ж самі елементи JRXML. Обєктна модель справді дуже проста і зручна в розумінні.
Створювати репорти динамічно просто і зручно. Але це є виключною ситуацією, оскільки завжди можна скористатися вашим олюбленим редактором XML або ж iReport.

Мені свого часу довелося створити чимало репортів за допомогою Jasper. Більшість шаблонів створювалися в редакторі XML, чимало динамічних створювалися в коді і могли поєднювати ще декілька динамічних репортів, інші просто накидувалися швидкоруч в iReport.

Для тих, кому цікаво, хочу привести декілька прикладів використання API JasperReport.

Створення елементів звіту:

public static JRStyle getDefaultStyle() {
JRDesignStyle style = new JRDesignStyle();
style.setName("defaultStyle");
style.setDefault(true);
style.setFontName("Arial");
style.setFontSize(8);
style.setPdfFontName("/TTF/arial.ttf");
style.setPdfEncoding("Identity-H");
style.setPdfEmbedded(true);
return style;
}

public static JRStyle getHeadStyle() {
JRDesignStyle style = new JRDesignStyle();
style.setName("headStyle");
style.setDefault(true);
style.setFontName("Arial");
style.setFontSize(8);
style.setBold(true);
style.setPdfFontName("/TTF/arial.ttf");
style.setPdfEncoding("Identity-H");
style.setPdfEmbedded(true);
return style;
}

public static JRDesignElement staticTextElement(String text,
int height, int width,
int x, int y,
boolean headStyle) {
JRDesignStaticText staticText = new JRDesignStaticText();
staticText.setText(text);
staticText.setWidth(width);
staticText.setHeight(height);
staticText.setX(x);
staticText.setY(y);
staticText.setBorder(JRGraphicElement.PEN_THIN);
if (headStyle) staticText.setStyle(getHeadStyle());

return staticText;
}

public static JRDesignElement textFieldElement(String text, Class type,
int height, int width,
int x, int y,
boolean headStyle) {
JRDesignTextField textField = new JRDesignTextField();
textField.setWidth(width);
textField.setHeight(height);
textField.setX(x);
textField.setY(y);
textField.setBorder(JRGraphicElement.PEN_THIN);
if (headStyle) textField.setStyle(getHeadStyle());

JRDesignExpression expression = new JRDesignExpression();
expression.setText(text);
expression.setValueClassName(type.getName());
textField.setExpression(expression);

return textField;
}

public static JRDesignElement textFieldElement(Property property,
int height, int width,
int x, int y,
boolean headStyle) {
JRDesignTextField textField = new JRDesignTextField();
textField.setWidth(width);
textField.setHeight(height);
textField.setX(x);
textField.setY(y);
textField.setBorder(JRGraphicElement.PEN_THIN);
if (headStyle) textField.setStyle(getHeadStyle());

JRDesignExpression expression = new JRDesignExpression();
expression.setValueClassName(property.getType().getName());
if (property instanceof Column) {
expression.addFieldChunk(property.getName());
}
else {
expression.addParameterChunk(property.getName());
}
textField.setExpression(expression);

return textField;
}


Ініціалізація звіту:

JasperDesign reportDesign = new JasperDesign();
reportDesign.setName(context.getDataResult().getDataReport().reportType().getReportName());
reportDesign.setOrientation(JasperDesign.ORIENTATION_LANDSCAPE);
reportDesign.setIgnorePagination(true);
reportDesign.setTopMargin(5);
reportDesign.setBottomMargin(5);
reportDesign.setLeftMargin(5);
reportDesign.setRightMargin(5);
reportDesign.setWhenNoDataType(JasperDesign.WHEN_NO_DATA_TYPE_NO_PAGES);

reportDesign.addImport("java.lang.*");
reportDesign.addImport("java.math.*");
reportDesign.addImport("java.util.*");
reportDesign.addImport("net.sf.jasperreports.engine.*");
reportDesign.addImport("net.sf.jasperreports.engine.data.*");

reportDesign.addStyle(getDefaultStyle());
reportDesign.addStyle(getHeadStyle());


Заповнення звіту:

JRDesignBand rootBand = new JRDesignBand();
rootBand.setHeight(40);
rootBand.setSplitAllowed(true);
reportDesign.setDetail(rootBand); // could be setTitle() or setSummary() etc.

rootBand.addElement(textFieldElement("Details", String.class,
height, 120, currentX, currentY, false));



Компіляція звіту:

JasperReport compileReport(JasperDesign reportDesign) throws ReportException {
try {
log.info("Compiling report...");
return JasperCompileManager.compileReport(reportDesign);
}
catch (Exception e) {
log.error("Error to compile report, cause:", e);
throw new ReportException("Error to compile report!", e);
}
}


Використання отриманого JasperReport:

JasperDesign reportDesign = new JasperDesign();

//
// preparing and filling report design goes here...
//

JasperReport jasperReport = compileReport(jasperDesign);
JasperPrint jasperPrint = JasperFillManager.fillReport(
jasperReport, prepareParameters(), prepareDataSource());

1 comment:

Ruslan Khmelyuk said...

Як я вже раніше десь казав, на мій погляд, є кращий варіант, менш болючіший, менше коду писати, а відповідно і менше баг. Полягає він у використання бібліотек для роботи з шаблонами. Наприклад, ті ж FreeMarker та Velocity. На крайній випадок, використати JSP або GSP.