Showing posts with label Open Source. Show all posts
Showing posts with label Open Source. Show all posts

Sunday, March 11, 2012

Serving Tapestry5 Assets As Static Resources

In Tapestry5 you use assets to reference *.js*.css or image files from your templates/code. The reference may look like:

    <link rel="stylesheet" type="text/css" href="${context:/css/all.css}" />

During the render phase Tapestry5 converts the ${context:/css/all.css} part to asset URL, which may look like the following (see Asset URLs section here):

    <link rel="stylesheet" type="text/css" href="/assets/stage-20120310/ctx/css/all.css" />

Here "stage-20120310" -- is an application version string, which Tapestry5 adds to asset URLs to manage assets versioning. When running in production Tapestry5 adds a far future expires header for the asset, which will encourage the client browser to cache it.

When you change one of your assets you have to change application version number in your AppModule.java, so that Tapestry5 generate new asset URLs and browser fetched new assets instead of using the ones from cache.

One disadvantage of such approach is that client browser will have to get all the assets once again, not just the one that was changed.

For the majority of assets the asset URL is generated by Tapestry5. Exceptions are assets, that are referenced from *.css files by the relative URL, like this (file all.css):

a.external {
background: transparent url(../images/external.png) no-repeat scroll right center;
display: inline-block;
margin-left: 2px;
height: 11px;
width: 11px;
zoom: 1;
}

In this case browser will form the URL itself relatively to "/assets/stage-20120310/ctx/css/all.css", and the resulting URL will be "/assets/stage-20120310/ctx/images/external.png".

So you have to change application version in AppModule.java if you provide new version of "external.png".

But, for the majority of assets it would be enough to append MD5/SHA1/... checksum as a GET-parameter to asset URL and make them look like:

    <link rel="stylesheet" type="text/css" href="/assets/stage-20120310/ctx/css/all.css?5ef25ac1ec38f119e283f338e6c120a4e53127b1" />

In Tapestry5 you have the ability to provide your own implementation of AssetPathConverter service and append this checksum manually. But, in this interface you only have original asset URL, and don't have the resource itself to calculate the checksum.

There are several ways this may be implemented. Ideally, I'd like this to be implemented in Tapestry5 core.

There's one thing I don't like about Tapestry5 assets handling, though, even if the above solution will be implemented -- is that assets are not static.

This means every asset URL is handled by the Java code, and in most cases assets handling is just streaming of existing files from filesystem to browser (with optional minimization and gzip-compression).

Once the asset was handled, Tapestry5 caches the response and uses it in further responses, but still this is all done in Java.

In Ping Service we've implemented "assets precompilation", and placed all the rendered assets as static files in the web app root folder.

This is done using custom implementation of org.apache.tapestry5.internal.services.ResourceStreamer, which is responsible for streaming every asset to client. During resource streaming we calculate asset checksum and store in a static.properties file, where we put asset URL as a key, and checksum as a value:

#Static Assets For Tapestry5 Application
#Sat Mar 10 19:42:38 UTC 2012
/assets/stage-20120310/ctx/css/all.css=5ef25ac1ec38f119e283f338e6c120a4e53127b1
/assets/stage-20120310/ctx/css/analytics.css=ee470432c344820e43995fb4632ab4bee3b92e38
/assets/stage-20120310/tapestry/t5-prototype.js=95e30b840a5654b82e6a0334a14a2766c57c4d99
...

Our implementation of AssetPathConverter uses this property file to modify asset URLs.

We run our implementation of ResourceStreamer only in production mode, since Google App Engine doesn't allow writing to the filesystem.

Also we've implemented it to work only if special HTTP-header passed with the request. To pass this header and to trigger every asset we have in our application, we use Selenium-powered integration test that queries every single page. We run this test before deploying new version to production.

Now Tapestry5 asset URLs and URLs of static files are the same in our application. So Google App Engine runtime won't even pass the request to Java. Also it uses its own facilities to serve static files, i.e. gzip-compression, etc.

Saturday, January 14, 2012

Simple Sorting Facade for Java (SSF4J)

In Java to sort two or more lists together you have to write a custom solution.

Say, you have list of names and corresponding list of weights. There is no API that allows you to sort names by weights (at least not that I know). However this is very common use case, especially when you analyzing data in your programs.

To achieve this, you, most likely, implement one of the sorting algorithms with a custom swap-logic.

Simple sorting facade is a pattern that already contains implementation of sorting algorithm(s) and only requires developers to specify source list, its bounds, and compare- and the swap-logic.

You can explore SSF4J on GitHub and contribute your implementations of sorting algorithms.

Here's an example of using SSF4J:


Monday, September 05, 2011

Running BIRT Reports in Tomcat

Context: You have database and you need to do data analysis: draw charts, build some tables, calculate totals, etc. You want it all to be available over the web and secured with a password.

Your database is any JDBC-supported database (I use MySQL 5.1.49).
Your server is running any OS where Java can run (I use Ubuntu Linux 10.10 available through SSH).

I will show how to implement this using Eclipse BIRT 3.7.

Developer Environment
  1. Download "Eclipse IDE for Java and Report Developers" package here.
    Unzip to install.
  2. Design new report (I created sales.rptdesign). This is really straightforward.
JDBC Driver. You will need MySQL JDBC driver to create Data Source for report. I got mysql-connector-java-5.1.17-bin.jar here.
Installing the driver in BIRT designer is easy using "Manage drivers..." dialog. But you will probably have problems deploying it to the runtime.

