21 июня 2010 г.

Apache FOP: Встраивание в Java

Как встраивать FOP в приложения Java
Мой перевод статьи с "официального" сайта Apache FOP: Embedding.

Обзор

Посмотрите Запуск FOP для получения важной информации, которая пригодится как для встраивания в приложения, так и для использования из командной строки, типа опций и заметок о производительности.
Чтобы внедрить Apache FOP в ваше приложение, необходимо сначала создать новый экземпляр org.apache.fop.apps.FopFactor. Этот объект может использоваться для запуска нескольких рендерингов. Для каждого запуска необходимо создать новый экземпляр org.apache.fop.apps.Fop с помощью одного из фабричных методов FopFactory. В вызова методе вы указываете, какой формат (например, Renderer) предполагается к использованию, и, если выбранный рендеринг требует OutputStream, используемый OutputStream для результатов рендеринга. Вы можете настроить поведение рендеринга FOP, создавая собственный экземпляр FOUserAgent. FOUserAgent можно, например, использовать для установки своего собственного экземпляра Renderer (подробности ниже). Наконец, вы получаете экземпляр SAX DefaultHandler от объекта Fop и используете его как SAXResult в вашей трансформации.
Note. Недавно мы изменили внешний API FOP к тому виду, который мы считаем окончательным видом API. Это может потребовать внесения некоторых изменений в приложения. Основными причинами этих изменений были повышение производительности за счет улучшения повторного использования многоразовых объектов и сокращение использования статических переменных для дополнительной гибкости в сложных условиях.

Основной приём использования

Apache FOP в значительной мере завязан на JAXP. Он использует SAX событий исключительно для получения XSL-FO входного документа. Поэтому хорошо, если вы знаете, кое-что о JAXP (что является хорошим скиллом в любом случае). Давайте посмотрим на основные техники использования FOP ...
Вот основной шаблон рендеринга XSL-FO в файл PDF:
import org.apache.fop.apps.FopFactory;
import org.apache.fop.apps.Fop;
import org.apache.fop.apps.MimeConstants;

/*..*/

// Шаг 1: Создание FopFactory
// (используйте повторно, если планируете рендерить несколько документов!!)
FopFactory fopFactory = FopFactory.newInstance();

// Шаг 2: Установка output stream.
// Замечание: Используйте BufferedOutputStream при возможности повысить производительность (типа как при FileOutputStreams).
OutputStream out = new BufferedOutputStream(new FileOutputStream(new File("C:/Temp/myfile.pdf")));

try {
  // Шаг 3: Построить fop с заданными форматами
  Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, out);

  // Шаг 4: Установка JAXP для использования "identity transformer"
  TransformerFactory factory = TransformerFactory.newInstance();
  Transformer transformer = factory.newTransformer(); // identity transformer
           
  // Шаг 5: Установка input и output для XSLT-трансформации
  // Установка input stream
  Source src = new StreamSource(new File("C:/Temp/myfile.fo"));

  // Результирующие SAX-события (созданные FO) должны быть запайпены (piped) до FOP
  Result res = new SAXResult(fop.getDefaultHandler());
            
  // Шаг 6: Запуск XSLT-трансформации и FOP-процессинга
  transformer.transform(src, res);

} finally {
  //закрытие
  out.close();
}
Давайте подробно обсудим эти 6 шагов в деталях:
  • Шаг 1: Вы создаёте новый экземпляр FopFactory. Экземпляр FopFactory имеет ссылки на информацию о конфигурации и кешированные данные. Важно повторно использовать этот экземпляр, если вы планируете сделать несколько рендерингов документов в течение жизни текущей JVM.
  • Шаг 2: Вы создали OutputStream, куда будут записаны созданные документы. Хорошая идея для повышения производительности буферизовать OutputStream как показано.
  • Шаг 3: Вы создаёте новый экземпляр Fop с помощью одного из фабричных методов FopFactory. Вы указываете желаемый выходной формат. Это делается с помощью MIME-типа требуемого выходного формата (например, "application/PDF"). Можно использовать одну из MimeConstants .* констант. Второй параметр - OutputStream из шага 2.
  • Шаг 4 Мы рекомендуем вам использовать трансформеры JAXP, даже если вы не собираетесь делать никаких XSLT преобразований для создания XSL-FO файла. Таким образом, вы всегда можете использовать этот же основной паттерн. В примере здесь настраивается так называемый "identity transformer", который только передаёт вход (Source) без изменений на выход (Result). Вы не должны работать с SAXParser, если вы не выполняете XSLT преобразований.
  • Шаг 5: Здесь мы создаём вход и выход для преобразования XSLT. Source объект создан таким, чтобы загрузить "myfile.fo" файл. Result устроен таким образом, что выход преобразования XSLT отправляется к FOP. FO-файл отправляется в FOP в виде событий SAX, это является наиболее эффективным способом. Пожалуйста, не сохраняйте промежуточные результаты в файл или буфер памяти, потому что это отрицательно влияет на производительность.
  • Шаг 6: И наконец, мы начинаем XSLT-преобразование, запуском JAXP Transformer. Как только трансформер JAXP начинает отправлять свой вывод FOP, FOP сам начинает его обработку в фоновом режиме. После возврата из transform() FOP также уже закончил преобразования FO-файла в файл PDF, и вы можете закрыть OutputStream.
