@@ -388,47 +388,74 @@ public void dispose() {
388388 }
389389
390390 try {
391- try {
392- XPathFactory xpathFactory = new net .sf .saxon .xpath .XPathFactoryImpl ();
393- // XPath to find empty text nodes.
394- XPathExpression xpathExp = xpathFactory .newXPath ().compile ("//text()[normalize-space(.) = '']" );
395- NodeList emptyTextNodes = null ;
396- emptyTextNodes = (NodeList ) xpathExp .evaluate (document , XPathConstants .NODESET );
397-
398- // Remove each empty text node from document.
399- for (int i = 0 ; i < emptyTextNodes .getLength (); i ++) {
400- Node emptyTextNode = emptyTextNodes .item (i );
401- emptyTextNode .getParentNode ().removeChild (emptyTextNode );
391+ // Synchronize on the document so that XPath cleanup and the
392+ // Saxon transform happen atomically with respect to any other
393+ // thread that might still hold a reference to the same DOM.
394+ // Saxon's DOMSender walks children via NodeList.item(i) after
395+ // calling getLength(); a concurrent removeChild on the DOM
396+ // can make item(i) return null and trigger a NullPointerException
397+ // in DOMSender.walkNode.
398+ synchronized (document ) {
399+ try {
400+ XPathFactory xpathFactory = new net .sf .saxon .xpath .XPathFactoryImpl ();
401+ // XPath to find empty text nodes.
402+ XPathExpression xpathExp = xpathFactory .newXPath ().compile ("//text()[normalize-space(.) = '']" );
403+ NodeList emptyTextNodes = (NodeList ) xpathExp .evaluate (document , XPathConstants .NODESET );
404+
405+ // Snapshot the list before mutating the DOM, then remove
406+ // each empty text node (guarding against nodes whose
407+ // parent has already been detached).
408+ int len = emptyTextNodes .getLength ();
409+ List <Node > toRemove = new ArrayList <Node >(len );
410+ for (int i = 0 ; i < len ; i ++) {
411+ Node n = emptyTextNodes .item (i );
412+ if (n != null ) {
413+ toRemove .add (n );
414+ }
415+ }
416+ for (Node emptyTextNode : toRemove ) {
417+ Node parent = emptyTextNode .getParentNode ();
418+ if (parent != null ) {
419+ parent .removeChild (emptyTextNode );
420+ }
421+ }
422+ } catch (XPathExpressionException e ) {
423+ //We don't care. It's just formatting.
402424 }
403- } catch (XPathExpressionException e ) {
404- //We don't care. It's just formatting.
405- }
406425
407- TransformerFactory transformerFactory = new net .sf .saxon .TransformerFactoryImpl ();
408- Transformer transformer = transformerFactory .newTransformer ();
409- transformer .setOutputProperty (OutputKeys .INDENT , "yes" );
410- transformer .setOutputProperty (OutputKeys .METHOD , "xml" );
411- transformer .setOutputProperty (OutputKeys .ENCODING , "UTF-8" );
412- transformer .setOutputProperty ("{http://xml.apache.org/xslt}indent-amount" , "2" );
413-
414- DOMSource source = new DOMSource (document );
415- /* Running this code in java 5 we had to change
416- StreamResult result = new StreamResult(config.getXmlFilePath());
417- into
418- StreamResult result = new StreamResult(config.getXmlFilePath().getPath());
419- Otherwise you get the following error:
420- javax.xml.transform.TransformerException: java.io.FileNotFoundException:
421- */
422- /*
423- * If the safePath is not escaped then it throws
424- * net.sf.saxon.trans.XPathException: java.net.URISyntaxException:
425- * Illegal character in safePath at index 9: /temp/XML Connector/test.xml
426- * String safePath = config.getXmlFilePath().getPath().replaceAll(" ", "%20");
427- */
428- FileOutputStream fos = new FileOutputStream (config .getXmlFilePath ());
429- StreamResult result = new StreamResult (fos );
430-
431- transformer .transform (source , result );
426+ TransformerFactory transformerFactory = new net .sf .saxon .TransformerFactoryImpl ();
427+ Transformer transformer = transformerFactory .newTransformer ();
428+ transformer .setOutputProperty (OutputKeys .INDENT , "yes" );
429+ transformer .setOutputProperty (OutputKeys .METHOD , "xml" );
430+ transformer .setOutputProperty (OutputKeys .ENCODING , "UTF-8" );
431+ transformer .setOutputProperty ("{http://xml.apache.org/xslt}indent-amount" , "2" );
432+
433+ DOMSource source = new DOMSource (document );
434+ /* Running this code in java 5 we had to change
435+ StreamResult result = new StreamResult(config.getXmlFilePath());
436+ into
437+ StreamResult result = new StreamResult(config.getXmlFilePath().getPath());
438+ Otherwise you get the following error:
439+ javax.xml.transform.TransformerException: java.io.FileNotFoundException:
440+ */
441+ /*
442+ * If the safePath is not escaped then it throws
443+ * net.sf.saxon.trans.XPathException: java.net.URISyntaxException:
444+ * Illegal character in safePath at index 9: /temp/XML Connector/test.xml
445+ * String safePath = config.getXmlFilePath().getPath().replaceAll(" ", "%20");
446+ */
447+ FileOutputStream fos = new FileOutputStream (config .getXmlFilePath ());
448+ try {
449+ StreamResult result = new StreamResult (fos );
450+ transformer .transform (source , result );
451+ } finally {
452+ try {
453+ fos .close ();
454+ } catch (IOException ioe ) {
455+ log .warn ("Failed to close XML output stream: {0}" , ioe );
456+ }
457+ }
458+ }
432459
433460 log .info ("Saving changes to xml file" );
434461 } catch (TransformerException ex ) {
0 commit comments