Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best practice: Creation of SAX parser for XMLReader

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.

like image 892
Eyal Avatar asked Aug 29 '12 14:08

Eyal


2 Answers

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.

like image 128
Deepak Bala Avatar answered Oct 20 '22 03:10

Deepak Bala


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.

like image 1
whiteagle Avatar answered Oct 20 '22 03:10

whiteagle