Report in

Рубрика: JasperReports | 13 March 2007, 09:42 | juriy

Рапорт на стол.
Или короткая история о том, как надо писать отчеты.

Все статьи цикла о JasperReports

Если вы хоть раз писали серьезное бизнес-приложение, то вам наверняка знакомо желание заказчиков, пользователей или начальников пользователей контролировать каждый её вздох.
Все начинается довольно безобидно: простые выборки, оформленные в виде табличек, всех устраивают и радуют глаз незамысловатым примитивизмом. Вскоре таблички становятся объемнее и пользователи просят “подсветить” те показатели, которые существенно отклоняются от нормы.
Запросы к базе тоже не стоят на месте: они растут и усложняются, включают в себя все более запутанные связи и зависимости.
На некотором этапе оказывается, что анализировать массивы чисел неудобно, а представленные результаты выглядят ненаглядно. Тогда к арсеналу табличек добавляется арсенал графиков. О, как же все они помешаны на графиках: больших и маленьких, круговых и сплайнах, двумерных и трехмерных, и побольше, побольше!
В этот момент понимаешь, что обойтись простыми функциями вроде “resultSetToHtmlTable” уже не выйдет. В схватку вступают объекты, представляющие аггрегированные данные и объекты, умеющие красиво отобразить первые объекты.
В этот момент начинаешь понимать, что попал в “смоляную яму”. Чем больше двигаешься, тем больше тебя засасывает.
Но заказчику как правило этого недостаточно. Дальше заказчик хочет получить экспорт в пару-тройку “офисных” форматов, поскольку… да в общем причин может быть масса.

Только не подумайте, что я считаю требования бизнес заказчиков абсолютной чушью. Впрочем, я немного отклонился от темы.

Так случилось, что отчеты стали занимать слишком много времени. Кроме того, в процессе разработки нового отчета меня не покидало чувство изобретения велосипеда. Совершенно естественно, что я решил попробовать упростить себе жизнь и как можно более автоматизировать этот процесс.

Первое что попалось на глаза – BIRT: кусок проекта Calisto, прекрасно интегрируется в платформу Eclipse, но, увы, уж слишком тяжеловесен и тормознут. Да и вообще, продукт оставляет впечатление недопеченного пирога, оздобленного сверху шапкой из взбитых сливок: на demo ролике все выглядит очень хорошо, но первые же тесты быстро возвращают на землю.

После BIRT я наткнулся на Jasper. Симпатичная библиотека, о которой и пойдет речь ниже.

Я не буду долго рассуждать о принципах работы библиотеки, а сразу кину “минимальный прожиточный” кусок кода, который покажет, как сделать простой проект a-la “Привет, мир!”. Да, да, именно “привет, мир”, а не “Hello, World!”, потому что после успешного “Hello, World!” я слишком много раз разбивал нос о “железный занавес локализации”.

Начало – вполне классическое: необходимо загрузить свежую версию библиотеки. Домашняя страничка проекта – http://jasperforge.org/sf/projects/jasperreports, а скачать библиотеку можно тут: http://sourceforge.net/projects/jasperreports/ .
Чтобы сгенерировать отчет, необходимо написать 2 файла: первый – xml c разметкой отчета, а второй – класс Java, который с помощью библиотек Jasper сгенерирует отчет по указанному xml.

Вот так будет выглядеть xml с разметкой отчета. Обратите внимание на строку pdfFontName=”c:\tahoma.ttf”. По непонятной причине, если не указать явно путь к файлу шрифта, то PDF генерируется некорректно: русские буквы накладываются друг на друга.

<?xml version="1.0" encoding="windows-1251"?>
<!DOCTYPE jasperReport
  PUBLIC "-//JasperReports//DTD Report Design//EN"
  "http://jasperreports.sourceforge.net/dtds/jasperreport.dtd">

<jasperReport name="simple">

	<style
		name="Normal"
		isDefault="true"
		pdfFontName="c:\tahoma.ttf"
		pdfEncoding="Cp1251"
	 />

	<detail>
		<band height="20">
			<staticText>
				<reportElement x="180" y="0" width="200" height="20" />
				<text><![CDATA[Тест !!]]></text>
			</staticText>
		</band>
	</detail>
</jasperReport>

Java класс для генерации отчета выглядит намного проще:

import java.util.HashMap;

import net.sf.jasperreports.engine.JREmptyDataSource;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JasperCompileManager;
import net.sf.jasperreports.engine.JasperExportManager;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.JasperReport;

