Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Apache POI 3.17 in OSGi

Preamble: I've seen this question, but it's obviously about an older version of POI, and Apache went contra to every Java standard since then.

Problem: My goal is to get POI to work with OSGi. As of now I'm pretty sure that's not possible, but maybe one of you guys has a good idea. What I tried so far:

1) Bundling the JARs

The easiest would be to add bundle information directly to the POI jars (the other answer has details on how to do that). This cannot work, because the JARs export the same packages, e.g. poi-3.17.jar and poi-ooxml-3.17.jar both export org.apache.poi, which is not allowed in OSGi (and in standard Java it's a best practice to have separate packages, too).

2) Using a prepackaged plug-in

I found org.apache.servicemix.bundles.poi, which was created by someone with no understanding of how OSGi works (maybe the Apache guys?). It contains wonky dependencies that are not present in any of the JARs, and especially the import package org.junit worries me.

I could not get it to work, since not all the necessary imports are bundles, yet. And since the bundle was so obviously broken I gave it up quickly.

3) Using a Plug-in with lib folder

It's really hard to find the correct import and export packages. Ultimately this fails because the POI JARs export standard packages (like javax.xml from xmlbeans).

4) Copying the sources to plug-ins

This is probably my favorite. When you copy the sources into their own plug-ins you get compile errors. The JAR poi-ooxml-3.17.jar needs a class named org.etsi.uri.x01903.v13.SignaturePolicyIdType. The class is contained in poi-ooxml-schemas-3.17.jar, but the disturbing truth is its name is SignaturePolicyIdentifierType.

5) Asking Apache

There is a question "Can POI be used with OSGI?" in the FAQ:

Starting with POI 3.16 there's a workaround for OSGIs context classloader handling, i.e. it replaces the threads current context classloader with an implementation of limited class view. This will lead to IllegalStateExceptions, as xmlbeans can't find the xml schema definitions in this reduced view. The workaround is to initialize the classloader delegate of POIXMLTypeLoader , which defaults to the current thread context classloader. The initialization should take place before any other OOXML related calls. The class in the example could be any class, which is part of the poi-ooxml-schema or ooxml-schema: POIXMLTypeLoader.setClassLoader(CTTable.class.getClassLoader());

I've not tried it, because it does not make any sense for me: How did they even get their non-standard-conform JARs into bundles? And how would setting the classloader AFTER the class was loaded even help?

Question: Is there any way to get the current POI to work with OSGi?

Note: I just found this question, but it's for an even older version of POI. But obviously it's an ongoing problem.

like image 937
Steffi S. Avatar asked Nov 07 '22 04:11

Steffi S.


1 Answers

I got it working by building my own 3.17 OSGi bundle, which i've dropped in Virgo /repository/usr:

<project ..>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.1.7</version>

<packaging>bundle</packaging>

<name>OSGi-wrapped poi-ooxml</name>

<dependencies>
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-ooxml</artifactId>
        <version>3.17</version>
    </dependency>
    <dependency>
        <groupId>com.github.virtuald</groupId>
        <artifactId>curvesapi</artifactId>
        <version>1.04</version>
    </dependency>
    <dependency>
        <groupId>org.apache.xmlbeans</groupId>
        <artifactId>xmlbeans</artifactId>
        <version>2.3.0</version>
    </dependency>
    <dependency>
        <groupId>stax</groupId>
        <artifactId>stax-api</artifactId>
        <version>1.0.1</version>
    </dependency>
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-ooxml-schemas</artifactId>
        <version>3.17</version>
    </dependency>
    <dependency>
        <groupId>commons-codec</groupId>
        <artifactId>commons-codec</artifactId>
        <version>1.10</version>
    </dependency>
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-collections4</artifactId>
        <version>4.1</version>
    </dependency>
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi</artifactId>
        <version>3.17</version>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.felix</groupId>
            <artifactId>maven-bundle-plugin</artifactId>
            <version>2.3.7</version>
            <extensions>true</extensions>
            <configuration>
                <instructions>
                    <Export-Package>org.apache.poi.*</Export-Package>
                    <!--
                    One important thing to note: if you are not exporting a package, you add it to the Private-Package instruction.
                    Otherwise, the classes inside the package will not be copied to your bundle, as the default value of this instruction is empty.
                    -->
                    <Private-Package>org.openxmlformats.*,org.apache.commons.*,com.graphbuilder.curve.*,org.apache.xmlbeans.*,schemaorg_apache_xmlbeans.*,schemasMicrosoftComOfficeExcel.*</Private-Package>
                </instructions>
            </configuration>
        </plugin>
    </plugins>
</build>

Then in my calling code, I create a thread and use the parent classloader. Would like to hear a better way - this is not trivial to setup. Would be nice if Apache had OSGi bundles. I may have extra or missing steps, but am generating Excel files with this code:

public void write(OutputStream out) throws IOException {

    Runnable sheetCreator = new Runnable() {
        @Override
        public void run() {
            Workbook workbook = null;
            try {
                // 3.16, but now obsolete
                // POIXMLTypeLoader.setClassLoader(CTTable.class.getClassLoader());
                workbook = new XSSFWorkbook();
                buildWorkbook(workbook);
                workbook.write(out);
                out.flush();
            } catch (Throwable t) {
                // log
            } finally {
                if (workbook != null) {
                    try {
                        workbook.close();
                    } catch (IOException e) {
                        // log
                    }
                }
            }
        }
    };

    try {
        Thread thread = Thread.currentThread();
        ClassLoader cl = thread.getContextClassLoader();

        Thread th = new Thread(sheetCreator);

        th.setContextClassLoader(cl.getParent());
        th.start();
        th.join();
    } catch (Throwable t) {
        // log
    }
}
like image 187
brianzinn Avatar answered Nov 11 '22 13:11

brianzinn