You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by ju...@apache.org on 2008/11/28 11:29:48 UTC

svn commit: r721414 - in /jackrabbit/branches/1.5: ./ jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/xml/ jackrabbit-jcr-commons/src/test/java/org/apache/jackrabbit/commons/xml/

Author: jukka
Date: Fri Nov 28 02:29:48 2008
New Revision: 721414

URL: http://svn.apache.org/viewvc?rev=721414&view=rev
Log:
1.5: Merged revisions 720455 and 720492 (JCR-1767)

Modified:
    jackrabbit/branches/1.5/   (props changed)
    jackrabbit/branches/1.5/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/xml/SerializingContentHandler.java
    jackrabbit/branches/1.5/jackrabbit-jcr-commons/src/test/java/org/apache/jackrabbit/commons/xml/SerializingContentHandlerTest.java

Propchange: jackrabbit/branches/1.5/
------------------------------------------------------------------------------
--- svn:mergeinfo (original)
+++ svn:mergeinfo Fri Nov 28 02:29:48 2008
@@ -1,2 +1,2 @@
 /jackrabbit/branches/1.3:631261
-/jackrabbit/trunk:703899-704158,704165,704167,704324,704358,704361,704864,704933,704939,705010,705033,705243,705496,705522,705579,705925,705932,705934,705937-705938,705961,706242,706273,706285-706286,706562,706606,706649,706655,706660,706697,706918,707303-707304,707307,707310,707630,708206,708598,708609,708613,708619,708634,708840,708863,708909,708929,708943,709115,709142,709207,709211,710047,711238,711566-711567,711595,711841-711843,712984-712985,713037,713059,713065,713072,713076,713162,713214,713956,713958,713964,713971,713975,714034,718218,718249-718250,718371,718376,718566,718632,718981,719225,719282,719575-719577,719579,719585-719586,719588,719592,720533
+/jackrabbit/trunk:703899-704158,704165,704167,704324,704358,704361,704864,704933,704939,705010,705033,705243,705496,705522,705579,705925,705932,705934,705937-705938,705961,706242,706273,706285-706286,706562,706606,706649,706655,706660,706697,706918,707303-707304,707307,707310,707630,708206,708598,708609,708613,708619,708634,708840,708863,708909,708929,708943,709115,709142,709207,709211,710047,711238,711566-711567,711595,711841-711843,712984-712985,713037,713059,713065,713072,713076,713162,713214,713956,713958,713964,713971,713975,714034,718218,718249-718250,718371,718376,718566,718632,718981,719225,719282,719575-719577,719579,719585-719586,719588,719592,720455,720492,720533

Modified: jackrabbit/branches/1.5/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/xml/SerializingContentHandler.java
URL: http://svn.apache.org/viewvc/jackrabbit/branches/1.5/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/xml/SerializingContentHandler.java?rev=721414&r1=721413&r2=721414&view=diff
==============================================================================
--- jackrabbit/branches/1.5/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/xml/SerializingContentHandler.java (original)
+++ jackrabbit/branches/1.5/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/xml/SerializingContentHandler.java Fri Nov 28 02:29:48 2008
@@ -21,6 +21,7 @@
 import java.io.Writer;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 
@@ -28,6 +29,7 @@
 import javax.xml.transform.Result;
 import javax.xml.transform.Transformer;
 import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.TransformerFactory;
 import javax.xml.transform.sax.SAXTransformerFactory;
 import javax.xml.transform.sax.TransformerHandler;
 import javax.xml.transform.stream.StreamResult;