public class JasperHello {
	public static void main(String[] args) {
		try {
			JasperReport jasperReport = JasperCompileManager
				.compileReport("reports/hello.xml");

			JasperPrint jasperPrint = JasperFillManager.fillReport(jasperReport,
				new HashMap(), new JREmptyDataSource());

			JasperExportManager.exportReportToPdfFile(jasperPrint, "reports/hello_report.pdf");
		} catch (JRException e) {
			e.printStackTrace();
		}
	}
}

Я поместил файл hello.xml в папку reports в корне проекта. В эту же папку будет записан созданный отчет. Скомпилируйте и запустите класс. В папке reports должен был появиться файл hello_report.pdf – это и есть ваш первый отчет.

Как видно, процесс генерации отчета состоит из трех шагов: компиляция отчета, заполнение отчета данными (JasperFillManager.fillReport(…)) и экспорт отчета в файл.
В нашем случае отчет не содержит никаких параметров и не работает с источниками данных, поэтому мы “заполняем” отчет пустышками: new HashMap() и new JREmptyDataSource() не передают в отчет абсолютно ничего.

Чтобы экспортировать отчет в другой формат, к примеру, в HTML, достаточно заменить строку

JasperExportManager.exportReportToPdfFile(jasperPrint,
	"reports/hello_report.pdf");

на строку

JasperExportManager.exportReportToHtmlFile(jasperPrint,
	"reports/hello_report.html");

Для начала – вполне достаточно. На сайте проекта есть масса примеров самых разных отчетов. Это самая лучшая демонстрация возможностей пакета.

Если статья вышла интересной – пишите комменты. В следующей статье я расскажу, как сгенерировать более “продвинутый” отчет, который использует БД в качестве источника данных.

Я создал небольшой видеоролик, который демонстрирует настройку Eclipse для работы с JasperReports, и процесс создания первого отчета. Видео есть тут.

Следующая тема: Работа с БД

Комментариев: 45

45 Responses to “Report in”

