Details
-
Bug
-
Resolution: Fixed
-
P4
-
8u25
-
b08
-
x86_64
-
windows_7
Description
FULL PRODUCT VERSION :
java version "1.8.0_25"
Java(TM) SE Runtime Environment (build 1.8.0_25-b18)
Java HotSpot(TM) 64-Bit Server VM (build 25.25-b02, mixed mode)
ADDITIONAL OS VERSION INFORMATION :
Windows 7
EXTRA RELEVANT SYSTEM CONFIGURATION :
Produced in Eclipse 4.4 Luna
A DESCRIPTION OF THE PROBLEM :
Summary: javax.xml.validation.Validator.validate(Source xmlSource) fails perfectly valid XML files with compatible XSD schemas whenever the Source has an XMLFilter whose startElement method receives a string parameter that was initialized otherwise than a string literal. The cause is a == in the Java code where a String.equals should be.
Details:
See the attached code sample to understand the context, for the circumstances in which the bug appeared are unusual. Normally validation might always pass, but the ill behavior manifests here only because we are using an XMLFilter.
See, in the sample code, we happen to want validation to ignore the namespace of the XML file, and we do so by passing an XMLFilter to make the validator treat all XML tags as though they match the XSD file's "targetNamespace". Nothing wrong here.
But here's the problem:
- If String parameter "uri" passed to the XMLFilter's "startElement" is passed as a string literal (even if assigned to a variable), validation passes.
- But, if "uri" is passed from a complex string construction such as in the attached example code, where XMLStreamReader.getAttributeValue returns a string, validation fails.
- The only difference is how the string "uri" passed to "startElement" is initialized. In both cases the string's contents are the same.
- Moreover, calling String.intern() before passing"uri" to "startElement" results in validation passing.
The reason therefore is the string "uri" in each cases is pointing at a different String object in RAM. This should not matter to Java, but unfortunately it does.
Hypothesized Location of Bug:
- Class: com.sun.org.apache.xerces.internal.impl.xs.SubstitutionGroupHandler
- Method: getMatchingElemDecl(QName, XSElementDecl)
Said method contains a reference comparison, instead of a content comparison, between element names: if (element.localpart == exemplar.fName && element.uri == exemplar.fTargetNamespace) ...
I believe replacing == with String.equals() can fix the problem.
See here for more info: http://stackoverflow.com/questions/27559503/java-sax-xmlfilter-not-recognizing-final-variable-initialized-by-a-method
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Choose any XML file, such as
<root><childtag></childtag></root>
Choose any compatible XSD file, such as
<xs:schema xmlns="mynamespace" xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="mynamespace" elementFormDefault="qualified">
<xs:element name="root">
<xs:complexType>
<xs:sequence>
<xs:element name="childtag" minOccurs="0" maxOccurs="1">
<xs:complexType>...</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
Pass the XML and XSD files to the code to validate.
Run sample code as is to see the fail case. Replace the line ... uri = targetNamespace ... with the line ... uri = "mynamespace" ... to see the pass case.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The validation should pass, and the method return, in any case as long as the XML file matches the XSD schema (sans the namespace, of course, which we deliberately ignored in the sample code).
ACTUAL -
Validation passes when the "uri" passed to XMLfilter.startElement is a string literal, but fails when the "uri" is assigned a string returned by a complex method such as that provided in the example.
ERROR MESSAGES/STACK TRACES THAT OCCUR :
cvc-complex-type.2.4.a: Invalid content was found starting with element 'childtag'. One of '{"mynamespace":childtag}' is expected.
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
public static void validate(byte[] xmlFile, byte[] xsdFile) throws ... {
String targetNamespace = getTargetNamespace(xsdFile);
XMLFilter namespaceFilter = new XMLFilterImpl(XMLReaderFactory.createXMLReader()) {
@Override
public void startElement(String uri, String localName, String qName, Attributes atts)
throws SAXException {
uri = targetNamespace; // overwriting the uri with our own choice
super.startElement(uri, localName, qName, atts);
}
};
Source xmlSource = new SAXSource(namespaceFilter, new InputSource(new ByteArrayInputStream(xmlFile)));
Source schemaSource = new StreamSource(new ByteArrayInputStream(xsdFile));
Validator validator = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI).newSchema(schemaSource).newValidator();
validator.validate(xmlSource);
}
private static String getTargetNamespace(byte[] xsdFile)
throws XMLStreamException, FactoryConfigurationError {
XMLStreamReader reader = XMLInputFactory.newInstance().createXMLStreamReader(new ByteArrayInputStream(xsdFile));
while (reader.hasNext()) {
int event = reader.next();
// Get the root element's "targetNamespace" attribute
if (event == XMLEvent.START_ELEMENT) {
String value = reader.getAttributeValue(null, "targetNamespace"); // fails validation
// String value = "mynamespace"; // passes validation
return value;
}
}
return null;
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
You can make validation pass by calling String.intern() on the "uri" before passing it to "startElement", thereby forcing "uri" to be referencing the interned String and thus be identical with the String object referenced by the erroneous Java code.
uri = uri.intern();
super.startElement(uri, localName, ...)
java version "1.8.0_25"
Java(TM) SE Runtime Environment (build 1.8.0_25-b18)
Java HotSpot(TM) 64-Bit Server VM (build 25.25-b02, mixed mode)
ADDITIONAL OS VERSION INFORMATION :
Windows 7
EXTRA RELEVANT SYSTEM CONFIGURATION :
Produced in Eclipse 4.4 Luna
A DESCRIPTION OF THE PROBLEM :
Summary: javax.xml.validation.Validator.validate(Source xmlSource) fails perfectly valid XML files with compatible XSD schemas whenever the Source has an XMLFilter whose startElement method receives a string parameter that was initialized otherwise than a string literal. The cause is a == in the Java code where a String.equals should be.
Details:
See the attached code sample to understand the context, for the circumstances in which the bug appeared are unusual. Normally validation might always pass, but the ill behavior manifests here only because we are using an XMLFilter.
See, in the sample code, we happen to want validation to ignore the namespace of the XML file, and we do so by passing an XMLFilter to make the validator treat all XML tags as though they match the XSD file's "targetNamespace". Nothing wrong here.
But here's the problem:
- If String parameter "uri" passed to the XMLFilter's "startElement" is passed as a string literal (even if assigned to a variable), validation passes.
- But, if "uri" is passed from a complex string construction such as in the attached example code, where XMLStreamReader.getAttributeValue returns a string, validation fails.
- The only difference is how the string "uri" passed to "startElement" is initialized. In both cases the string's contents are the same.
- Moreover, calling String.intern() before passing"uri" to "startElement" results in validation passing.
The reason therefore is the string "uri" in each cases is pointing at a different String object in RAM. This should not matter to Java, but unfortunately it does.
Hypothesized Location of Bug:
- Class: com.sun.org.apache.xerces.internal.impl.xs.SubstitutionGroupHandler
- Method: getMatchingElemDecl(QName, XSElementDecl)
Said method contains a reference comparison, instead of a content comparison, between element names: if (element.localpart == exemplar.fName && element.uri == exemplar.fTargetNamespace) ...
I believe replacing == with String.equals() can fix the problem.
See here for more info: http://stackoverflow.com/questions/27559503/java-sax-xmlfilter-not-recognizing-final-variable-initialized-by-a-method
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Choose any XML file, such as
<root><childtag></childtag></root>
Choose any compatible XSD file, such as
<xs:schema xmlns="mynamespace" xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="mynamespace" elementFormDefault="qualified">
<xs:element name="root">
<xs:complexType>
<xs:sequence>
<xs:element name="childtag" minOccurs="0" maxOccurs="1">
<xs:complexType>...</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
Pass the XML and XSD files to the code to validate.
Run sample code as is to see the fail case. Replace the line ... uri = targetNamespace ... with the line ... uri = "mynamespace" ... to see the pass case.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The validation should pass, and the method return, in any case as long as the XML file matches the XSD schema (sans the namespace, of course, which we deliberately ignored in the sample code).
ACTUAL -
Validation passes when the "uri" passed to XMLfilter.startElement is a string literal, but fails when the "uri" is assigned a string returned by a complex method such as that provided in the example.
ERROR MESSAGES/STACK TRACES THAT OCCUR :
cvc-complex-type.2.4.a: Invalid content was found starting with element 'childtag'. One of '{"mynamespace":childtag}' is expected.
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
public static void validate(byte[] xmlFile, byte[] xsdFile) throws ... {
String targetNamespace = getTargetNamespace(xsdFile);
XMLFilter namespaceFilter = new XMLFilterImpl(XMLReaderFactory.createXMLReader()) {
@Override
public void startElement(String uri, String localName, String qName, Attributes atts)
throws SAXException {
uri = targetNamespace; // overwriting the uri with our own choice
super.startElement(uri, localName, qName, atts);
}
};
Source xmlSource = new SAXSource(namespaceFilter, new InputSource(new ByteArrayInputStream(xmlFile)));
Source schemaSource = new StreamSource(new ByteArrayInputStream(xsdFile));
Validator validator = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI).newSchema(schemaSource).newValidator();
validator.validate(xmlSource);
}
private static String getTargetNamespace(byte[] xsdFile)
throws XMLStreamException, FactoryConfigurationError {
XMLStreamReader reader = XMLInputFactory.newInstance().createXMLStreamReader(new ByteArrayInputStream(xsdFile));
while (reader.hasNext()) {
int event = reader.next();
// Get the root element's "targetNamespace" attribute
if (event == XMLEvent.START_ELEMENT) {
String value = reader.getAttributeValue(null, "targetNamespace"); // fails validation
// String value = "mynamespace"; // passes validation
return value;
}
}
return null;
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
You can make validation pass by calling String.intern() on the "uri" before passing it to "startElement", thereby forcing "uri" to be referencing the interned String and thus be identical with the String object referenced by the erroneous Java code.
uri = uri.intern();
super.startElement(uri, localName, ...)