2011年11月29日火曜日

Solution for gdata-java-client's SAXNotRecognizedException

You will see SAXNotRecognizedException if you try to retrieve data from YouTube using gdata-java-client.

gdata-java-client
http://code.google.com/p/gdata-java-client/

The root cause is reported in detail as Issue 9493.

http://code.google.com/p/android/issues/detail?id=9493

The first solution that came to my mind is to replace SAXParserFactory using the system property "javax.xml.parsers.SAXParserFactory". This mechanism is described also in Android's online document.

http://developer.android.com/reference/javax/xml/parsers/SAXParser.html

So, I tried the following code to replace the implementation of SAXParserFactory with xerces's one.

System.setProperty("javax.xml.parsers.SAXParserFactory", "org.apache.xerces.jaxp.SAXParserFactoryImpl");

However, this did not work, and I finally found that Android's SAXParserFactory does not support the mechanism to replace the implementation.

http://www.java2s.com/Open-Source/Android/android-core/platform-libcore/javax/xml/parsers/SAXParserFactory.java.htm

/**
 * Returns Android's implementation of {@code SAXParserFactory}. Unlike
 * other Java implementations, this method does not consult system
 * properties, property files, or the services API.
 *
 * @return a new SAXParserFactory.
 *
 * @exception FactoryConfigurationError never. Included for API
 *     compatibility with other Java implementations.
 */
public static SAXParserFactory newInstance() {
    // instantiate the class directly rather than using reflection
    return new SAXParserFactoryImpl();
}

So, I returned back to the report of Issue 9493. It says that gdata-java-client defines and uses SecureGenericXMLFactory and it intentionally disables some features that are needed to parse YouTube responses.

The actual source code lines to disable the necessary features are written in the constructor of SecureSAXParserFactory, which is an internal class of SecureGenericXMLFactory.

/* Setting the attribute
 * http://apache.org/xml/features/disallow-doctype-decl to true causes an
 * immediate exception when a DTD is encountered. Unfortunately, an XML
 * document will sometimes include a harmless DTD so we cannot ban DTDs
 * outright.
 */
try {
  factory.setFeature(
    "http://xml.org/sax/features/external-general-entities",
    false);
} catch (IllegalArgumentException e) {
  /* OK.  Not all parsers will support this attribute */
} catch (SAXNotRecognizedException e) {
  /* OK.  Not all parsers will support this attribute */
}

try {
  factory.setFeature(
    "http://xml.org/sax/features/external-parameter-entities",false);
} catch (IllegalArgumentException e) {
  /* OK.  Not all parsers will support this attribute */
} catch (SAXNotRecognizedException e) {
  /* OK.  Not all parsers will support this attribute */
}

After seeing this fact, the solution I adopted was to replace the implementation of SecureGenericXMLFactory with my own. The concrete steps were as follows.

(1) Remove the original SecureGenericXMLFactory from gdata-core-1.0.jar

cd gdata-src.java-1.46.0/gdata/java/lib
mkdir gdata-core-1.0-no-SecureGenericXMLFactory
cd gdata-core-1.0-no-SecureGenericXMLFactory
jar xf ../gdata-core-1.0.jar
rm -rf com/google/gdata/util/common/xml/parsing
jar cfm ../gdata-core-1.0-no-SecureGenericXMLFactory.jar META-INF/MANIFEST.MF com

(2) Replace the original JAR file with the new one created by the step (1) in Eclipse.

(3) Copy the original source code of SecureGenericXMLFactory to my Android project.

(4) Add an unconditional 'return' in the constructor of SecureSAXParserFactory before the code lines that disable the necessary features.

// "if (true)" was added to avoid a compilation error in Eclipse.
if (true)
{
    return;
}


The detailed report of Issue 9493 was really helpful. I want to thank the reporter.