@Test
public void testGeneratePDF() throws Exception {
byte[] pdf = new PDFGeneratorEJB().createPDF(...);
...
}
Posted by nerdcoding on June 16, 2015
How to make your JavaEE 6 application unusable with the usage of docx4j and deployed on WebLogic 12.1.2.
Docx4j is a library for creation and manipulation of Office Open XML
documents. In my case I had a .docx template file and my application generated at runtime a PDF file based on this
template. The business logic was inside of an EJB. Let us call it PDFGeneratorEJB
. This EJB read the .docx template
and inside of the template are some placeholders which are dynamically filled with data. With the combination of
template and filtered placeholders a PDF file could be generated. Here is a simple JUnit test for this EJB:
@Test
public void testGeneratePDF() throws Exception {
byte[] pdf = new PDFGeneratorEJB().createPDF(...);
...
}
We manually create an instance of our EJB and call the PDF generation method. This method takes some parameters which are used to fill the placeholders and finally the generated PDF file is returned as an byte array.
Docx4j uses JAXB to parse the .docx template and create an in-memory object representation. The reference implementation of JAXB is part of the Metro project and is shipped with the default JDK 1.7 which I had to use. Docx4j finds this JAXB implementation on the classpath and is using it. On my local machine the PDF generation with the JUnit test executes in ca. 4 seconds. Thats not really fast, but OK.
In the real world the PDFGeneratorEJB
is part of an JavaEE 6 application running in the WebLogic application server
12.1.2. Here is where the fun begins. The WebLogic server does not use the JAXB implementation shipped with the JDK, but
the EclipseLink implementation is used. The EclipseLink project also implements the JAXB specification and this project
is named MOXy. WebLogic as an application server is shipped with
EclipseLink and each deployed application needs to use this version of EclipseLink.
When now our deployed PDFGeneratorEJB
generates a PDF file and the docx4j searches for an JAXB implementation on the
classpath, the implementation of EclipseLink is found. WebLogic 12.1.2.0 uses the EclipseLink version 2.4.2. In my
WebLogic installation I found this in $MW_HOME/oracle_common/modules/oracle.toplink_12.1.2/eclipselink.jar
.
The deployed PDFGeneratorEJB
now needs ca. 2 minutes to generate a PDF file (yes I mean minutes(!) not seconds). The
only difference between the deployed PDFGeneratorEJB
and the JUnit test is the different JAXB implementation.
But it gets worse. In my application the PDFGeneratorEJB
is called (indirectly) by an message driven bean (MDB) which
is triggered by JMS messages. For each incoming JMS message the PDFGeneratorEJB
is called and the generated PDF is
saved as BLOB in the database.
The WebLogic server creates 16 instances (default value) of that MDB inside of pool. When there are many incoming JMS messages, there are up to 16 parallel PDF generations. Because each PDF generation runs inside if the same server they are limited by the server resources. Now a PDF generation takes an average of 20 minutes for a single PDF and the server CPU is constantly at 100%. Anymore, during the PDF generation JAXB creates a huge amount of short-lived objects which leads to a lot minor garbage collections. And when now a small base of users login to the application and in the background are some PDF generations, the response times are really really slow.
Complicating is that there are many timeouts because of the long running PDF generations. Either there are transaction timeouts or, when the transaction timeout is increased, the WebLogic throws stuck threads timeouts when the execution of an MDB takes too much time. Such an timeout aborts the PDF generation, the initial JMS message is put back on the queue and over the time the amount of JMS messages is increased. Of course I can increase the transaction and stuck thread timeout. But what is a reasonable value here? 20 minutes? 30 minutes? There’s no way that’s a realistic scenario.
The root of all evil is that we are forced to use the JAXB implementation EclipseLink MOXy 2.4.2 in WebLogic 12.1.2. The next WebLogic release 12.1.3 comes with EclipseLink MOXy 2.5.2 and when we deploy our application on that WebLogic version the PDF generation is done again in a few seconds. To solve our problem there are three options:
Get rid of docx4j and use another library for PDF generation which is not heavely based on JAXB.
Update the WebLogic application server from 12.1.2 to 12.1.3
Change the JAXB implementation inside of the WebLogic 12.1.2.
The first two solutions make the most sense, but they are not implemented quickly. To change the library of our PDF generation means a lot of coding and testing. And to update the application server also means a lot of testing. The third solution is definitely the dirtiest but also the fastest solution.
I see two ways to ways to change EclipseLink MOXy 2.4.2 to EclipseLink MOXy 2.5.2 in WebLogic 12.1.2.
SharedLibrary
The WebLogic supports a concept called Shared Library.
We have to create a new deployable artifact (e.g. an EAR file) which only contains the new EclipseLink version. This EAR
is deployed to the WebLogic and our real application references this SharedLibrary with a <library-ref>
in the
deployment descriptor weblogic-application.xml
APP-INF/lib
At the EAR level of our application we create a APP-INF/lib
directory which contains the new eclipselink.jar
.
WebLogic uses an system and an application classloader and normally classes loaded by the system classloader are
preferred. This means the EclipseLink version provided by the container is used by default. To enforce to load classes
from the application classloader, and therefore use the EclipseLink version form our APP-INF/lib
directory, we need to
set <prefer-application-packages>
in the
weblogic-application.xml
deployment descriptor.
In my case I implemented the APP-INF/lib
solution and it worked.