Monday, April 21, 2008

Создание Java-расширений для OpenOffice.org

Продолжение здесь.

OpenOffice.org - это не только набор офисных программ, это еще и платформа для разработки приложений и расширений.

В этом посте я расскажу как создать простое расширение для OOo. Мое расширение будет вести журнал активности пользователя, а именно фиксировать когда и какие файлы были открыты, сохранены или распечатаны.

Хорошей отправной точкой для знакомства с программирование для OOo является книга OpenOffice.org 2.3 Developer's Guide (она же в PDF, 17 МБ), на основе которой я и сделал свою реализацию.

API SDK, которым мы будем пользоваться для создания нашего расширения, позволяет получить полный контроль (в том числе удаленно по сети) над офисными приложениями OOo. Достигается это за счет использования технологии компонентного программирования UNO (Universal Network Objects). На сегодняшний день API SDK доступно практически для всех языков программирования, включая Java, C++, CLI (C#, VB.NET), Python и других, включая встроенный скриптовый язык OOo Basic.

Мы будем пользоваться привязкой UNO к языку Java.

Для создания UNO компонентов разработчики OOo предлагают инструменты для интеграции с разными IDE, в частности, есть плагины для Eclipse и NetBeans. NetBeans я не смотрел, хотя в книге есть ссылки только на NetBeans, а вот для Eclipse плагин я посмотрел. Все, что он делает - это позволяет создавать простые компоненты, задействуя утилиты OOo для компиляции собственных IDL интерфейсов в java-классы.

В нашем случае эта возможность не потребуется - мы не будем создавать своих интерфейсов, вместо этого мы будем пользоваться готовыми интерфейсами SDK OOo, в частности com.sun.star.task.XAsyncJob, который позволит интегрировать в окружение OOo наш сервис, который подключится к шине обработчиков событий документов OOo.

Наше расширение мы будем создавать при помощи Maven2. Следует отметить, что у разработчиков OOo есть плагин для интеграции с Maven2 (maven-ooo-plugin), но:

  1. его нет в публичных репозиториях, а можно только скачать исходники из репозитория OOo и собрать плагин самому;
  2. он, также как и плагины для IDE, поддерживает только интеграцию с утилитами OOo, например, для компиляции IDL интерфейсов в java-классы;
  3. он все еще находится на стадии разработки и не поддерживает необходимые функции, например, сборку артефактов в готовый компонент *.oxt.
Вот, что у меня получилось в итоге (см. рисунок)

После запуска mvn clean verify на выходе мы получим готовую сборку расширения activity-monitor-service-1.0.oxt.

Файл oxt (OOo eXTension) представляет собой zip-архив со следующим содержимым:

activity-monitor-service-1.0.oxt \
META-INF \
manifest.xml
activity-monitor-service-1.0.jar \
META-INF \
MANIFEST.MF
org \ ...
description.xml
jobs.xcu

manifest.xml


Файл манифеста, который описывает содержимое этого oxt-архива. Здесь перечислены *.xcu и *.jar файлы.
<?xml version="1.0" encoding="UTF-8"?>
<manifest:manifest>
<manifest:file-entry
manifest:full-path="${artifactId}-${version}.jar"
manifest:media-type="application/vnd.sun.star.uno-component;type=Java" />
<manifest:file-entry manifest:full-path="jobs.xcu"
manifest:media-type="application/vnd.sun.star.configuration-data" />
</manifest:manifest>

activity-monitor-service-1.0.jar


В этом jar'е находятся классы реализации нашего расширения.

У нас, класс, который реализует интерфейс com.sun.star.task.XAsyncJob (org.keyintegrity.ooo.ActivityMonitor), также является регистрационным классом, то есть в нем реализованы два статических метода - фабрика сервиса и регистратор сервиса. Для их реализации в SDK входит два класса-помощника (первый, второй). Так как это статические методы, то они не могут быть навязаны каким-то интерфейсом, про них нужно просто помнить. Интерфейс com.sun.star.lang.XServiceInfo предоставляет OOo информацию о сервисах, которые предоставляет данный компонент.

MANIFEST.MF


Стандартный манифест jar-файла, в котором содержится указание на регистрационный класс нашего сервиса.

Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Created-By: Apache Maven
Built-By: dmitrygusev
Build-Jdk: 1.5.0_06
RegistrationClassName: org.keyintegrity.ooo.ActivityMonitor

description.xml


Файл описания нашего расширения. Такая информация, как версия расширения, версия OOo, для которого оно было разработано и др. хранится здесь.
<?xml version="1.0" encoding="UTF-8"?>
<description xmlns="http://openoffice.org/extensions/description/2006"
xmlns:d="http://openoffice.org/extensions/description/2006"
xmlns:xlink="http://www.w3.org/1999/xlink">
<version value="${version}" />
<dependencies>
<OpenOffice.org-minimal-version value="2.4"
d:name="OpenOffice.org 2.4" />
</dependencies>
</description>

jobs.xcu


Конфигурационный файл, который регистрирует наш сервис, а именно подписывает его на события OnLoad, OnSaveDone, OnSaveAsDone и OnPrint. Сюда также можно включить конфигурационную информацию для сервиса - у нас это имя файла, где ведется журнал.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE oor:component-data SYSTEM "../../../../component-update.dtd">
<oor:component-data oor:name="Jobs" oor:package="org.openoffice.Office"
xmlns:oor="http://openoffice.org/2001/registry"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<node oor:name="Jobs">
<node oor:name="MonitorJob" oor:op="replace">
<prop oor:name="Service">
<value>org.keyintegrity.ooo.ActivityMonitor</value>
</prop>
<node oor:name="Arguments">
<prop oor:name="fileName" oor:type="xs:string"
oor:op="replace">
<value>c:/Temp/activity-monitor-service.log</value>
</prop>
</node>
</node>
</node>
<node oor:name="Events">
<node oor:name="OnLoad" oor:op="fuse">
<node oor:name="JobList">
<node oor:name="MonitorJob" oor:op="replace" />
</node>
</node>
<node oor:name="OnSaveDone" oor:op="fuse">
<node oor:name="JobList">
<node oor:name="MonitorJob" oor:op="replace" />
</node>
</node>
<node oor:name="OnSaveAsDone" oor:op="fuse">
<node oor:name="JobList">
<node oor:name="MonitorJob" oor:op="replace" />
</node>
</node>
<node oor:name="OnPrint" oor:op="fuse">
<node oor:name="JobList">
<node oor:name="MonitorJob" oor:op="replace" />
</node>
</node>
</node>
</oor:component-data>


Естественно, все не так просто, как кажется на первый раз. Если оставить в стороне сложности, связанные с разборками в API OOo (они хотя бы описаны в книге), то на первый фон выходит создание каркаса проекта для расширения OOo и его реализация с Maven2.

Во-первых, как оказалось для версии SDK 2.4.0 в открытых Maven2 репозиториях еще нет артефактов необходимых артефактов. Поэтому пришлось делать их самому. Здесь помог тот самый Eclipse плагин. При создании проектов с его помощью в CLASSPATH проекта вставляются ссылки на необходимые jar'ы. Как оказалось, они идут в поставке самого OOo (даже не SDK):

<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="source"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="lib" path="build"/>
<classpathentry kind="lib" path="C:/dev/bin/ooo-2.4/program/classes/juh.jar"/>
<classpathentry kind="lib" path="C:/dev/bin/ooo-2.4/program/classes/jurt.jar"/>
<classpathentry kind="lib" path="C:/dev/bin/ooo-2.4/program/classes/officebean.jar"/>
<classpathentry kind="lib" path="C:/dev/bin/ooo-2.4/program/classes/ridl.jar"/>
<classpathentry kind="lib" path="C:/dev/bin/ooo-2.4/program/classes/unoil.jar"/>
<classpathentry kind="lib" path="C:/dev/bin/ooo-2.4/program/classes/unoloader.jar"/>
<classpathentry kind="output" path="bin"/>
</classpath>

Именно из этих jar'ов я сделал артефакты и установил себе в локальный репозиторий.

В конце поста есть архив с готовыми артефактами; все что нужно - скопировать их себе в репозиторий.

Структура Maven2 проекта сделана таким образом, что все содержимое папки src/main/oxt будет скопировано в корень oxt-архива, который получается при сборке проекта. Эта папка является ресурсной папкой проекта и при сборке все содержимое фильтруется, так что в файлах конфигурации можно использовать выражения подстановки (например, ${version} в файле description.xml).

Я постарался сделать эту структуру как можно более универсальной, чтобы на ее основе каждый cмог сделать своё собственное расширение для OOo. Может быть кто-нибудь сделает из нее archetype?

Установка расширения в OOo


Чтобы установить расширение достаточно запустить *.oxt файл на выполнение (щелкнуть по нему два раза :), чтобы запустился Extension Manager OOo. Предварительно нужно закрыть все офисные приложения, иначе может быть поврежден реестр OOo.

Жмем OK и расширение установлено.

Примечание: Если устанавливать расширение повторно, то сначала нужно его удалить; в противном случае появится ошибка "Could not create java implementation loader". Правда, если попробовать еще раз - установка пройдет нормально.

Теперь можно проверить наш сервис в работе. Для этого откройте, сохраните или напечатайте какой-нибудь документ из любого приложения OOo. В файле журнала появятся записи, что-то вроде:

Mon Apr 21 00:19:35 MSD 2008 OnLoad file:///C:/Temp/ActivityMonitor.odt
Mon Apr 21 00:19:38 MSD 2008 OnSaveDone file:///C:/Temp/ActivityMonitor.odt
Mon Apr 21 00:19:45 MSD 2008 OnSaveAsDone file:///C:/Temp/ActivityMonitor-Modified.odt

Отладка расширения


Чему я всегда восхищался в отладчиках, так это возможности подключаться к любому процессу (локальному или удаленному). В Java это делается очень просто, благодаря JPDA.

При запуске OOo регистрирует все обработчики в своей шине событий на основании конфигурационных файлов (*.xcu). Когда очередное событие срабатывает оно идет по цепочке обработчиков и если очередной обработчик написан на Java, то OOo запускает JVM, в которую загружает этот обработчик.

Список виртуальных машин, доступных OOo, можно найти в меню Сервис | Параметры... ветка OpenOffice.org/Java.

Для того, чтобы к JVM можно было подключиться по JDWP нужно ее запускать с ключиками:

-Xdebug
-Xrunjdwp:transport=dt_socket,server=y,address=8000,suspend=n


Эти ключики можно вписать в окошке Параметры... конкретной JRE.

После этого к нашему сервису можно подключаться, например, из Eclipse. Для этого нужно выбрать Run | Open Debug Dialog... Создать новую конфигурацию в ветке Remote Java Application (дважды на ней щелкнуть). Параметры по умолчанию вновь созданной конфигурации подойдут, если в параметры JVM вы добавили точно такие строчки, как я показал выше.

Если нажать Debug, то Eclipse подключится к работающему экземпляру JVM OOo. Можно поставить точки останова, где нужно и ждать, пока вызов дойдет до нужной точки.

Примечание: Если при нажатии Debug Eclipse скажет "Connection refused" это значит, что OOo еще не загрузил экземпляр JVM, потому что наш обработчик еще не был ни разу вызван.


Примечание: В путях к OOo и SDK не должно быть пробелов. Для этих целей я пользуюсь утилитой junction, чтобы создавать символьные ссылки. Например, для OOo я сделал такую ссылку:

junction.exe c:\dev\bin\ooo-2.4 "c:\Program Files\OpenOffice.org 2.4"

Примечание: Все "раскрашенные" исходники я сделал с помощью Eclipse-плагина java2html и GNU Source-Highlight.

Файлы для загрузки


Файл проекта (исходники + собранный файл *.oxt) (13 КБ)
Maven2 артефакты SDK OOo 2.4 (1.4 МБ)

Примечание: Чтобы установить артефакты себе в локальный Maven2 репозиторий нужно распаковать архив в папку <userdir>/.m2/repository.

Примечание: Чтобы собрать проект и получить oxt файл, нужно выполнить mvn clean verify.

Примечание: Чтобы загрузить исходники в Eclipse, нужно предварительно выполнить mvn eclipse:eclipse. Затем в Eclipse File | Import... | Existing projects into Workspace | Next > выбрать папку с проектом и нажать Finish.