Best practice: Creation of SAX parser for XMLReader
Asked Answered
R

3

8

I'm using the Amazon S3 SDK in two separate wars running on the same Tomcat. I initialize an AmazonS3Client in the @PostConstruct of one of my Spring services.

If I run these wars separately, everything usually works fine. If I run them together, one of them - the second one to start up - throws the following exception:

com.amazonaws.AmazonClientException: Couldn't initialize a sax driver for the XMLReader

I have a workaround where I set the following System property if this happens, after catching the AmazonClientException:

try {
  init();
} catch (AmazonClientException ase) {
  System.setProperty("org.xml.sax.driver", "com.sun.org.apache.xerces.internal.parsers.SAXParser");
  init();
}

But this is of course horrible. Is there a better way to do this? Why does this occur in these circumstances?

UPDATE: At first, it seemed that moving the intitalization of the AmazonS3Client out of the @PostConstruct and initializing it lazily prevented this error completely. But apparently it still occurs sometimes - even when I only run one war instead of both.

Rosaceous answered 29/8, 2012 at 14:44 Comment(6)
sounds like a classloader issue. are you doing anything non-standard in your tomcat setup? are you deploying any xml libraries in your wars (like xerces, xml-apis, etc)?Award
Have you tried taking the libraries out of the WARs and putting them into the endorsed directory (<TOMCAT_ROOT>/endorsed) of apache tomcat? Might do the trick - I encountered similar problems with shared libraries.Becoming
@jtahlborn: I'm also assuming it's a classloader issue. I'm using Jackson - but both wars have it, so I find it strange that it should only cause problems when they are loaded together.Rosaceous
@tbk Worth trying, but it will wreak havoc on our deployment procedures. Might be worth doing regardless.Rosaceous
@Eyal: Indeed, the endorsed variant is quite awful. However, Tomcat's classloader is quite painful sometimes. I usually run into issues with XML parsers. Running a full fledged J2EE server inspite of a servlet container might be more suited to deal with your problem. If you don't mind what kind of server you are running, you could take a look at glassfish.Becoming
I found a marginally less horrible way to avoid these errors - it appears that having the first call to Amazon take place in a Spring @PostConstruct might have caused the problem. I changed it to make the call lazily after the service was constructed, and the problem doesn't seem to occur anymore (without changing the org.xml.sax.driver property).Rosaceous
K
6

The XMLReader goes through a series of steps to identify which drive to use. Quoting the docs

  • If the system property org.xml.sax.driver has a value, that is used as an XMLReader class name.
  • The JAR "Services API" is used to look for a class name in the META-INF/services/org.xml.sax.driver file in jarfiles available to the runtime.
  • SAX parser distributions are strongly encouraged to provide a default XMLReader class name that will take effect only when previous options (on this list) are not successful.
  • Finally, if ParserFactory.makeParser() can return a system default SAX1 parser, that parser is wrapped in a ParserAdapter. (This is a migration aid for SAX1 environments, where the org.xml.sax.parser system property will often be usable.)

Looking at the code for the AWS SDK ...

public XmlResponsesSaxParser() throws AmazonClientException {
    // Ensure we can load the XML Reader.
    try {
        xr = XMLReaderFactory.createXMLReader();
    } catch (SAXException e) {
        // oops, lets try doing this (needed in 1.4)
        System.setProperty("org.xml.sax.driver", "org.apache.crimson.parser.XMLReaderImpl");
        try {
            // Try once more...
            xr = XMLReaderFactory.createXMLReader();
        } catch (SAXException e2) {
            throw new AmazonClientException("Couldn't initialize a sax driver for the XMLReader");
        }
    }
}

There are a couple of things I don't like about that code.

  1. The root cause of SaxException e is eaten up.
  2. The root cause of SaxException e2 is also eaten up. The least the code should do is print a warning mentioning the root cause.
  3. Using System.setProperty() inside level framework code can cause some hard to debug issues.

These points make it harder to debug the issue. The best educated guess I can make is that the crimson parser is accessible in one class loading path but absent in the other. A conclusive way to find the problem would be to set a breakpoint on the code that tries to instantiate the reader and find what the underlying root cause is.

Knott answered 21/10, 2012 at 12:24 Comment(1)
Your final paragraph is correct, but that doesn't help me, really. I've indeed debugged this, and can see that sometimes the calls to createXMLReader fail. But I don't know why it fails- especially why it fails and succeeds in the same place, depending on other wars which are loaded. As I wrote in the question- I have a fix which causes all my AmazonS3Clients to be initialized, but it doesn't feel like the best solution for this phenomena.Rosaceous
I
1

as it uses the singleton model, the only way to isolate this calls would be to have entire set of SAX-related JARs within the WARs themselves (they would load to different classloaders). It worked for me the time I had the same problem. This will have a PermGen impact, but what to do.. Or if you don't mind to change the S3 lib, make this method static synchronized and share the lib. If the Amazon guys make this calls synchronized this wouldn't be issue.

Inainability answered 28/10, 2012 at 9:22 Comment(0)
M
0

Execption can be reolved by adding "xerces-2.9.0.jar" to the classpath

Morose answered 9/9, 2022 at 7:11 Comment(1)
As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.Danutadanya

© 2022 - 2024 — McMap. All rights reserved.