Комментарии:

  1. vadim

    Сам недавно столкнулся с необходимостью автоматизировать генерацию отчетов, так что твоя статья – очень кстати.
    Хотелось продолжения – увидеть отчет с таблицой из БД, с некоторыми вычисляемыми значениями и “подсветкой отклонений”.

  2. dimat

    “Hello, world” на русском языке будет “превед красавчег” ;-)

  3. dima2605

    А есть ли возможность передавать из Jav -ы бизнес объекты?
    И можноли получать PDF не в виде файла, а строки (стрима) ?

  4. juriy

    Да, отправлять бизнес-объекты (да и не только бизнес :-) ) в JasperReports можно и нужно.

    Делается это так:

    public static void main(String[] args) {
    	JasperReport jasperReport;
    	JasperPrint jasperPrint;
    	try {
    		jasperReport = JasperCompileManager
    				.compileReport("reports/hello.xml");
    
    		// Создаем Map параметров
    		Map myParams = new HashMap();
    
    		// Заполняем Map параметрами. Параметром может быть
    		// любой java-класс
    		myParams.add("myObject", new MyBusinessObject());
    
    		// На этапе заполнения отчета передаем Map в отчет
    		jasperPrint = JasperFillManager.fillReport(jasperReport,
    				myParams, new JREmptyDataSource());
    		JasperExportManager.exportReportToPdfFile(jasperPrint, "reports/hello_report.pdf");
    	} catch (JRException e) {
    		e.printStackTrace();
    	}
    }
    
    ...
    <parameter name="myObject" class="ua.kiev.voituk.MyBusinessObject" />
    ...
    
    <title>
    	<band height="70">
    		<textField isBlankWhenNull="true" bookmarkLevel="1">
    			<reportElement x="0" y="10" width="515" height="30"/>
    			<textElement textAlignment="Center">
    				<font size="22"/>
    			</textElement>
    			<textFieldExpression class="java.lang.String"><![CDATA[$P{myObject}.callMyMethod()]]></textFieldExpression>
    			<anchorNameExpression><![CDATA["Title"]]></anchorNameExpression>
    		</textField>
    	</band>
    </title>
    ...
    

    Ну а печатать в поток – совсем просто:

    
    try {
    	jasperReport = JasperCompileManager
    			.compileReport("reports/hello.xml");
    	jasperPrint = JasperFillManager.fillReport(jasperReport,
    			new HashMap(), new JREmptyDataSource());
    	JasperExportManager.exportReportToPdfStream(jasperPrint, outputStream);
    } catch (JRException e) {
    	e.printStackTrace();
    }
    
  5. dima2605

    Спасибо ;)

  6. dima2605

    И еще один вопрос. Есть ли инструмент для визуального проектирования осчетов.

  7. Juriy

    Да есть, даже несколько. От создателей JasperReports есть иструмент iReport http://jasperforge.org/sf/projects/ireport .

    Но мой совет – лучше писать шаблоны для отчетов “руками” – так ты контролируешь, что получается. Это как с WYSIWYG редакторами для HTML – ничего хорошего, как правило, не выходит.

    Второй нюанс – мне не удалось заставить iReport создавать русскоязычные отчеты. Если получится – ставлю пиво за рецепт ;-)

    А тут – http://jasperreports.sourceforge.net/gui.tools.html перечислено еще несколько GUI утилит.

  8. dima2605

    Еще раз спасибо.
    По 1. Я предпочитаю смешенное кодирование. Часть руками часть визуально.
    По 2 поводу кирилицы, то у меня проблемм не было. По крайней мере как статический текст.
    В меню “редактировать/свойства отчета” после закладки “more” (название закладки нечитаемо) есть выбор кодировки.

  9. Pafnutiy

    Добрый день.
    Спасибо за вводную статью. Подскажите, пожалуйста, является ли Jasper Reports бесплатным для коммерческого использования? На сайте пишут, что использовать можно везде, но для коммерческих проектов … и.т.д. Так везде или не везде?

  10. vadim

    JasperReports распостраняется по лицензии LGPL (Lesser General Public License)
    Основное отличие этой лицензии от GPL состоит в том, что она может применяться только к компоненте всей программы и не накладывает каких-либо лицензионных ограничений на остальную часть программного комплекса.

  11. juriy

    Немного разъясню.
    Да, вы можете свободно использовать JasperReprots и все его компоненты в своем коммерческом проекте. Вот ссылка на официальный форум, где рассмотрена эта тема:
    http://www.jasperforge.org/index.php?option=com_joomlaboard&Itemid=&func=view&catid=8&id=7255#7255

    Вот краткое резюме по вашему вопросу:

    In the case of JasperReports, the underlying libraries have been chosen so that JasperReports itself does not transfer viral license conditions. So as long as you do not change JasperReports or the underlying libraries, using them as-is, you can use JR without being forced to open source your code.

  12. Pafnutiy

    Спасибо за подробный ответ! Однако, не очень понятен такой момент: если я буду использовать слегка подправленный мною JR (гипотетически, разумеется), то буду обязан открыть свой код? Или меня подводит знание англ-го?

  13. vadim

    Иммено так.
    Если вы “подправите” JR то ваш код должен распостраняться по лицензии какая предусматривает “modification for the customer’s own use and reverse engineering for debugging such modifications.”
    И если я ничего не путаю, ваши изменения должны быть обратно совместимы с той версией, какую вы модифицируете.

  14. vadim

    P.S. Русский перевод LGPL http://ru.wikisource.org/wiki/GNU_Lesser_General_Public_License

  15. Pafnutiy

    Большое спасибо!

  16. Anna Safronova

    Спасибо огромное за статью, но у меня при копировании Вашего примера один в один возникли некторые ошибки – был не найден файл для компиляции отчёта (1) и Digester не смог правильно пропарсить xml-файл (2).

    проблемы были решены следующим образом (может, понадобиться, кому ;) )

    1) String jrXMLPath = this.getServletContext().getRealPath(“/”)+ “reports\\”;
    String reportPath = jrXMLPath.concat(“hello.xml”);
    JasperReport jasperReport = JasperCompileManager.compileReport(reportPath);

    т.е. я прописала полный путь доступа

    2) я исключила из xml-ного файла следующую строку:


    – и прописала язык

  17. Anna Safronova

    исключила вот это -

  18. Anna Safronova

    DOCTYPE jasperReport
    PUBLIC “-//JasperReports//DTD Report Design//EN”
    “http://jasperreports.sourceforge.net/dtds/jasperreport.dtd>

  19. Slava

    1.Необходимо получить многостраничный отчет в виде бланка (спецификация по ЕСКД). На первом листе у бланка штамп одного размера, на всех остальных штамп другого размера. Можно ли как то запрограммировать смену штампа ? Я сам эту проблему решить не смог, в итоге сделал два разных отчета, что заставило извращаться с разбиением данных по двум отчетам.
    2. Делали ли вы отчет в PDF формате с русским текстом ? У меня все русские буквы шлепались в одну точку. Пришлось отказаться от PDF формата.

  20. Juriy

    Насчет первого пункта, я думаю проблему можно решить, используя условие printWhenExpression и проверяя значение переменной PAGE_NUMBER. Если надо – напишу подробнее.

    Насчет второго вопроса – с этой проблемой сталкивался. Решается следующим образом:

    [xml]
    <style name=”Normal”
    isDefault=”true”
    fontName=”Arial”
    fontSize=”10″
    isBold=”false”
    isItalic=”false”
    isUnderline=”false”
    isStrikeThrough=”false”
    pdfFontName=”c:\tahoma.ttf”
    pdfEncoding=”Cp1251″
    isPdfEmbedded=”false” />
    [/xml]

    Обратите внимание на то, что кодировка pdfEncoding указана явно и pdfFontName ссылается непосредственно на файл со шрифтом. В таком формате работает нормально (проверено).

  21. juriy

    2 Anna Safronova

    Дело в том, что пример я писал из расчета, что JasperReports будет запускаться из Java SE среды, а не из сервлета. Для сервлетов я задумывал отдельную заметку, но все не доходят руки.

  22. Slava

    Спасибо за быстрый ответ, но пробовать буду уже после отпуска :)
    C условием printWhenExpression мысль интересная, но так как надо будет пометить очень много элементов этим условием, а также появляются доп.строки данных, то может оказаться тоже неудобно. Будет результат эксперимента, напишу.

  23. AndreyS

    А можно ли отчет сохранять в виде обычного текстового файла .txt ?
    И как отчет конвертировать просто в обычную строку текста (стрим), не html или pdf ?

  24. Stas

    При запуске выкинул, что ему нужно com/lowagie/text/pdf/FontManager
    Но на sourceforge ничего про зависимоти не сказано :)…

  25. Juriy

    IText видимо не в classpath. Нужно качать архив с названием ***project – там помимо самого JasperReports лежат все зависимости.

  26. ban

    Юрий, Вадим и Артем – огромное спасибо за информацию, помогли быстро разобраться. Небольшой вопрос, может быть, не в тему, но я все-таки поинтересуюсь – возможно-ли как-то подавить сообщения aplet alert, появляющиеся в процессе создания отчета и сопровождающие все обращения к лок. ресурсам, предлагающие allow, disallow, stop applet, чтобы всегда было allow? Скажем, если генерация отчета выполняется в сервлете?

  27. ban

    При первом обращении последовательно предлагается следующее:

    The applet is attempting to invoke the java/lang/System.getProperty() operation on user.dir

    The applet is attempting to invoke the java/lang/System.getPropert() operation on java.class.path

    The applet is attempting to access the “exists” state attributes of the E:\jboss-4.0.1sp1\bin\jasperreports.properties

    The applet is attempting to connect to jar: file:/E:/jboss-4.0.1sp1/server/default/jasperreports-3.5.0-javaflow.jar!/jasperreports_extension.properties

    The applet is attempting to connect to jar: file:/E:/jboss-4.0.1sp1/server/default/lib/jasperreports-3.5.0.jar!/jasperreports_extension.properties

    The applet is attempting to invoke the java/lang/System.getProperties() operation

    Три кнопки в окошке: allow, dissalow, stop applet.

    При всех последующих – только одно соббщение:

    The applet is attempting to invoke the java/lang/System.getProperties() operation

    Если кликать allow, клиент получает отчет. Непонятно, как с этим бороться.

  28. skif18

    при закрытии окна JasperViewer, закрывает и всю прогу. Как выходили из положения?

  29. Ol

    возникает вот такая проблема:
    0 [main] WARN xml.JRBandFactory – The ‘isSplitAllowed’ attribute is deprecated. Use the ‘splitType’ attribute instead.
    Подскажите как решить?

  30. Koss

    при попытке повторить пример у меня создается пустой PDF файл. Все сделал также, правда я написал абсолютный путь до файлов. Подскажите с чем может быть связана засада.

  31. denys

    to Ol: если использовать последний визуальный редактор iReport 3.5.2 то отчет генерится с

    а не

    вообще это баг в библиотеке: они сами ставят дефолтное значение isSplitAllowed и потом сами же ругаются на него – правда отчеты все равно генерятся, это просто предупрежждение.

    мой вопрос к знающим JasperReports: у меня возникло сомнение что создание одного шаблона достаточно для генерации отчета в любом формате: например если взять HTML и PDF – в HTML я например не хочу разбивать на страницы, но это необходимо для PDF. Это только один из примеров – важна концепция основанная на опыте использования: действительно ли можно нормальный сложный отчет написать так что он будет правильно показываться во многих форматах или все-таки надо писать отдельный шаблом под каждый формат?

  32. sfclub

    Для нормальной локализации нужно использовать
    pdfEncoding=”Identity-H”
    И шрифты которые имеют кирилицу
    при это кодировка будет браться из Java.
    Проверено на Windows, mac, linux, solaris.
    Думаю если использовать такую запись и IReport будет работать коректно.

  33. volkodav

    помогите пожалуйста собрать такой проект в jar. Уже на стену лезу, все работает только в eclipse, отдельно – ни в какую

  34. Vladimir

    Помогите разобратья в том как создать отчёт (хотя бы JREmptyDataSource) в NetBeans 6.7.1 + iReport-nb-3.6.0-plugin

  35. Silokhin

    to skif18:
    Это делается очень просто:
    //Предпросмотр отчета.
    JasperViewer.viewReport(JasperPrint jasperPrint, boolean isExitOnClose); //Пишешь false.

  36. Roman

    День добрый!
    Юрий, а Вам не приходилось вставлять в отчеты http ссылки?
    У меня отчеты формируются в веб-приложении и хочется получить в ячейках отчета ссылку на другое веб-приложение.

  37. kv31174

    Зачем каждый раз компилировать отчёт из xml:
    jasperReport = JasperCompileManager
    .compileReport(“reports/hello.xml”)?
    Лучше запускать готовый отчет:
    jasperReport = (JasperReport)JRLoader.loadObject(“reports/hello.jasper”);

  38. Berkov

    Чтобы заставить iReport работать с русскими буквами, в JDBC URL добавьте после имени базы указание на кодировку: ?CHARSET=koi8
    Кроме того может потребоваться указать в свойствах отчёта (дерево слева, корневой элемент – правой кнопко -> Properties -> Properties [...]свойство …character.encoding в CP1251, хотя тут не уверен – нада ли это делать.
    Ну и, конечно, драйвера JDBC должны быть правильными 8)

  39. Egor

    Полностью повторил пример, но вылетает перед строкой компиляции файла .jrxml
    При этом даже ошибки никакой не выдает.
    Попытка сделать сразу заполнить готовый .jasper отчет тоже ни к чему не привела.
    В первом случае выполнение доходит до строчки
    jasperReport = JasperCompileManager.compileReport(“reports/hello.xml”);

    Во втором случае до строки
    JasperPrint jasperPrint = JasperFillManager.fillReport(jasperReport, parameters, new JREmptyDataSource());

    И ниже лог сервера ничего не пишет. Вообще ничего.
    Нет ни html файла, ни сообщений об ошибке.

  40. vdasus

    Кажется продублирую, но забыл там подписаться на ответы:
    Нужен совет куда смотреть:

    Есть проблема с jasperreports – после обновления tomcat на 7 версию при запуске отчета он всё правильно формирует, но выбрасывает сообщение “Session error”. Есть ли другое решение, кроме как вернуться на 6 tomcat?

    Если будет возможность – ответьте в этот топик, пожалуйста.

  41. Евгений

    Здравствуйте. Спасибо большое за статью. Я скопировал ваш пример, но вылетает ошибка:
    Exception in thread “main” java.lang.NoClassDefFoundError: org/apache/commons/digester/Digester
    at net.sf.jasperreports.engine.JasperCompileManager.compileReport(JasperCompileManager.java:146)
    at JasperHello.main(JasperHello.java:20)
    Caused by: java.lang.ClassNotFoundException: org.apache.commons.digester.Digester
    at java.net.URLClassLoader$1.run(URLClassLoader.java:202)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:307)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:248)
    … 2 more
    Java Result: 1

    Не могу понять в чем дело, помогите пожалуйста

  42. Эфмуд

    Заказчик еще хочет конструктор отчетов. Вот уже прошло 4 года с написания статьи, появились ли какие конструкторы отчетов доступные простым смертным а не айтишникам ? )

Leave a Reply