Fonts. You probably won't have any problems with fonts in BIRT designer. And again you will likely have problems with fonts in runtime.

Connection Profile Store. With BIRT 3.7 you can use Connection Profiles to hold database connections. After you've finished designing and testing your report, double click report Data Source to bring properties dialog and create new connection profile store in there. Save it to some file (I saved to planet33_v2.xml).
Here's what I have in there:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<DataTools.ServerProfiles version="1.0">
    <profile autoconnect="No" desc=""
        id="ecc3bc60-d4fd-11e0-957a-e0e31b9a34ee" name="planet33_v2"
        providerID="org.eclipse.datatools.enablement.mysql.connectionProfile">
        <baseproperties>
            <property
                name="org.eclipse.datatools.connectivity.db.connectionProperties"
                value="" />
            <property name="org.eclipse.datatools.connectivity.db.savePWD"
                value="true" />
            <property name="org.eclipse.datatools.connectivity.drivers.defnType"
                value="org.eclipse.datatools.enablement.mysql.5_1.driverTemplate" />
            <property name="jarList"
                value="/usr/local/share/mysql-connector-java-5.1.17-bin.jar" />
            <property name="org.eclipse.datatools.connectivity.db.username"
                value="your_username" />
            <property name="org.eclipse.datatools.connectivity.db.driverClass"
                value="com.mysql.jdbc.Driver" />
            <property name="org.eclipse.datatools.connectivity.db.databaseName"
                value="planet33_v2" />
            <property name="org.eclipse.datatools.connectivity.db.password"
                value="your_password" />
            <property name="org.eclipse.datatools.connectivity.db.version"
                value="5.1" />
            <property name="org.eclipse.datatools.connectivity.db.URL"
                value="jdbc:mysql://127.0.0.1:3306/planet33_v2" />
            <property name="org.eclipse.datatools.connectivity.db.vendor"
                value="MySql" />
        </baseproperties>
        <org.eclipse.datatools.connectivity.versionInfo>
            <property name="server.version" value="5.1.49" />
            <property name="technology.name.jdbc" value="JDBC" />
            <property name="server.name" value="MySQL" />
            <property name="technology.version.jdbc" value="4.0.0" />
        </org.eclipse.datatools.connectivity.versionInfo>
        <driverreference>
            <property name="driverName" value="MySQL JDBC Driver" />
            <property name="driverTypeID"
                value="org.eclipse.datatools.enablement.mysql.5_1.driverTemplate" />
        </driverreference>
    </profile>
</DataTools.ServerProfiles>

Note: I bet you can use JDNI data sources here (and I suppose this is even preferable because of connection pooling, etc.). Please, drop a few lines in comments below with instructions how you do this.

You can now edit XML source of you report and replace /report/data-sources with something like this:

<data-sources>
    <oda-data-source extensionID="org.eclipse.birt.report.data.oda.jdbc.dbprofile"
        name="Planet33 V2 Data Source" id="359">
        <property name="OdaConnProfileName">planet33_v2</property>
        <property name="OdaConnProfileStorePath">../conf/planet33_v2.xml</property>
    </oda-data-source>
</data-sources>