Tip. Хорошая идея делать все преобразования в блоке try..finally. Если вы закроете OutputStream в разделе finally, это гарантирует, что OutputStream будет закрыта должным образом, даже если во время преобразования происходит исключение.
Если вы не знакомы близко с JAXP-трансформерами, то посмотрите на примеры встраивания ниже. В данном разделе содержатся примеры для всех видов использования. Если вы посмотрите на них, вы должны заметить закономерности в использовании [...]. На первый взгляд это может показаться сложным, но это действительно только сочетание преобразования XSL и запуска FOP. Легко закомментировать часть FOP для отладки, например, для отслеживания ошибки в stylesheet. Вы можете легко вывести результат XSL-FO преобразований XSL в файл, чтобы удостовериться, что эта часть генерирует ожидаемые результаты. Примеры этому могут быть найдены в примерах (см. "ExampleXML2FO").

Логгирование

Логгинг в настоящее время немного другой, чем был в FOP 0.20.5. Мы перешли от Avalon Logging к Jakarta Commons Logging. Хотя с Avalon Logging логгирование было непосредственно конкретного FOP, в настоящее время FOP получает журнал(ы) с помощью статически доступных LogFactory. Это похоже, например, на общую схему, которая используется при работе с Apache Log4J напрямую. Мы называем это "static logging" (Commons Logging, Log4J) в отличие от "instance logging" (Avalon Logging). Как следствие, вы не можете больше дать FOP журнал для каждого отдельного рендеринга. Выводы журналов многочисленных, одновременно запущенных рендерингов FOP отправляется в один и тот же журнал.
Note. Мы знаем, что это может быть проблемой в многопоточных серверных средах, если вы хотите знать, что происходит в каждом отдельном запуске FOP. Мы планируем добавить дополнительные объекты обратной связи с FOP, которые могут быть использованы для получения всех видов обратной связи (проверка сообщений, проблема вёрстки и т.д.). "Static logging" в основном интересен разработчикам, работающим на FOP и для продвинутых пользователей, которые отлаживают FOP. Мы не считаем логи полезными для обычных пользователей FOP. Пожалуйста, потерпите, пока мы сможем добавить эту функцию или приходите и помогайте нам реализовать её. [...]
По умолчанию в Jakarta Commons Logging используется JDK логгинг (доступный в JDKs 1.4 и выше) в качестве бэкэнда. Вы можете настроить Commons Logging использовать альтернативные бэкенды, например Log4J. Чтобы узнать об этом больше, обратитесь к документации по Jakarta Commons Logging.

Обработка XSL-FO

