How to prevent xalan.jar that has META-INF\services\javax.xml.transform.TransformerFactory from taking over JDK 1.6 built in Xalan implementation?
Asked Answered
J

4

4

Consider this code (based entirely on flying saucer's "getting started" code, their rights reserved):

package flyingsaucerpdf;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;

import org.xhtmlrenderer.pdf.ITextRenderer;


    public class PDFMaker {
        public static void main(String[] args) throws Exception {
            new PDFMaker().go();
        }

        public void go() throws Exception {
            String inputFile = "sample.html";
            String url = new File(inputFile).toURI().toURL().toString();
            String outputFile = "firstdoc.pdf";
            OutputStream os = new FileOutputStream(outputFile);

            ITextRenderer renderer = new ITextRenderer();
            renderer.setDocument(url);
            renderer.layout();
            renderer.createPDF(os);

            os.close();
        }
    }

Few facts:

  1. Running it standalone (calling main) with JDK 1.6 or 1.5 works perfectly (PDF is generated)
  2. But when loaded via a URLClassLoader from an existing web application it fails with this error:

Caused by: org.w3c.dom.DOMException: NAMESPACE_ERR: An attempt is made to create or change an object in a way which is incorrect with regard to namespaces.
    at org.apache.xerces.dom.AttrNSImpl.setName(Unknown Source)
    at org.apache.xerces.dom.AttrNSImpl.(Unknown Source)
    at org.apache.xerces.dom.CoreDocumentImpl.createAttributeNS(Unknown Source)
    at org.apache.xerces.dom.ElementImpl.setAttributeNS(Unknown Source)
    at org.apache.xml.utils.DOMBuilder.startElement(DOMBuilder.java:307)
    ... 19 more

After looking for a while in the wrong place (for example, I created a child-first / parent-last class loader suspecting xalan / xerces jars, but it still fails), I finally narrowed down the root cause:

It seems that the web application that loads my code, has an old xalan.jar, specification version 1.2

I did a little test, I ran the code above as standalone (which worked fine before) but this time I added the xalan.jar from the web app to it's classpath, and bingo, the same error as in the web app scenario

So I inspected that old xalan.jar and wondered, what can cause the JVM to load it's old xalan implementation instead of the JDK's? after all my child-first class loader is also parent-last e.g. system in the middle, to say: searching the system classloader before the parent (to avoid loading parent overriden JDK jars, just like this case of the parent's xalan.jar overriding the JDK's xalan implementation)

Then something cought my eyes - a file in: xalan.jar/META-INF/services/ named javax.xml.transform.TransformerFactory with this content:

org.apache.xalan.processor.TransformerFactoryImpl

So I imediately pressed Ctrl+T in eclipse and looked for the full qualified name... only in xalan.jar!

Then I searched only for "TransformerFactoryImpl", and this is what the JDK has:

com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl

Easy to see the difference

So, if you read till here, my bottom line question is: How do I make my TransformerFactory use the JDK's implementation and not the old Xalan's one? (I can't remove that jar from the web app my code will be loaded from)

Jamisonjammal answered 27/3, 2011 at 6:31 Comment(2)
My previous related quesitons on the topic: #5440895 #5446011 #5444746Jamisonjammal
I think this is also related download.oracle.com/javase/6/docs/technotes/guides/standards so there is not way to do it programmatically?Jamisonjammal
J
8

It seems the answer is simpler than I thought.

  1. In your classloader, add to the classpath (jar not required) this folder: /META-INF/services/

  2. In it, create a file named javax.xml.transform.TransformerFactory

  3. Edit it and set this as it's content to: com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl

Thats it!

Why does it work? see this class used by Java for loading the Xalan implementation.

Notice that it seems de-facto a parent-last (or child-first) loader for that specific "META-INF" entry (the oposite of how regular Java class loaders work, e.g. parent-first / child-last) but feel free to correct me if I'm wrong

Snippet from javax.xml.datatype.FactoryFinder

   /*
     * Try to find provider using Jar Service Provider Mechanism
     *
     * @return instance of provider class if found or null
     */
    private static Object findJarServiceProvider(String factoryId)
        throws ConfigurationError
    {

        String serviceId = "META-INF/services/" + factoryId;
        InputStream is = null;

        // First try the Context ClassLoader
        ClassLoader cl = ss.getContextClassLoader();
        if (cl != null) {
            is = ss.getResourceAsStream(cl, serviceId);

            // If no provider found then try the current ClassLoader
            if (is == null) {
                cl = FactoryFinder.class.getClassLoader();
                is = ss.getResourceAsStream(cl, serviceId);
            }
        } else {
            // No Context ClassLoader, try the current
            // ClassLoader
            cl = FactoryFinder.class.getClassLoader();
            is = ss.getResourceAsStream(cl, serviceId);
        }

        if (is == null) {
            // No provider found
            return null;
        }

        ...
Jamisonjammal answered 27/3, 2011 at 9:5 Comment(3)
Even I am facing the same problem. I did as you told. I opened the xalan.jar and changed the content of the meta-inf/service/javax.xml.transform.TransformerFactory file to com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl . Now jboss runs with lots of errors. The web application now works fine but there a lot of errors in jboss. I am also not able to use database connection pooling. How can I make both work(run jboss properly as well as ,make the trasformer factory work)Chlorpromazine
@Chlorpromazine You are definitely not supposed to change xalan.jar but to add a file named META-INF/service/javax.xml.transform.TransformerFactory to your application. Not sure if that helps, tough.Knives
And which is the classloader on an Java applet? (I have the same problem)Tollhouse
L
3

If you are developing a web application and thus can't set a system property, then the most direct way is to explicitly request the JDK transformer. Here is an example for the internal XSLTC transformer (has StAXSupport).

TransformerFactory tf = TransformerFactory.newInstance(
        "com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl", null);
Landrum answered 8/2, 2016 at 5:56 Comment(0)
H
2

You should also note that you aren't required to use the SPI mechanism at all.

You can use http://download.oracle.com/javase/6/docs/api/javax/xml/xpath/XPathFactory.html#newInstance(java.lang.String, java.lang.String, java.lang.ClassLoader) to use a copy of xalan in your own isolated class loader once you know the name of the relevant class.

Hudgens answered 27/3, 2011 at 13:41 Comment(3)
This is interesting! I'll take a look, this might be the answer I was looking forJamisonjammal
But how do I force 3rd party code to use my class loader if they didn't provide a delegate to that? :)Jamisonjammal
Oh, whoops, my bad. If you've got to redirect other people's code, you're stuck with the SPI.Hudgens
I
0

I'm using wildfly application server and 3rd jar which uses TransformerFactory.

Overriding TransformerFactory using file resources\META-INF\services\javax.xml.transform.TransformerFactory. Didn't work for me.

When I looked to FactoryFinder implementation (JDK8u201). I found the following code fragment

String systemProp = ss.getSystemProperty(factoryId);
if (systemProp != null) {
    dPrint("found system property, value=" + systemProp);
    return newInstance(type, systemProp, null, true);
}

Thus, solution was to set system property javax.xml.transform.TransformerFactory to com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl.

Cheers

Illimani answered 2/5, 2019 at 8:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.