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