После того, как экземпляр Fop настроен, вызовите getDefaultHandler(), чтобы получить экземпляр SAX DefaultHandler, в который можно отправить SAX события, входящие в XSL-FO документ, который вы хотите отрендерить. Обработка FOP начинается, как только в DefaultHandler вызван startDocument() метод. Обработка останавливается, когда в DefaultHandler вызван метод endDocument(). Пожалуйста, обратитесь к базовому шаблону использования показанному выше, чтобы сделать простой FO документ XSL.

Обработка XSL-FO генерации из XML+XSLT

Если вы хотите настроить процесс генерации XSL-FO из XML с использованием XSLT мы снова рекомендуем , используя стандартные JAXP, сделать XSLT и отправить (piping), генерируемые SAX-события напрямую в FOP. Единственное, что нужно изменить в стандартном шаблоне использования выше, это создание трансформера:
//без использования XSLT:
//Transformer transformer = factory.newTransformer(); // identity transformer

//с использованием XSLT:
Source xslt = new StreamSource(new File("mystylesheet.xsl"));
Transformer transformer = factory.newTransformer(xslt);

Входные Источники

Входной XSL-FO документ всегда приходит в FOP как SAX-поток (см. Parsing Design Document для обоснования). Тем не менее, не всегда ваш входной документ представлен в виде потока SAX. Но с JAXP легко конвертировать различные источники ввода в поток SAX, чтобы вы могли его запихнуть (pipe) в FOP. Это звучит сложнее, чем есть на самом деле. Вам просто нужно создать правильный экземпляр Source в качестве входных данных для преобразования JAXP. Несколько примеров:
  • URL: Source src = new StreamSource("http://localhost:8080/testfile.xml");
  • File: Source src = new StreamSource(new File("C:/Temp/myinputfile.xml"));
  • String: Source src = new StreamSource(new StringReader(myString)); // myString is a String
  • InputStream: Source src = new StreamSource(new MyInputStream(something));
  • Byte Array: Source src = new StreamSource(new ByteArrayInputStream(myBuffer)); // myBuffer is a byte[] here
  • DOM: Source src = new DOMSource(myDocument); // myDocument is a Document or a Node
  • Java объект: пожалуйста, посмотрите на примеры встраивания, которые содержат пример этого.
Возможен целый ряд предшествующих манипуляций с данными. Например, у вас может быть DOM и таблицы стилей, или вы можете установить переменные в таблице стилей. Документация по интерфейсу и некоторые рецепты этих ситуаций приводится в Xalan Основы использования шаблонов.

Настройка Apache FOP программно

Apache FOP обеспечивает два уровня, на котором можно настроить поведение FOP: FopFactory и user agent.

Настройка FopFactory

FopFactory содержит конфигурационные данные и ссылки на объекты, которые могут повторно использоваться в течение нескольких рендерингов. Важно, чтобы его экземпляр создавался только один раз (за исключением особых случаев) и использовался каждый раз для создания новых экземпляров FOUserAgent и Fop. Вы можете установить всё это у FopFactory:
  • базовый URL шрифтов для использования при разрешении относительных URL-адресов для шрифтов. fopFactory.setFontBaseURL("file:///C:/Temp/fonts");
  • базовый URL для использования при решении относительных URL-адресов для моделей слогоделения. fopFactory.setHyphenBaseURL("file:///C:/Temp/hyph");
  • Отключение строгой проверки. Если отключено, то FOP менее строг с правилами, установленными FO-спецификациями XSL. fopFactory.setStrictValidation(false);
  • Включить альтернативный свод правил для отступов текста, который пытается имитировать поведение многих коммерческих реализаций, которые решили разорвать спецификации в этом отношении. По умолчанию эта опция "ложно", что указывает Apache FOP вести себя так, как описано в спецификации. Для того чтобы включить альтернативное поведение, вызовите: fopFactory.setBreakIndentInheritanceOnReferenceAreaBoundary(true);
  • Установить разрешение источника для документа. Это используется внутри для определения размера пикселя для SVG изображений и растровых изображений без информации о разрешении. По умолчанию: 72 точек на дюйм. fopFactory.setSourceResolution(96); // =96dpi (точек/пикселов в дюйме)
  • Вручную добавить экземпляр ElementMapping. Если вы хотите поставить специальное расширение FOP (extension) вы можете дать экземпляр FOUserAgent. Как правило, FOP расширения могут автоматически обнаруживаться (см. документацию по расширению). fopFactory.addElementMapping(myElementMapping); // myElementMapping is a org.apache.fop.fo.ElementMapping
  • Установить URIResolver для custom-разрешения URI. Предоставляя JAXP URIResolver вы можете добавлять пользовательские разрешения URI в функциональность FOP. Например, вы можете юзать Apache XML Commons Resolver для использования Xcatalogs. fopFactory.setURIResolver(myResolver); // myResolver is a javax.xml.transform.URIResolver