@@ -69,6 +71,49 @@
     private static final String XML = "http://www.w3.org/XML/1998/namespace";
 
     /**
+     * The factory used to create serializing SAX transformers.
+     */
+    private static final SAXTransformerFactory FACTORY =
+        // Note that the cast from below is strictly speaking only valid when
+        // the factory instance supports the SAXTransformerFactory.FEATURE
+        // feature. But since this class would be useless without this feature,
+        // it's no problem to fail with a ClassCastException here and prevent
+        // this class from even being loaded. AFAIK all common JAXP
+        // implementations do support this feature.
+        (SAXTransformerFactory) TransformerFactory.newInstance();
+
+    /**
+     * Flag that indicates whether we need to work around the issue of
+     * the serializer not automatically generating the required xmlns
+     * attributes for the namespaces used in the document.
+     */
+    private static final boolean NEEDS_XMLNS_ATTRIBUTES =
+        needsXmlnsAttributes();
+
+    /**
+     * Probes the available XML serializer for xmlns support. Used to set
+     * the value of the {@link #NEEDS_XMLNS_ATTRIBUTES} flag.
+     *
+     * @return whether the XML serializer needs explicit xmlns attributes
+     */
+    private static boolean needsXmlnsAttributes() {
+        try {
+            StringWriter writer = new StringWriter();
+            TransformerHandler probe = FACTORY.newTransformerHandler();
+            probe.setResult(new StreamResult(writer));
+            probe.startDocument();
+            probe.startPrefixMapping("p", "uri");
+            probe.startElement("uri", "e", "p:e", new AttributesImpl());
+            probe.endElement("uri", "e", "p:e");
+            probe.endPrefixMapping("p");
+            probe.endDocument();
+            return writer.toString().indexOf("xmlns") == -1;
+        } catch (Exception e) {
+            throw new UnsupportedOperationException("XML serialization fails");
+        }
+    }
+
+    /**
      * Creates a serializing content handler that writes to the given stream.
      *
      * @param stream serialization target
@@ -102,10 +147,7 @@
     public static DefaultHandler getSerializer(Result result)
             throws SAXException {
         try {
-            SAXTransformerFactory factory = (SAXTransformerFactory)
-            SAXTransformerFactory.newInstance();
-
-            TransformerHandler handler = factory.newTransformerHandler();
+            TransformerHandler handler = FACTORY.newTransformerHandler();
             handler.setResult(result);
 
             // Specify the output properties to avoid surprises especially in
@@ -116,17 +158,7 @@
             transformer.setOutputProperty(OutputKeys.ENCODING, ENCODING);
             transformer.setOutputProperty(OutputKeys.INDENT, "no");
 
-            // Test whether the NamespaceAsAttributes wrapper is needed
-            StringWriter writer = new StringWriter();
-            TransformerHandler probe = factory.newTransformerHandler();
-            probe.setResult(new StreamResult(writer));
-            probe.startDocument();
-            probe.startPrefixMapping("p", "uri");
-            probe.startElement("uri", "e", "p:e", new AttributesImpl());
-            probe.endElement("uri", "e", "p:e");
-            probe.endPrefixMapping("p");
-            probe.endDocument();
-            if (writer.toString().indexOf("xmlns") == -1) {
+            if (NEEDS_XMLNS_ATTRIBUTES) {
                 // The serializer does not output xmlns declarations,
                 // so we need to do it explicitly with this wrapper
                 return new SerializingContentHandler(handler);
@@ -160,6 +192,22 @@
      */
     private boolean hasMappings = false;
 
+    /**
+     * Stack of the prefixes of explicitly generated prefix mapping calls
+     * per each element level. An entry is appended at the beginning of each
+     * {@link #startElement(String, String, String, Attributes)} call and
+     * removed at the end of each {@link #endElement(String, String, String)}
+     * call. By default the entry for each element is <code>null</code> to
+     * avoid losing performance, but whenever the code detects a new prefix
+     * mapping that needs to be registered, the <code>null</code> entry is
+     * replaced with a list of explicitly registered prefixes for that node.
+     * When that element is closed, the listed prefixes get unmapped.
+     *
+     * @see #checkPrefixMapping(String, String)
+     * @see <a href="https://issues.apache.org/jira/browse/JCR-1767">JCR-1767</a>
+     */
+    private final List addedPrefixMappings = new ArrayList();
+
     private SerializingContentHandler(ContentHandler handler) {
         super(handler);
     }