Several things to mention here:
  • planet33_v2.xml (Connection Profile Store)
    • Check all properties and change them according to your connection.
    • Note the jarList property, there you should specify path(s) to where your JDBC drivers located (I copied driver that I've downloaded to /usr/local/share/mysql-connector-java-5.1.17-bin.jar).
    • When you create connection profile store file from designer it places property with name="org.eclipse.datatools.connectivity.driverDefinitionID". You should remove this property because of this issue.
  • sales.rptdesign (The Report)
    • You should keep value of ida-data-source@id attribute the same that was in your design.
    • Value of OdaConnProfileName should match value of DataTools.ServerProfiles/profile@name attribute from planet33_v2.xml.
    • Note that OdaConnProfileStorePath is relative path (see below). But you can keep it absolute if you want. 

Server Environment

(Note: I recommend to configure Tomcat instance on your developer machine first to make it easier to verify report settings, and then transfer the entire $CATALINA_HOME to production server. Of course, you can do all these steps on production server directly.)
  1. Download Apache Tomcat (any Java application server should be fine).
    Unzip to some folder (I used /usr/local/share/apache-tomcat-5.5.33/) -- this will be $CATALINA_HOME.
  2. Download BIRT "Runtime" package here.
    Copy birt.war (BIRT Web Viewer application) to $CATALINA_HOME/webapps.
  3. Edit $CATALINA_HOME/catalina.sh and paste these lines somewhere after JAVA_OPTS variable initialized (this prepares workspace for DTP plugin):
    
    
    java_io_tmpdir=$CATALINA_HOME/temp
    org_eclipse_datatools_workspacepath=$java_io_tmpdir/workspace_dtp
    mkdir -p $org_eclipse_datatools_workspacepath
    
    JAVA_OPTS="$JAVA_OPTS -Dorg.eclipse.datatools_workspacepath=$org_eclipse_datatools_workspacepath"
    
    
  4. Start Tomcat by running $CATALINA_HOME/startup.sh. After this BIRT Report Viewer application should be available by http://localhost:8080/birt. Also birt.war should be now extracted to $CATALINA_HOME/webapps/birt -- this will be $BIRT_HOME. You can now delete $CATALINA_HOME/webapps/birt.war.
  5. Copy planet33_v2.xml to $CATALINA_HOME/conf as (remember OdaConnProfileStorePath property in sales.rptdesign file?).
  6. Copy your sales.rtpdesign file to $BIRT_HOME.
At this point you should be able to execute the report by simply following the address http://localhost:8080/birt/frameset?__report=sales.rptdesign&__dpi=600.

Note the __dpi URL parameter -- it controls DPI of chart images rendered in HTML/PDF. You will probably want to modify like this it to increase image quality. Also note that if you set chart output format to SVG you will get vector graphics quality in PDF output.

Security

There are obvious reasons why you may want to keep your reports secure.

Besides that keep in mind that in BIRT Web Viewer application all reports sources (*.rptdesign files) available to user by request. Try navigating to http://localhost:8080/birt/sales.rptdesign and you'll see what I mean. I think this is a good reason, why you should use connection profile store (or at least JNDI data sources), because that files not available through HTTP.

Implementing simple (HTTP BASIC AUTH) security with Tomcat is pretty simple.

First, modify $BIRT_HOME/WEB-INF/web.xml, by adding this (you can change role names as you want):
<!-- Define a security constraint on this application -->
<security-constraint>
  <web-resource-collection>
    <web-resource-name>Entire Application</web-resource-name>
    <url-pattern>/*</url-pattern>
  </web-resource-collection>
  <auth-constraint>
    <!-- This role is not in the default user directory -->
    <role-name>manager</role-name>
  </auth-constraint>
</security-constraint>             
<!-- Define the login configuration for this application -->
<login-config>
  <auth-method>BASIC</auth-method>
  <realm-name>BIRT Report Viewer</realm-name>
</login-config>
<!-- Security roles referenced by this web application -->
<security-role>
  <description>
    The role that is required to log in to the BIRT Report Viewer
  </description>
  <role-name>manager</role-name>
</security-role>

You may also do the same for $CATALINA_HOME/conf/web.xml to secure all applications in this Tomcat instance.
Second, you should edit $CATALINA_HOME/conf/tomcat-users.xml to define user login and password.

Thats all, you're secured :) This is should be fine for most cases, but I would recommend you to read about HTTPS if your data is extremely secure.

Deploy to server

  1. Copy Tomcat to the server:
    Tip: Use scp command in terminal to transfer files from your machine to the server over SSH:
    scp /usr/local/share/apache-tomcat-5.5.33 dmitrygusev@planet33.ru:/usr/local/share/
  2. Copy JDBC Driver to the server:
    • Copy this driver to the same path as specified in the jarList property from planet33_v2.xml file.
    • DO NOT COPY this driver to $BIRT_HOME/WEB-INF/lib, because it may lead to ClassNotFoundException.
Fix file permissions. When you copy files over scp you may need to chmod them to grant read/execute access. This should fix it:

chmod a+r /usr/local/share/mysql-connector-java-5.1.17-bin.jar
chmod -R a+r /usr/local/share/apache-tomcat-5.5.33/
chmod a+x /usr/local/share/apache-tomcat-5.5.33/bin/*.sh

Now you should be able to start tomcat and run reports on the server.

Fonts. BIRT PDF output doesn't work good for Russian fonts out-of-the-box, because of licensing issues with fonts. One simple solution to fix this is:
  1. Get the *.ttf font files you need (you can copy them from any Windows installation, look in c:\Windows\Fonts). These 8 files should be enough in most cases (these are "Arial" and "Times New Roman" fonts):

    arialbd.ttf  arialbi.ttf  ariali.ttf  arial.ttf
    timesbd.ttf  timesbi.ttf  timesi.ttf  times.ttf
  2. Copy these files to /usr/share/fonts/truetype (or any other place that is referenced from fontsConfig.xml).
  3. Don't forget to fix file permissions:
    chmod a+r /usr/share/fonts/truetype/*.ttf
  4. Reference the fonts from *.rptdesign (or configure font-aliases):
    /report/styles
    <style name="report" id="4">
        <property name="fontFamily">"Arial"</property>
        <property name="fontSize">9pt</property>
    </style>
    
  5. Restart Tomcat:
    • $CATALINA_HOME/bin/shutdown.sh
    • $CATALINA_HOME/bin/startup.sh
Russian Localization. I used this method to do it. Although you may want to try BIRT Language Packs.


Troubleshooting.

  • Neither the JAVA_HOME nor the JRE_HOME environment variable is defined
    At least one of these environment variable is needed to run this program

    You should define JAVA_HOME variable. Execute this command before running Tomcat's *.sh files in terminal:

    export JAVA_HOME=/usr/bin/java
  • If you get OutOfMemoryError you may want to give JVM more memory. Edit $CATALINA_HOME/bin/catalina.sh to include this (see this thread on stackoverflow, and read more about JVM memory settings):

    JAVA_OPTS="$JAVA_OPTS -Xms512m -Xmx512m -XX:MaxPermSize=256m"
  • If you got OutOfMemoryError you most likely couldn't restart Tomcat using $CATALINA_HOME/bin/shutdown.sh script.

    To kill Tomcat instance use htop command in terminal. In htop interface select Tomcat process (this is /usr/lib/java), press 'k', select 9 SIGKILL in "Send signal" area, and press Enter. To exit htop press 'q'.
  • Executing report never stops. Tomcat process consumes all CPU resources.

    I've seen this situation when used charts in report and they were on page break. I fixed this by moving charts to other place (far from page break). Changing page size to avoid page breaks also fixes this issue.

Monday, July 13, 2009

Wednesday, July 08, 2009

Повышение качества изображения при переводе из PDF в JPEG

Если использовать пример из предыдущего поста для перевода PDF в JPEG, можно заметить потерю качества изображения, особенно если оно сильно уменьшается в размерах, например, при создании thumbnails:





Слева результат работы Ghostscript (скачать файл Gordon_Moore_1965_Article.pdf, 820 КБ):

c:\Temp>"c:\Program Files\gs\gs8.64\bin\gswin32c.exe" -dSAFER -dBATCH -dNOPAUSE -sDEVICE=jpeg -r30 -dTextAlphaBits=4 -dGraphicsAlphaBits=4 -dMaxStripSize=8192 -sOutputFile=page-%d.jpg Gordon_Moore_1965_Article.pdf

Справа - ImageMagick, набора программ для обработки изображений:

c:\Temp>"c:\Program Files\ImageMagick-6.5.4-Q16\convert.exe" -resize 255x330 Gordon_Moore_1965_Article.pdf output.jpg

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

Согласно документации, для перевода PDF файлов в графический формат, ImageMagick использует Ghostscript. Чтобы понять откуда разница в качестве посмотрим, с какими параметрами ImageMagick вызывает Ghostscript (я на время переименовал папку "c:\Program Files\gs", чтобы ImageMagick не смог найти Ghostscript):

c:\Temp>"c:\Program Files\ImageMagick-6.5.4-Q16\convert.exe" -resize 255x330 Gordon_Moore_1965_Article.pdf output.jpg
convert.exe: `%s': %s "C:/Program Files/gs/gs8.64/bin/gswin32c.exe" -q -dQUIET -dPARANOIDSAFER -dBATCH -dNOPAUSE -dNOPROMPT -dMaxBitmap=500000000 -dEPSCrop -dAlignToPixels=0 -dGridFitTT=0 "-sDEVICE=bmpsep8" -dTextAlphaBits=4 -dGraphicsAlphaBits=4 "-r72x72" -dUseCIEColor "-sOutputFile=C:/Users/DMITRY~1/AppData/Local/Temp/magick-zgPdpP6n" "-fC:/Users/DMITRY~1/AppData/Local/Temp/magick-xyf_86Fy" "-fC:/Users/DMITRY~1/AppData/Local/Temp/magick-ETtA_r9y" @ utility.c/SystemCommand/1880.
convert.exe: Postscript delegate failed `Gordon_Moore_1965_Article.pdf': No such file or directory @ pdf.c/ReadPDFImage/611.
convert.exe: missing an image filename `output.jpg' @ convert.c/ConvertImageCommand/2772.


Помимо прочих параметров, основным отличием является использование специфического устройства вывода (-sDEVICE=bmpsep8). На основании входного файла ImageMagick определяет промежуточный формат, в который он должен перевести содержимое PDF без потери качества. Это может быть RAW-формат (например, pnmraw) или, как в примере, bmpsep8. Дальнейшие трансформации (например, изменение размера) ImageMagick производит на этом временном файле.

Известно, что RAW/BMP файлы имеют большой размер (пропорционально размеру канвы изображения), и даже если на выходе мы хотим получить файл размером 255x300, как в примере, промежуточный файл может быть гигантских размеров. Поэтому нужно быть осторожным при использовании такого метода конвертации, особенно если вы заведомо не знаете о содержимом ваших PDF-файлов.


Так, например, для PDF в 544 КБ (скачать можно здесь), размер промежуточного файла ImageMagick составляет 1,6 ГБ (!). На выходе получается JPEG на 8 КБ, при этом процедура конвертации занимает около 5 минут. Та же самая процедура с использованием Ghostscript + JPEG device занимает меньше секунды при размере выходного файла 4 КБ, но подбирать параметры масштабирования в данном случае приходится вручную. Результат представлен ниже (слева Ghostscript, справа - ImageMagick), о качестве судите сами.


GhostscriptImageMagick



При тестировании использовались Ghostscript 8.64 и ImageMagick 6.5.4-2-Q16.

P.S.
Список поддерживаемых Ghostscript devices можно найти здесь: http://www.gnu.org/software/ghostscript/devices.html.

В одном из следующих постов я расскажу как можно организовать пакетную печать PDF-документов в Windows, используя Ghostscript и устройство mswinpr2.

Wednesday, April 29, 2009

Использование Ghostscript для перевода PDF в JPEG

В одном из последних проектов я занимался пакетной обработкой pdf-документов: слияние (merge) документов и распознание 1D штрих-кодов (barcode recognition).

Хочу поделиться интересными, на мой взгляд, особенностями и ссылками на Open Source-проекты.

С первой задачей (merge документов) отлично справился PDFsharp - Open Source-проект со свободной лицензией и с достаточно простым и удобным API (.Net).

Со штрих-кодами оказалось немного сложнее. Дело в том, что основной формат документов для "читалок" - файлы изображений (jpeg, png, tiff и т.д.), а перевести PDF в картинку оказалось не так просто...

Мы решили эту задачу с помощью Ghostscript - Open Source (GPL License) интерпретатор языка PostScript и пакет для работы с PDF.

Вообще говоря, перевести PDF в картинку, используя Ghostscript, можно одной командой, примерно так:


c:\Temp>c:\dev\bin\gs\gs8.64\bin\gswin32c.exe -q -dSAFER -dBATCH -dNOPAUSE -sDEVICE=jpeg -r150 -dTextAlphaBits=4 -dGraphicsAlphaBits=4 -dMaxStripSize=8192 -sOutputFile=page-%d.jpg mydocument.pdf


На выходе должно быть несколько файлов (по одному на страницу): в данном случае page-1.jpg, page-2.jpg и т.д.

Проблемы начинаются, если PDF-документ содержит экзотические шрифты (например, IDAutomationHC39M.ttf - шрифт штрих-кода Code39), или если документ был создан при помощи MS Reporting Services или другой программы, которая по тем или иным причинам не включает используемые шрифты в выходной PDF-файл (PDF fonts embedding).

Наш PDF был именно такой:



В результате во время рендеринга у меня возникла такая ошибка:


Error: /undefined in findresource
Operand stack:
--nostringval-- --dict:7/16(L)-- F14 10.0 --dict:5/5(L)-- --dict:5/5(L)-- TimesNewRoman,Bold --dict:11/12(ro)(G)-- --nostringval-- CIDFontObject --dict:6/6(L)-- --dict:6/6(L)-- Adobe-Identity
Execution stack:
%interp_exit .runexec2 --nostringval-- --nostringval-- --nostringval-- 2 %stopped_push --nostringval-- --nostringval-- --nostringval-- false 1 %stopped_push 1862 1 3 %oparray_pop 1861 1 3 %oparray_pop 1845 1 3 %oparray_pop --nostringval-- --nostringval-- 3 1 2 --nostringval-- %for_pos_int_continue --nostringval-- --nostringval-- --nostringval-- --nostringval-- %array_continue --nostringval-- false 1 %stopped_push --nostringval-- %loop_continue --nostringval-- --nostringval-- --nostringval-- --nostringval-- --nostringval-- --nostringval-- %array_continue --nostringval-- --nostringval-- --nostringval-- --nostringval-- --nostringval-- %loop_continue --nostringval-- --nostringval-- --nostringval-- %loop_continue
Dictionary stack:
--dict:1156/1684(ro)(G)-- --dict:1/20(G)-- --dict:74/200(L)-- --dict:74/200(L)-- --dict:106/127(ro)(G)-- --dict:278/300(ro)(G)-- --dict:22/25(L)-- --dict:4/6(L)-- --dict:21/40(L)-- --dict:10/13(L)--
Current allocation mode is local
Last OS error: No such file or directory
GPL Ghostscript 8.64: Unrecoverable error, exit code 1


Ошибка связана с тем, что у меня на компьютере нет шрифта TimesNewRoman,Bold.

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

Обратите также внимание на кодировку шрифта: Identity-H. Из того, что мне удалось выяснить, эта кодировка говорит о том, что используемый шрифт, вообще говоря, не существует, и в документе используются только коды символов. Для отображения символов, соответствующих этим кодам Ghostscript'у нужно "подсунуть" шрифт с аналогичной таблицей символов. Как это сделать описано в документации Ghostscript (см. CID Font Substitution).

В кратце, в файл c:\dev\bin\gs\gs.8.64\lib\cidfmap (этот файл не будет создан, если при установке вы не отметили галочку "Use Windows TrueType fonts for Chinese, Japanese and Korean"; в этом случае создайте его сами) нужно добавить инструкцию, которая будет указывать, какой шрифт использовать на самом деле вместо того, который указан в PDF.

У меня заняло некоторое время, чтобы подобрать параметры для этой инструкции. Дело в том, что нельзя заменить шрифт на любой другой (например, я не могу заменить мой английский шрифт на японский или корейский). Кроме как в исходниках я нигде больше не смог найти, что нужно подставить на место /CSI Ordering. В итоге получилась такая строчка:


/TimesNewRoman,Bold << /FileType /TrueType /Path (timesbd.ttf) /SubfontID 0 /CSI [(Unicode) 0] >> ;


Файл timesbd.ttf я взял из папки C:\Windows\fonts. Есть некоторые особенности использования абсолютных и относительных путей файла с использованием опции -dSAFER. Если указана эта опция, то путь к имени файла шрифта должен быть относительным. Поэтому я скопировал его в папку c:\dev\bin\gs\fonts (в моем случае именно она используется для поиска шрифтов по умолчанию).

Чтобы посмотреть детальные логи работы Ghostscript, нужно убрать опцию -q, и вот что мы увидим:


GPL Ghostscript 8.64 (2009-02-03)
Copyright (C) 2009 Artifex Software, Inc. All rights reserved.
This software comes with NO WARRANTY: see the file PUBLIC for details.
Processing pages 1 through 2.
Page 1
Substituting font Helvetica for IDAutomationHC39M.
Loading NimbusSanL-Regu font from %rom%Resource/Font/NimbusSanL-Regu... 2875432 1123207 25112624 23807796 3 done.
Loading NimbusRomNo9L-Regu font from %rom%Resource/Font/NimbusRomNo9L-Regu... 2912216 1253685 25112624 23819376 3 done.
Loading NimbusRomNo9L-Medi font from %rom%Resource/Font/NimbusRomNo9L-Medi... 3069576 1402046 25132720 23832802 3 done.
Page 2
Substituting font Helvetica for IDAutomationHC39M.
Loading NimbusSanL-Bold font from %rom%Resource/Font/NimbusSanL-Bold... 3166648 1539642 25152816 23854836 3 done.
Loading a TT font from C:\dev\bin\gs\fonts/timesbd.ttf to emulate a CID font TimesNewRoman,Bold ... Done.


Ошибка пропала, но при рендеринге штрих-кода вместо "правильного" шрифта использовался Helvetica. Это связано с тем, что Ghostscript не может использовать системные шрифты по-умолчанию. Эту проблему я решил копированием файлов шрифтов (IDAutomationHC39M.ttf и IDAutomationHC39M_0.ttf) в папку C:\dev\bin\gs\fonts и указанием ключика -sFONTPATH="C:\dev\bin\gs\fonts".

В результате получаем:


c:\Temp>c:\dev\bin\gs\gs8.64\bin\gswin32c.exe -sFONTPATH="C:\dev\bin\gs\fonts" -dSAFER -dBATCH -dNOPAUSE -sDEVICE=jpeg -r150 -dTextAlphaBits=4 -dGraphicsAlphaBits=4 -dMaxStripSize=8192 -sOutputFile=page-%d.jpg mydocument.pdf

GPL Ghostscript 8.64 (2009-02-03)
Copyright (C) 2009 Artifex Software, Inc. All rights reserved.
This software comes with NO WARRANTY: see the file PUBLIC for details.
Processing pages 1 through 2.
Page 1
Scanning c:\dev\bin\gs\fonts for fonts... 3 files, 3 scanned, 2 new fonts.
Loading IDAutomationHC39M font from c:\dev\bin\gs\fonts/IDAutomationHC39M.ttf... 2868992 1068452 25189416 23882410 3 done.

Loading NimbusRomNo9L-Regu font from %rom%Resource/Font/NimbusRomNo9L-Regu... 2885680 1201118 25209512 23896006 3 done.
Loading NimbusRomNo9L-Medi font from %rom%Resource/Font/NimbusRomNo9L-Medi... 3022944 1346357 25209512 23905928 3 done.
Loading NimbusSanL-Regu font from %rom%Resource/Font/NimbusSanL-Regu... 3120016 1438677 25531048 24170429 3 done.
Page 2
Loading IDAutomationHC39M font from c:\dev\bin\gs\fonts/IDAutomationHC39M.ttf... 3120016 1443415 25189416 23883485 3 done.
Loading NimbusSanL-Bold font from %rom%Resource/Font/NimbusSanL-Bold... 3217088 1539692 25229608 23926657 3 done.
Loading a TT font from C:\dev\bin\gs\fonts/timesbd.ttf to emulate a CID font TimesNewRoman,Bold ... Done.


Хочется отметить еще одну полезную опцию Ghostscript: -r150. Если этот параметр не указывать, то рендеринг будет происходить в мастшабе по умолчанию, в результате чего из-за маленьких размеров не распознавался штрих-код. Увеличение размера до 150 пикселей на дюйм (-r150) это исправило.

Аналогично можно также уменьшать размер картинки, например, чтобы сделать thumbnails (см. заметки по качеству перевода PDF в JPEG).

Tuesday, December 30, 2008

Пример Java-приложения, построенного на Open Source решениях

Не смог придумать лучшего названия :)

Эту презентацию я готовил для девятой встречи JUG во Владимире, которая состоялась 25 декабря 2008. Запись презентации будет доступна чуть позже на сайте JUG, а пока я решил выложить слайды.

Рассматриваемое приложение - один из заказных проектов KeyIntegrity. Его особенность в том, что он полностью реализован на Java OpenSource-решениях. Вот лишь некоторые из них:

В презентации дается обзор архитектуры готового решения и разбирается дизайн проекта. В частности: общий дизайн web-приложения, подсистема доступа к данным, генератор отчетов и так далее. Кстати, предыдущий пост "Single Sign-On в интеграционных проектах IIS и Java" также относится к этому проекту.

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

Я бы отнес этот проект к категории "маленьких", но, несмотря на его размер, он оказался очень интересным в плане реализации.

Как обычно не обошлось без тонкостей использования OpenSource-решений в production. Например:
  • реализации JPA могут работать не стабильно если используется pool соединений (то есть почти всегда) и с приложением не работают более 8 часов (настройки MySQL по умолчанию), например, ночью когда нет активных пользователей;
  • использование AJP13 в Tomcat с настройками по-умолчанию через некоторое время приводит к отказу работы системы;
  • и другие.
Самое интересное, что в открытых источниках я не нашел решений этих проблем. Часть ответов я привожу в презентации (в слайдах или в записи). Но, к сожалению, большинство, все-таки, остается за кадром.

Часть вопросов по доступу к данным закрылось библиотекой keyintegrity-orm-jpa (небольшая обертка для JPA), которую пришлось написать для этого проекта. О ней также есть информация в слайдах. Скорее всего удастся выпустить эту библиотечку под Apache License v2, но это уже только в следующем году (обязательно напишу про это отдельную новость).

В общем я очень доволен как себя показала Tapestry5 с её встроенной поддержкой IoC. Всем рекомендую посмотреть этот Web Framework, тем более, что недавно вышел финальный релиз.

Также остались только хорошие впечатления от BIRT - в очередной раз удалось реализовать все, что нужно, включая русскую локализацию для Web Viewer'а.

Monday, September 22, 2008

Single Sign-On в интеграционных проектах IIS и Java

Описание проблемы


Для одного из наших проектов нужно было разработать несколько новых компонентов для существующего решения - web-портала.

Портал написан на ASP (даже не ASP.NET) с MS SQL Server 2000 и до сих пор поддерживается - периодически выходят новые версии.

Одним из требований заказчика была поддержка функции Single Sign-On между существующими и разрабатываемыми компонентами.

В этом посте я хочу рассказать о тем чем я руководствовался при разработке архитектуры решения и как в итоге был реализован механизм SSO. Забегая вперед, отмечу, что данное решение может подойти для интеграционных проектов, в которых есть необходимость организовать SSO между существующими web-приложениями (ASP/PHP) и вновь разрабатываемыми на Java EE компонентами.

Выбор инструмента


Платформа


Из соображений лицензионной чистоты и снижения лицензионных отчислений было принято решение разрабатывать новые компоненты с использованием стека Open Source решений и, в частности, на платформе Java. Если кому-то интересно почему Java - обращайтесь в комментарии - это не тема для данного поста :)

В последнее время мне очень нравится Open Source линейка OpenX, поддерживаемая открытым сообществом dev.java.net во главе с Sun Microsystems. Особенно привлекает NetBeans, который является по-настоящему мощной и, поправьте если я не прав, первой в мире и единственной бесплатной Open Source RAD для разработки Java-приложений (включая Java ME/SE/EE), чем не может похвастаться ни одна другая IDE (включая Eclipse и IDEA).

Eclipse, как IDE, мне нравится больше NetBeans, особенно JDT и его Java Editor, который в разы превосходит NetBeans по функциональности. Но Eclipse очень сильно проигрывает NetBeans в плане RAD. Многие знают, что на базе Eclipse есть разные реализации RAD (от IBM, ориентированная на линейку WebSphere; от SAP для NetWeaver), но все они платные и, поэтому, не идут ни в какое сравнение с NetBeans.

Что мне особенно нравится у NetBeans - это набор его WYSIWYG-редакторов GUI и различные мастера конфигураций, которые очень сильно сокращают время разработки. Именно этим мы и хотели воспользоваться.

Single Sign-On


На тот момент я был знаком лишь с одним фреймворком, поддерживающим SSO - это JOSSO.

JOSSO позволяет организовать SSO между веб-приложениями, физически расположенными в рамках одного сервера приложений. После того, как пользователь прошел аутентификацию в одном из "партнерских" приложений JOSSO, серверный агент JOSSO запоминает его логин и членство в ролях. При последующих обращениях пользователя к партнерским приложениям, агент автоматически пытается аутентифицировать пользователя, избавляя его от повторного ввода пароля. Эта схема работает на Tomcat. Существуют агенты и для других серверов приложений.

Но именно из-за того, что JOSSO не поддерживает Glassfish, я побоялся остановить на нем свой выбор - Glassfish еще слишком молодой и не имеет большой поддержки со стороны Open Source сообщества.

В линейке OpenX есть продукт Open SSO, поддерживающий Glassfish, но реализованного агента для IIS у этого продукта не было.

У JOSSO есть агент для IIS, но, как потом выяснилось, и JOSSO, в дополнение к вышесказанному, и Open SSO под Single Sign-On понимают немного другую схему аутентификации - схему основанную на токенах.

В этой схеме приложение, проводя процедуру аутентификации пользователя, получает у Identity Provider'а токен - сессионный ключ. Этот ключ может передаваться между партнерскими приложениями, например, через Cookies. В такой схеме, имея сессионный токен пользователя, приложение самостоятельно может запросить у централизованного сервиса права пользователя и провести процедуру авторизации.

В нашем случае это означало бы полностью переписать функции аутентификации и авторизации портала по новой схеме, что было бы крайне нежелательно, так как требовало бы больших усилий при разработке и применении новых обновлений портала. Кроме того ASP и MS SQL Server 2000 - это устаревшие технологии и никаких новых стратегических компетенций мы бы не получили с этого проекта.

Исходя из всего вышесказанного было принято решение в качестве сервера приложений выбрать Apache Tomcat - наиболее распространенный и широко поддерживаемый открытым сообществом Open Source сервер приложений (я знаю, что там только web-контейнер, нам больше не надо :). Решение было принято из расчета на то, что если и можно сделать SSO на Java, то на Tomcat это решение заработало бы с вероятностью 99.999%.

Описание решения


В основе найденного решения лежит The Apache Tomcat Connector - "мостик", который используется для запуска Tomcat за web-серверами, такими как Apache HTTPD и Microsoft IIS.

В этой схеме весь HTTP-трафик пользователя проходит через web-сервер (в нашем случае IIS) и передается Tomcat'у, который обрабатывает запрос и возвращает результат.

Это возможно за счет ISAPI-фильтра, который отвечает за перенаправление всех HTTP-запросов. Такое перенаправление возможно за счет поддержки Tomcat'ом бинарного протокола AJP13, по которому происходит эта передача. Возможно другие сервера также поддерживают AJP13 и для них можно организовать аналогичную схему - я не пробовал, но если у кого-то получится, большая просьба отметиться в комментариях.

Особенности решения


Аутентификация пользователей в портале ASP осуществляется путем сохранения информации об имени пользователя и его членства в ролях, которая сохраняется в объекте ASP Session (сессия приложения).

Если бы аутентификация осуществлялась стандартными средствами, например, HTTP Basic Authentication, то в Java EE приложении имя пользователя можно было бы получить через интерфейс HttpServletRequest.getRemoteUser(...).

Но так как сессия лежит за границами протокола HTTP, из Java непосредственно нельзя получить к ней доступ. Однако в HTTP-заголовках передаются Cookies, в которых сохраняются сессионные ключи приложений (JSESSIONID, PHPSESSIONID, ASPSESSIONIDxxx).

Благодаря этому мы можем обратиться к приложению по протоколу HTTP, передав сессионный ключ и попасть в контекст нужной сессии, где нам будет доступна вся необходимая информация.

Реализация


Для этого в партнерское приложение достаточно добавить страницу sessioninfo.asp следующего содержания:

UserID=<%=Session("UserID")%>
UserType=<%=Session("UserType")%>


Соответственно, из Java приложения, чтобы получить информацию о пользователе (в данном случае это его идентификатор и тип, на основании которых можно сделать запрос к БД и получить всю дополнительную информацию, если это необходимо), достаточно обратиться по адресу, например, http://localhost/asp-portal/sessioninfo.asp и рас-parse-ить результат:

01: <%@ page contentType="text/html; charset=iso-8859-1" language="java" %>
02: <%@ page import="java.net.*"%>
03: <%@ page import="java.io.*"%>
04: <%@ page import="java.util.Properties"%>
05:
06: <%
07: Cookie[] cookies = request.getCookies();
08:
09: String value = "";
10:
11: for (int i = 0; i < cookies.length; i++) {
12: Cookie cookie = cookies[i];
13: value += cookie.getName() + "=" + cookie.getValue() + ";";
14: }
15:
16: URL url = new URL("http://localhost/asp-portal/sessioninfo.asp");
17:
18: URLConnection c = url.openConnection();
19:
20: c.setRequestProperty("Cookie", value);
21:
22: InputStreamReader in = new InputStreamReader(c.getInputStream());
23:
24: Properties p = new Properties();
25: p.load(in);
26:
27: in.close();
28: %>


При таком подходе на каждый запрос к Java-компоненту идет обратный дополнительный запрос к ASP-порталу. Вопрос производительности для нас не был критичным, но в несколько миллисекунд обратный запрос укладывался.

Остается отметить, что чтобы браузер передавал Cookies Java-приложению они должны быть в одном домене (URL) с порталом. Для других проектов это может быть ограничением, но в нашем случае это было скорее требованием.

Источники информации


Инструкцию по настройке Tomcat и IIS можно найти здесь: How To Configure IIS 6.0 and Tomcat with the JK 1.2 Connector. Мне пришлось немного изменить описанный сценарий, моя установка отказалась принимать приведенный там в примере файл workers.properties и я написал свой, руководствуясь вот этой документацией.

Sunday, August 24, 2008

Поддержать проекты / Donate

Выразите свою поддержку словами - напишите комментарий к этому сообщению!

Еще Вы можете сделать перевод на один из моих электронных кошельков:















  • E106267571334

  • R157472504481

  • U489309160087

  • Z135116474858


Сделать перевод




41001334106330



Сделать перевод



Жители России и СНГ могут отправить записку-послание в мою sms.копилку.




Если ни один из способов вам не подходит - можете кликнуть по любому понравившемуся баннеру на этой странице... а можете не кликать :)

Спасибо за проявленный интерес к проектам!

Список моих Open Source проектов:

  • AnjLab.SyncIT - Небольшое приложение, которое позволяет синхронизировать ваши системы управления проектами/заданиями (такие как Trac, Google Code, dotProject и даже 1С) с MS Outlook, позволяя вам централизованно управлять своим временем.

  • Мобильная версия "4 конверта" - Мобильный клиент для занесения трат в online-сервис www.4konverta.com, который позволяет держать руку на пульсе при ведении домашнего бюджета.


Более подробную информацию о проектах можно найти в этом блоге или на официальных сайтах проектов.

Saturday, August 23, 2008

Мобильный клиент "4 Конверта": Теперь и для Windows Mobile

Я уже писал о том, что решил пользоваться мобильным телефоном для учета затрат в online-сервисе "4 Конверта".

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

Для анализа я пользуюсь возможностями Excel'я, но это уже совсем другая история, о которой я напишу позже.

Сегодня, одновременно с новой версией Java-клиента вышла первая версия для Windows Mobile. Теперь счастливые обладатели телефонов и коммуникаторов с Windows Mobile также смогут воспользоваться преимуществами мобильного клиента.

Обе версии являются полными функциональными аналогами друг-друга. В новой версии, помимо возможности занесения затрат, появилась новая функция "Что в конверте?", которая позволяет просматривать содержимое конвертов: бюджет на день/неделю, сколько потрачено/осталось за день/неделю.

Пользователи Java-приложений могут бесплатно загрузить новую версию с сайта проекта.

Версия для Windows Mobile будет распространяться за деньги. Подробности о приобретении можно также прочитать на сайте в разделе Windows Mobile.

Thursday, July 10, 2008

Учет затрат при помощи мобильного телефона


С прошлого месяца, когда был запущен online-сервис 4 конверта, мы с Настей пользуемся правилом 4 конвертов для планирования и учета ежедневных трат.

В двух словах, при использовании эта система требует ежедневного занесения трат, которые были произведены за день. Я заметил, что спустя две недели в "конвертах" образовались пустые места, потому что были проблемы с интернетом (как сейчас, например, из-за переезда в новую квартиру) или просто потому, что вечером я так и не дошел до компьютера, чтобы вписать цифру...

Другой причиной появления этих "дырок" являются мелкие траты, которых в течение дня может быть много и к вечеру они просто вылетают из головы.

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

Приложение не предъявляет никаких сверх требований к мобильному телефону: главное - это наличие поддержки Java и настроенный интернет (например, GPRS).

Пользователи сервиса могут скачать приложение с сайта проекта (30 Кб).

Сам проект я выложил как Open Source'овый, там же на сайте можно скачать исходники и свободно использовать их для своих нужд.

Если своих силенок на внесение изменений не хватит, можно там же на сайте или здесь в комментариях оставить пожелания, я посмотрю, что можно будет сделать.

Надеюсь это будет кому-то полезным.

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.