Note. И в FopFactory и в FOUserAgent есть метод для установки URIResolver. URIResolver из FopFactory в основном используется для решения URI на фабричном уровне (слогоделительные шаблоны, например), и он всегда используется, если нет других URIResolver (например, из FOUserAgent), которые первыми разрешат URI.

Настройка User Agent

user agent является сущностью, которая позволяет взаимодействовать с одним запуском рендеринга, т. е. обработкой одного документа. Если вы хотите настроить поведение пользовательского агента, первым шагом является создание собственного экземпляра FOUserAgent, используя соответствующий фабричный метод FopFactory, и передать его другому фабричному методу, который создаст новый экземпляр Fop:
FopFactory fopFactory = FopFactory.newInstance(); // Reuse the FopFactory if possible!
// do the following for each new rendering run
FOUserAgent userAgent = fopFactory.newFOUserAgent();
// customize userAgent
Fop fop = fopFactory.newFop(MimeConstants.MIME_POSTSCRIPT, userAgent, out);
Вы можете делать всякие вещи с user agent:
  • Базовый URL, используемый при решении относительных URL-адресов. userAgent.setBaseURL("file:///C:/Temp/");
  • Установить продюссера документа. Это мета-информация, которая может быть использована для определённых форматов, таких как PDF. По умолчанию продюссер "Apache FOP". userAgent.setProducer("MyKillerApplication");
  • Установить создателя документа. Это мета-информация, которая может быть использована для определённых форматов, таких как PDF. userAgent.setCreator("John Doe");
  • Установить автора документа. Это мета-информация, которая может быть использована для определённых форматов, таких как PDF. userAgent.setAuthor("John Doe");
  • Заменить дату и время создания документа. Это мета-информация, которая может быть использована для определённых форматов, таких как PDF. userAgent.setCreationDate(new Date());
  • Установить заголовок документа. Это мета-информация, которая может быть использована для определённых форматов, таких как PDF. userAgent.setTitle("Invoice No 138716847");
  • Набор ключевых слов документа. Это мета-информация, которая может быть использована для определённых форматов, таких как PDF. userAgent.setKeywords("XML XSL-FO");
  • Установить разрешение выходного документа. Это используется для указания выходного разрешения для растровых изображений порождённых растровой визуализацией (например, TIFF рендеринг) и растровых изображений порождённых Apache Batik. По умолчанию: 72 точек на дюйм. userAgent.setTargetResolution(300); // =300dpi (точек/пикселов в дюйме)
  • Установить собственный экземпляр Renderer. Если вы хотите ввести собственную визуализацию или настроить Renderer специальным образом вы можете сделать экземпляр FOUserAgent. Обычно экземпляр Renderer создаётся FOP. userAgent.setRendererOverride(myRenderer); // myRenderer is an org.apache.fop.render.Renderer
  • Установить собственный экземпляр FOEventHandler. Если вы хотите ввести собственный FOEventHandler или настроить подкласс FOEventHandler специальным образом вы можете дать экземпляр FOUserAgent. Обычно FOEventHandler создаётся FOP. userAgent.setFOEventHandlerOverride(myFOEventHandler); // myFOEventHandler is an org.apache.fop.fo.FOEventHandler
  • Установить URIResolver для пользовательских разрешений URI. Предоставляя JAXP URIResolver вы можете добавлять пользовательские разрешения URI в функциональность FOP. Например, вы можете юзать Apache XML Commons Resolver для пользования Xcatalogs. userAgent.setURIResolver(myResolver); // myResolver is a javax.xml.transform.URIResolver
