Lotus 8.5 and Jasper Report
Извечная проблема лотусовой платформы - отсутствие средств формирования отчетов.
Отчеты, графики, печать на бланках - от этих слов у лотусового разработчика начинаются некотролируемые приступы идиосинкразии, головной боли и желания взять на недельку отпуск за свой счет...
Т.к. стандарного решения нет - каждый проходит этот квест своим путем:
строит очередную вьюху или папку
пишет дикий код, формирующий отчет в RT поле.
собирает собственный велосипед оригинальной конструкции.
Вьюхи, папки и RT поля обсуждать не хочется: убить, расчленить и сжечь.
Велосипеды характерны наличием следущих шагов:
формирование выборки: обычно это некая коллекция документов.
применение преобразования к выборке для получения результата, типа:
перегон коллекции в xml, применение шаблона xsl
заполнение шаблона Word
....
Изобретение велосипеда - занятие безусловно полезное, т.к. обычно развивает проф. навыки разработчика и само по себе является интересной задачей. Однако, рано или поздно, эта стадия проходит: разработчик начинает тихо подозревать, что подобную задачу уже кто-то решал до него.
Создав несколько великолепных велосипедов, я также начал искать нормальное решение. Собственно поиск занял около двух минут. Итак, JasperReport.
link removed
Ссылка скрыта от гостей
Ссылка скрыта от гостей
Штука классная, бесплатная, простая и с лихвой покрывает любые хотелки клиента. Основная беда в том, что большинство разработчиков старой закалки боятся java, как бандерлоги боялись того питона...
Интеграция внешних java библиотек в лотусовое окружение - задача не всегда простая. Иерархия доминошных класслоадеров велика и извилиста, как индусская река Ганг. А любовь индусов перепаковывать внешние либы устаревших версий и вставлять их в труднодоступные места продукта только добавляет радости бедному разработчику.
Пример: включение xstream-1.2.2 в домино 8.5. Версия от 2007 года, лежит в
%lotusHome%/xsp/nsf/lib. Грузится класслоадером от xpage - после классов из
lib/ext но до классов из
web-inf/lib. При конвертации в json получаем радостный NoClassDefFoundError уже не вспомню для кого. Решается установкой свежей версии xstream и jettison в
lib/ext.
Кодить на java под домино без нормального эклипса - унылое занятие. Кодить на java не используя средств автоматической сборки - идиотизм. Соответсвенно нам потребуюца
Ссылка скрыта от гостей
и
Ссылка скрыта от гостей
. Ленивые могут скачать
Ссылка скрыта от гостей
- там все уже установлено и настроено. После установки всего этого добра не забываем добавить путь к мавену в системный
PATH
mvn archetype:create -DgroupId=ru.turumbay -DartifactId=jasper-example
Добавляем в
pom.xml:
XML:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.6</source>
<target>1.6</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
...
Создаем
src/main/resources/report.xml:
XML:
<!DOCTYPE jasperReport
PUBLIC "-//JasperReports//DTD Report Design//EN"
"http://jasperreports.sourceforge.net/dtds/jasperreport.dtd">
<jasperReport name="myReport">
<style name="default" isDefault="true" pdfFontName="tahoma.ttf" pdfEncoding="Cp1251" isPdfEmbedded="true" />
<detail>
<band height="20">
<staticText>
<reportElement x="180" y="0" width="200" height="20" forecolor="#006699" backcolor="#E6E6E6" />
<text><![CDATA[Превед, jasper!]]></text>
</staticText>
</band>
</detail>
</jasperReport>
src/main/java/JasperExample.java:
Java:
package ru.turumbay;
import net.sf.jasperreports.engine.*;
public class JasperExample {
public static void main(String... args) throws JRException{
JasperReport jasperReport = JasperCompileManager
.compileReport( JasperExample.class.getClassLoader().getResourceAsStream("report.xml"));
JasperPrint jasperPrint = JasperFillManager.fillReport(jasperReport, null, new JREmptyDataSource());
JasperExportManager.exportReportToPdfFile(jasperPrint, "preved.pdf");
}
}
Запускаем на исполнение и получаем выхлоп:
23.02.2011 21:24:51 net.sf.jasperreports.engine.xml.JRBandFactory createObject
WARNING: The 'isSplitAllowed' attribute is deprecated. Use the 'splitType' attribute instead.
Exception in thread "main" net.sf.jasperreports.engine.JRRuntimeException: Could not load the following font :
pdfFontName : tahoma.ttf
pdfEncoding : Cp1251
isPdfEmbedded : true
at net.sf.jasperreports.engine.export.JRPdfExporter.getFont(JRPdfExporter.java:1950)
at net.sf.jasperreports.engine.export.JRPdfExporter.writePageAnchor(JRPdfExporter.java:676)
at net.sf.jasperreports.engine.export.JRPdfExporter.exportReportToStream(JRPdfExporter.java:612)
at net.sf.jasperreports.engine.export.JRPdfExporter.exportReport(JRPdfExporter.java:384)
at net.sf.jasperreports.engine.JasperExportManager.exportReportToPdfFile(JasperExportManager.java:122)
at ru.turumbay.JasperExample.main(JasperExample.java:17)
Немного магии: добавляем в
src/main/resources шрифт:
tahoma.ttf и два файла:
jasperreports_extension.properties:
Код:
net.sf.jasperreports.extension.registry.factory.fonts=net.sf.jasperreports.engine.fonts.SimpleFontExtensionsRegistryFactory
net.sf.jasperreports.extension.simple.font.families.ireport=irfonts.xml
irfonts.xml:
XML:
<?xml version="1.0" encoding="UTF-8"?>
<fontFamilies>
<fontFamily name="Tahoma">
<normal><![CDATA[tahoma.ttf]]></normal>
<pdfEncoding><![CDATA[Cp1251]]></pdfEncoding>
<pdfEmbedded><![CDATA[true]]></pdfEmbedded>
</fontFamily>
</fontFamilies>
Компиляция отчета в рантайме чаще всего не нужна. По жизни, для создания отчетов используется
iReport, предоставляющий GUI для "рисование" отчетов. Компиляция производится им же. Также, для компиляции можно воспользоваться
Maven 2 JasperReports Plugin:
pom.xml
XML:
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>jasperreports-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>compile-reports</goal>
</goals>
<phase>compile</phase>
</execution>
</executions>
</plugin>
запускаем
mvn jasperreports:compile-reports, читаем сообщение об ошибке и:
перемещаем файл с отчетом в
src/main/jasperreports
mvn jasperreports:compile-reports:
[INFO] Scanning for projects...
[INFO] --------------------------------------------------------------
[INFO] Building jasper-example
[INFO] task-segment: [jasperreports:compile-reports]
[INFO] --------------------------------------------------------------
[INFO] [jasperreports:compile-reports {execution: default-cli}]
[INFO] Compiling 1 report design files.
[INFO] Compiling report file: report.jrxml
[INFO] Compiled 1 report design files.
[INFO] --------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
....
видим в
target\classes наш отчет.
Глумица в
src/main - западло и неудобно, поэтому перемещаемся в
src/test и убираем компиляцию из рантайма:
src/test/java/JasperExampleTest.java:
Java:
import lotus.domino.AgentBase;
import lotus.domino.AgentContext;
import lotus.domino.Session;
import net.sf.jasperreports.engine.JREmptyDataSource;
import net.sf.jasperreports.engine.JasperExportManager;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperPrint;
public class JavaAgent extends AgentBase {
public void NotesMain() {
try {
Session session = getSession();
AgentContext agentContext = session.getAgentContext();
// (Your code goes here)
JasperPrint jasperPrint = JasperFillManager.fillReport( this.getClass().getClassLoader().getResourceAsStream("report.jasper") , null, new JREmptyDataSource() );
JasperExportManager.exportReportToPdfFile(jasperPrint, "preved.pdf");
} catch(Exception e) {
e.printStackTrace();
}
}
}
наличие зеленой полоски и файла preved.pdf сигнализирует об успехе. Тест можно сделать реально автоматическим, проверяя контент файла, но щаз не об этом...
Выкидываем ненужные зависимости:
pom.xml
XML:
<exclusions>
<exclusion>
<artifactId>jdtcore</artifactId>
<groupId>eclipse</groupId>
</exclusion>
<exclusion>
<artifactId>jfreechart</artifactId>
<groupId>jfree</groupId>
</exclusion>
<exclusion>
<artifactId>bcmail-jdk14</artifactId>
<groupId>org.bouncycastle</groupId>
</exclusion>
<exclusion>
<artifactId>bcmail-jdk14</artifactId>
<groupId>bouncycastle</groupId>
</exclusion>
<exclusion>
<artifactId>bcprov-jdk14</artifactId>
<groupId>org.bouncycastle</groupId>
</exclusion>
<exclusion>
<artifactId>bcprov-jdk14</artifactId>
<groupId>bouncycastle</groupId>
</exclusion>
<exclusion>
<artifactId>bctsp-jdk14</artifactId>
<groupId>org.bouncycastle</groupId>
</exclusion>
</exclusions>
Сборка всего:
pom.xml
XML:
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
</plugin>
mvn clean assembly:assembly
получился нефиговый файл на 6 мегабайт... В принципе - его можно радикально уменьшить, выкинув неиспользуемые компоненты, но нужно иметь веские причины.
Что мы имеем на данном этапе: собранные в один файл компоненты, достаточные для генерации отчетов, в т.ч.
pdf с кириллицей.
Т.е. никакого кода мы не пока не создали - это просто набор библиотек в удобном виде( один jar файл ).
Теперь собственно самое интересное:
Интеграция в домино
Код скомпилирован под 1.6. Его можно собрать и под 1.5, но по мне проще заставить клиента поднять сервер с 8 до 8.5.
Способ 1. lib/ext way
Самое дубовое и надежное решение
копируем
jasper-0.0.1-SNAPSHOT-jar-with-dependencies.jar в
lib/ext
при этом нам плевать на размер файла, т.к. он доступен локально - т.е. не гоняем трафик по сети.
Агент:
Java:
import lotus.domino.AgentBase;
import lotus.domino.AgentContext;
import lotus.domino.Session;
import net.sf.jasperreports.engine.JREmptyDataSource;
import net.sf.jasperreports.engine.JasperExportManager;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperPrint;
public class JavaAgent extends AgentBase {
public void NotesMain() {
try {
Session session = getSession();
AgentContext agentContext = session.getAgentContext();
// (Your code goes here)
JasperPrint jasperPrint = JasperFillManager.fillReport( this.getClass().getClassLoader().getResourceAsStream("report.jasper") , null, new JREmptyDataSource() );
JasperExportManager.exportReportToPdfFile(jasperPrint, "preved.pdf");
} catch(Exception e) {
e.printStackTrace();
}
}
}
Запускаем агента. Агент отрабатывает но файла нет... Чтобы посмотреть что происходит, нужна включить console: в клиенте
tools->show java debug console.
Тут нас ждет
индусский привет: для того чтобы консоль открылась нужно закрыть дизайнер. После всех мытарств, открываем коносль, стартуем агента и курим примерно такой выхлоп:
net.sf.jasperreports.engine.JRException: Error loading expression class : myReport_1298488674328_724315
at net.sf.jasperreports.engine.design.JRAbstractJavaCompiler.loadEvaluator(JRAbstractJavaCompiler.java:102)
... 14 more
Caused by: java.security.AccessControlException: Access denied (java.lang.RuntimePermission getProtectionDomain)
at java.security.AccessController.checkPermission(AccessController.java:108)
at java.lang.SecurityManager.checkPermission(SecurityManager.java:532)
at COM.ibm.JEmpower.applet.AppletSecurity.superDotCheckPermission(AppletSecurity.java:1449)
at COM.ibm.JEmpower.applet.AppletSecurity.checkRuntimePermission(AppletSecurity.java:1311)
at COM.ibm.JEmpower.applet.AppletSecurity.checkPermission(AppletSecurity.java:1611)
at COM.ibm.JEmpower.applet.AppletSecurity.checkPermission(AppletSecurity.java:1464)
at java.lang.Class.getProtectionDomain(Class.java:1105)
... 19 more
Ключевая фраза:
java.security.AccessControlException: Access denied (java.lang.RuntimePermission getProtectionDomain).
открываем
jvm/lib/security/java.policy и добавляем
permission java.lang.RuntimePermission "getProtectionDomain"; в секцию
grant. Перезапускаем клиента. Пинаем агента.
Время работы < 1 секунды. Результат лежит в
lotushome/preved.pdf
Недостаток способа очевиден - не работает стандартный нотусовый механизм обновления дизайна. Для перехода на более новую версии библиотеки нужно перезакатывать файлы в
lib/ext. Файлы из
lib/ext обычно заблокированы сервером, при добавлении файлов требуется перезапуск сервера и т.п. Прямо скажем, для продакшна не очень удобно...
Те же проблемы, если требуется выполнение кода на клиенте.
Способ №2: agent way
используем элемент дизайна java library, подключенную к агенту.
удаляем нафиг jasper-0.0.1-SNAPSHOT-jar-with-dependencies.jar из
lib/ext. Перезапускаем клиента.
создаем java library, кладем в ресурсы наш файл, сохраняем либу и подключаем ее к агенту.
Первый запуск агента ~ 10 секунд. Последующие - 3-4 сек. Возможно библиотека кэшируется.
//TODO: провести при случае ислледование на тему кэширования элементов дизайна....
Способ в общем неплох. Обновления накатываются автоматически, нет необходимости дергать админов и все такое.
Минус: время холодного старта. Архив весит 6 метров. Если планируется выполнение кода на клиенте, нужно понимать что клиенту придется эти шесть метров выкачать с сервера. На хреновом канале такой способ не прокатит вовсе... Однако выполнение кода всегда можно перебросить на сервер. Т.е. клиент формирует данные для отчета ( кстати, с учетом доступа ) и отдает это добро серверу. В ответ получает сгенеренный репорт.
Какой вариант выбрать - зависит от нескольких факторов.
- Если не планируется выполнени кода на клиенти и сервера под нашим контролем - однозначно
lib/ext.
- Если планируется выполнение на клиенте и есть отличный канал - лучше
agent way. Если клиенты работают в терминалке - опять же проще
lib/ext. В общем универсального решения нет...
- Если требуется решение для веб, то
агенты - не самое правильное решение. Запуск любого агента под веб (?OpenAgent) - крайне тяжелая и медленная операция.
Способ №3. XPage way
to be continiued