@@ -199,6 +247,50 @@
     }
 
     /**
+     * Checks whether a prefix mapping already exists for the given namespace
+     * and generates the required {@link #startPrefixMapping(String, String)}
+     * call if the mapping is not found. By default the registered prefix
+     * is taken from the given qualified name, but a different prefix is
+     * automatically selected if that prefix is already used.
+     *
+     * @see <a href="https://issues.apache.org/jira/browse/JCR-1767">JCR-1767</a>
+     * @param uri namespace URI
+     * @param qname element name with the prefix, or <code>null</code>
+     * @throws SAXException if the prefix mapping can not be added
+     */
+    private void checkPrefixMapping(String uri, String qname)
+            throws SAXException {
+        // Only add the prefix mapping if the URI is not already known
+        if (uri != null && uri.length() > 0 && !uri.startsWith("xml")
+                && !uriToPrefixMap.containsKey(uri)) {
+            // Get the prefix
+            String prefix = "ns";
+            if (qname != null && qname.length() > 0) {
+                int colon = qname.indexOf(':');
+                if (colon != -1) {
+                    prefix = qname.substring(0, colon);
+                }
+            }
+
+            // Make sure that the prefix is unique
+            String base = prefix;
+            for (int i = 2; prefixToUriMap.containsKey(prefix); i++) {
+                prefix = base + i;
+            }
+
+            int last = addedPrefixMappings.size() - 1;
+            List prefixes = (List) addedPrefixMappings.get(last);
+            if (prefixes == null) {
+                prefixes = new ArrayList();
+                addedPrefixMappings.set(last, prefixes);
+            }
+            prefixes.add(prefix);
+
+            startPrefixMapping(prefix, uri);
+        }
+    }
+
+    /**
      * Ensure all namespace declarations are present as <code>xmlns:</code> attributes
      * and add those needed before calling superclass. This is a workaround for a Xalan bug
      * (at least in version 2.0.1) : <code>org.apache.xalan.serialize.SerializerToXML</code>
@@ -207,6 +299,12 @@
     public void startElement(
             String eltUri, String eltLocalName, String eltQName, Attributes attrs)
             throws SAXException {
+        // JCR-1767: Generate extra prefix mapping calls where needed
+        addedPrefixMappings.add(null);
+        checkPrefixMapping(eltUri, eltQName);
+        for (int i = 0; i < attrs.getLength(); i++) {
+            checkPrefixMapping(attrs.getURI(i), attrs.getQName(i));
+        }
 
         // try to restore the qName. The map already contains the colon
         if (null != eltUri && eltUri.length() != 0 && this.uriToPrefixMap.containsKey(eltUri)) {
@@ -282,7 +380,18 @@
         if (null != eltUri && eltUri.length() != 0 && this.uriToPrefixMap.containsKey(eltUri)) {
             eltQName = this.uriToPrefixMap.get(eltUri) + eltLocalName;
         }
+
         super.endElement(eltUri, eltLocalName, eltQName);
+
+        // JCR-1767: Generate extra prefix un-mapping calls where needed
+        int last = addedPrefixMappings.size() - 1;
+        List prefixes = (List) addedPrefixMappings.remove(last);
+        if (prefixes != null) {
+            Iterator iterator = prefixes.iterator();
+            while (iterator.hasNext()) {
+                endPrefixMapping((String) iterator.next());
+            }
+        }
     }
 
     /**

Modified: jackrabbit/branches/1.5/jackrabbit-jcr-commons/src/test/java/org/apache/jackrabbit/commons/xml/SerializingContentHandlerTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/branches/1.5/jackrabbit-jcr-commons/src/test/java/org/apache/jackrabbit/commons/xml/SerializingContentHandlerTest.java?rev=721414&r1=721413&r2=721414&view=diff
==============================================================================
--- jackrabbit/branches/1.5/jackrabbit-jcr-commons/src/test/java/org/apache/jackrabbit/commons/xml/SerializingContentHandlerTest.java (original)
+++ jackrabbit/branches/1.5/jackrabbit-jcr-commons/src/test/java/org/apache/jackrabbit/commons/xml/SerializingContentHandlerTest.java Fri Nov 28 02:29:48 2008
@@ -61,6 +61,45 @@
         assertContains(xml, "</p:a>");
     }
 
+    /**
+     * Test case for JCR-1767.
+     *
+     * @see <a href="https://issues.apache.org/jira/browse/JCR-1767">JCR-1767</a>
+     */
+    public void testNoPrefixMappingCalls() throws Exception {
+        StringWriter writer = new StringWriter();
+
+        ContentHandler handler =
+            SerializingContentHandler.getSerializer(writer);
+        handler.startDocument();
+        handler.startElement("uri", "a", "p:a", new AttributesImpl());
+        AttributesImpl attributes = new AttributesImpl();
+        attributes.addAttribute("uri", "foo", "p:foo", "CDATA", "bar");
+        handler.startElement(null, "b", "b", attributes);
+        handler.characters("abc".toCharArray(), 0, 3);
+        handler.endElement(null, "b", "b");
+        handler.startElement(null, "c", "c", new AttributesImpl());
+        handler.endElement(null, "c", "c");
+        handler.characters("xyz".toCharArray(), 0, 3);
+        handler.endElement("uri", "a", "p:a");
+        handler.endDocument();
+
+        String xml = writer.toString();
+        assertContains(xml, "<p:a");
+        assertContains(xml, "xmlns:p");
+        assertContains(xml, "=");
+        assertContains(xml, "uri");
+        assertContains(xml, ">");
+        assertContains(xml, "<b");
+        assertContains(xml, "p:foo");
+        assertContains(xml, "bar");
+        assertContains(xml, "abc");
+        assertContains(xml, "</b>");
+        assertContains(xml, "<c/>");
+        assertContains(xml, "xyz");
+        assertContains(xml, "</p:a>");
+    }
+
     private void assertContains(String haystack, String needle) {
         if (haystack.indexOf(needle) == -1) {
             fail("'" + haystack + "' does not contain '" + needle+ "'");