Note. И в FopFactory и в FOUserAgent есть метод для установки URIResolver. URIResolver на FOUserAgent используется для решения document-related URI. Если он не установлен или не может решить URI, то используется URIResolver от FopFactory.
Note. Не нужно (хотя можно) повторно использовать FOUserAgent между запусками рендеринга FOP. Особенно это плохая идея в многопоточной среде.

Использование конфигурационных файлов

Вместо того, чтобы устанавливать параметры вручную в коде, как показано выше, можно также установить множество значений из файла конфигурации в формате XML:
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder;

/*..*/

DefaultConfigurationBuilder cfgBuilder = new DefaultConfigurationBuilder();
Configuration cfg = cfgBuilder.buildFromFile(new File("C:/Temp/mycfg.xml"));
fopFactory.setUserConfig(cfg);

/* ..или.. */

fopFactory.setUserConfig(new File("C:/Temp/mycfg.xml"));
Расположение конфигурационного файла описано на странице конфигурации.

Подсказки

Повторное использование объектов

Экземпляры Fop не должны (и не могут) быть использован повторно. Пожалуйста, воссоздавайте с помощью FopFactory экземпляры Fop и FOUserAgent для каждого рендеринга. Это дешёвые операции, а вся многоразовая информация хранится в FopFactory. Вот почему так важно повторно использовать экземпляр FopFactory.

Проблема AWT

Если XSL-FO файлы содержат SVG, то будет использоваться Apache Batik. Когда Batik инициализируется он использует определённые классы в java.awt что приводит к инициализации Java AWT классов. Это означает, что демон поток создаётся JVM и на Unix ей необходимо будет подключиться к DISPLAY. Нить означает, что приложение Java не может автоматически завершить работу, для завершения нужно вызвать System.exit(). These issues should be fixed in the JDK 1.4. Если у вас возникли проблемы с работой FOP на безголовом сервере см. заметки о Батик.

Получение информации о процессе рендеринга

Чтобы получить число страниц, которые были срендерены FOP можно вызвать Fop.getResults(). Возвращается объект FormattingResults, где можно посмотреть количество произведённых страниц. Это также даёт вам последовательности страниц, которые были подготовлены вместе с их id-атрибутами и номера страниц. Это особенно полезно, если вы рендерите нескольких документов [...], и нужно знать количество страниц каждого документа.

Повышение эффективности

Есть несколько вариантов для рассмотрения:
  • Когда это возможно, попробуйте использовать SAX для соединения отдельных участвующих компонентов (парсер, XSL трансформер, источник данных SQL и т. д.).
  • В зависимости от целевой OutputStream (например, в случае FileOutputStream, но не для ByteArrayOutputStream), можно значительно повысить производительность, если буферизовать OutputStream с помощью BufferedOutputStream: out = new java.io.BufferedOutputStream(out); Убедитесь, что вы правильно закрыли OutputStream, когда работа FOP закончена.
  • Кэш таблицы стилей. Если вы используете один и тот же stylesheet несколько раз, вы можете настроить JAXP Templates объект и использовать его каждый раз при XSL трансформации. (Более подробную информацию можно найти здесь.)
  • Используйте XSLT компилятор, типа XSLTC, который поставляется с Xalan-J.
  • Тонко тюнингуйте ваши stylesheet, чтобы сделать XSLT процесс более эффективным и создавайте XSL-FO, которые могут быть обработаны FOP более эффективно. Лучше меньше да лучше: пробуйте использовать property inheritance, где это возможно.

Многопоточность FOP

Apache FOP в настоящее время не может быть полностью потокобезопасными. Код пока не был полностью протестирован на многопоточность. Если вы заметите что-либо подозрительное, пожалуйста, сообщите нам. Существует также известные проблемы с перемешиванием шрифтов между потоками при использовании Java2D/AWT-рендер (который используется при выходных параметрах -awt и -print). В общем, вы не можете безопасно пропускать несколько потоков через AWT-рендер.

Примеры

Каталог "{fop-dir}/examples/embedding" содержит несколько рабочих примеров.

ExampleFO2PDF.java

Этот пример демонстрирует основные модели использования для преобразования XSL-FO файла в PDF с использованием FOP.

ExampleXML2FO.java

Этот пример не имеет ничего общего с FOP. Здесь показано, как файл XML может быть преобразован в XSL-FO с использованием XSLT. JAXP API используется для выполнения преобразования. Убедитесь, что вы имеете JAXP-совместимый процессор XSLT в вашем пути к классам (например Xalan).

ExampleXML2PDF.java

Этот пример демонстрирует, как можно преобразовать произвольный XML файл в PDF использованием XSLT и XSL-FO/FOP. Он представляет собой комбинацию первых двух примеров выше. В этом примере используется JAXP для преобразования XML файл XSL-FO и FOP для преобразования XSL-FO в PDF.
Выход (XSL-FO) в результате преобразования XSL отправляется по трубе до FOP, используя SAX-события. Это самый эффективный способ сделать это, потому что промежуточные результаты не сохраняются куда-либо. Часто начинающие пользователи сохраняют промежуточный результат в файл, в массив байтов или в дерево DOM. Мы настоятельно рекомендуем вам не делать этого, если это не является реально необходимым. Производительность значительно выше через SAX.

ExampleObj2XML.java

Этот пример является подготовительным примером для следующего. Это пример того, как произвольный Java объект может быть преобразован в XML. Это часто бывает необходимо. Часто люди создают DOM дерево объектов Java и использует её. Это очень просто. Например здесь показано, как сделать это, используя SAX, что, вероятно, будет быстрее, а не ещё более сложно, если только вы знаете, как это работает.
В этом примере мы создали два класса: ProjectTeam и ProjectMember (находится в xml-fop/examples/embedding/java/embedding/model). Они представляют собой ту же структуру данных, которая содержится в xml-fop/examples/embedding/xml/xml/projectteam.xml. Мы хотим сериализации в XML проектной группы с несколькими членами, которые существуют как объекты Java. Поэтому мы создали два класса: ProjectTeamInputSource и ProjectTeamXMLReader (в том же месте, ProjectTeam выше). Имплементация XMLReader (рассматривайте его как особый вид XML-парсера) отвечает за создание событий SAX от объекта Java. The InputSource class is only used to hold the ProjectTeam object to be used. Обратите внимание на источник ExampleObj2XML.java, чтобы узнать, как подобное использовать. Для получения более подробной информации см. другие ресурсы на JAXP [...].

ExampleObj2PDF.java

Этот пример сочетает в себе предыдущие, и третий, чтобы продемонстрировать, каким образом можно преобразовать Java-объект в PDF напрямую в один-единственный запуск, генерируя события SAX от объекта Java, которые потом подаются к преобразованиям XSL. Результат преобразования затем преобразуется в PDF с использованием FOP, как раньше.

ExampleDOM2PDF.java

В этом примере FOP использует DOMSource вместо StreamSource для использования DOM-дерева в качестве входных данных для XSL-преобразования.

ExampleSVG2PDF.java (PDF-транскодер, например)

Этот пример показывает использование PDF-транскодера, как sub-application внутри FOP. Он используется для создания документов PDF из файлов SVG.

Заключительное слово

Эти примеры должны дать вам представление о возможностях. Легко приспособить эти примеры под ваши нужды. Кроме того, если у вас есть другие примеры, которые вы считаете, следует добавить сюда, пожалуйста, сообщите нам через fop-user или fop-dev рассылки. Наконец, для получения дополнительной помощи шлите ваши вопросы в fop-users.

Версия 632784

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

Отправить комментарий