You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commons-dev@ws.apache.org by ve...@apache.org on 2009/08/02 22:41:46 UTC

svn commit: r800144 - in /webservices/commons/trunk/modules/axiom/modules/axiom-api/src: main/java/org/apache/axiom/util/namespace/ main/java/org/apache/axiom/util/stax/dialect/ test/java/org/apache/axiom/util/namespace/

Author: veithen
Date: Sun Aug  2 20:41:46 2009
New Revision: 800144

URL: http://svn.apache.org/viewvc?rev=800144&view=rev
Log:
Work around a problem in the XMLStreamWriter of the StAX reference implementation, which fails to handle masked namespace bindings in the expected way. The solution is to implement all the namespace context related methods in a wrapper and only invoke methods on the underlying writer that don't require information from the namespace context. Note that the same technique could probably be used to work around the non conformance of XLXP.

Added:
    webservices/commons/trunk/modules/axiom/modules/axiom-api/src/main/java/org/apache/axiom/util/namespace/
    webservices/commons/trunk/modules/axiom/modules/axiom-api/src/main/java/org/apache/axiom/util/namespace/ScopedNamespaceContext.java   (with props)
    webservices/commons/trunk/modules/axiom/modules/axiom-api/src/main/java/org/apache/axiom/util/stax/dialect/NamespaceContextCorrectingXMLStreamWriterWrapper.java   (with props)
    webservices/commons/trunk/modules/axiom/modules/axiom-api/src/test/java/org/apache/axiom/util/namespace/
    webservices/commons/trunk/modules/axiom/modules/axiom-api/src/test/java/org/apache/axiom/util/namespace/ScopedNamespaceContextTest.java   (with props)
Modified:
    webservices/commons/trunk/modules/axiom/modules/axiom-api/src/main/java/org/apache/axiom/util/stax/dialect/BEAStreamWriterWrapper.java

Added: webservices/commons/trunk/modules/axiom/modules/axiom-api/src/main/java/org/apache/axiom/util/namespace/ScopedNamespaceContext.java
URL: http://svn.apache.org/viewvc/webservices/commons/trunk/modules/axiom/modules/axiom-api/src/main/java/org/apache/axiom/util/namespace/ScopedNamespaceContext.java?rev=800144&view=auto
==============================================================================
--- webservices/commons/trunk/modules/axiom/modules/axiom-api/src/main/java/org/apache/axiom/util/namespace/ScopedNamespaceContext.java (added)
+++ webservices/commons/trunk/modules/axiom/modules/axiom-api/src/main/java/org/apache/axiom/util/namespace/ScopedNamespaceContext.java Sun Aug  2 20:41:46 2009
@@ -0,0 +1,188 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.axiom.util.namespace;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+import javax.xml.XMLConstants;
+import javax.xml.namespace.NamespaceContext;
+
+/**
+ * {@link NamespaceContext} implementation that supports nested scopes. A scope is typically
+ * associated with a start tag / end tag pair. The implementation takes care of correctly
+ * handling masked namespace bindings. Masking occurs when the same prefix is bound to a different
+ * namespace URI in a nested scope.
+ */
+public class ScopedNamespaceContext implements NamespaceContext {
+    /**
+     * Array containing the prefixes for the namespace bindings.
+     */
+    String[] prefixArray = new String[16];
+    
+    /**
+     * Array containing the URIs for the namespace bindings.
+     */
+    String[] uriArray = new String[16];
+    
+    /**
+     * The number of currently defined namespace bindings.
+     */
+    int bindings;
+    
+    /**
+     * Tracks the scopes defined for this namespace context. Each entry in the array identifies
+     * the first namespace binding defined in the corresponding scope and points to an entry
+     * in {@link #prefixArray}/{@link #uriArray}.
+     */
+    private int[] scopeIndexes = new int[16];
+    
+    /**
+     * The number of currently defined scopes. This is the same as the depth of the current scope,
+     * where the depth of the root scope is 0.
+     */
+    private int scopes;
+
+    /**
+     * Define a prefix binding in the current scope. It will be visible in the current scope as
+     * well as each nested scope, unless the prefix is bound to a different namespace URI in that
+     * scope.
+     * 
+     * @param prefix the prefix to bind
+     * @param namespaceURI the corresponding namespace URI
+     */
+    public void setPrefix(String prefix, String namespaceURI) {
+        if (bindings == prefixArray.length) {
+            int len = prefixArray.length;
+            int newLen = len*2;
+            String[] newPrefixArray = new String[newLen];
+            System.arraycopy(prefixArray, 0, newPrefixArray, 0, len);
+            String[] newUriArray = new String[newLen];
+            System.arraycopy(uriArray, 0, newUriArray, 0, len);
+            prefixArray = newPrefixArray;
+            uriArray = newUriArray;
+        }
+        prefixArray[bindings] = prefix;
+        uriArray[bindings] = namespaceURI;
+        bindings++;
+    }
+    
+    /**
+     * Start a new scope. Since scopes are nested, this will not end the current scope.
+     */
+    public void startScope() {
+        if (scopes == scopeIndexes.length) {
+            int[] newScopeIndexes = new int[scopeIndexes.length*2];
+            System.arraycopy(scopeIndexes, 0, newScopeIndexes, 0, scopeIndexes.length);
+            scopeIndexes = newScopeIndexes;
+        }
+        scopeIndexes[scopes++] = bindings;
+    }
+    
+    /**
+     * End the current scope and restore the scope in which the current scope was nested.
+     * This will remove all prefix bindings declared since the corresponding invocation
+     * of the {@link #startScope()} method.
+     */
+    public void endScope() {
+        bindings = scopeIndexes[--scopes];
+    }
+    
+    public String getNamespaceURI(String prefix) {
+        if (prefix == null) {
+            throw new IllegalArgumentException("prefix can't be null");
+        } else if (prefix.equals(XMLConstants.XML_NS_PREFIX)) {
+            return XMLConstants.XML_NS_URI;
+        } else if (prefix.equals(XMLConstants.XMLNS_ATTRIBUTE)) {
+            return XMLConstants.XMLNS_ATTRIBUTE_NS_URI;
+        } else {
+            for (int i=bindings-1; i>=0; i--) {
+                if (prefix.equals(prefixArray[i])) {
+                    return uriArray[i];
+                }
+            }
+            return null;
+        }
+    }
+
+    public String getPrefix(String namespaceURI) {
+        if (namespaceURI == null) {
+            throw new IllegalArgumentException("namespaceURI can't be null");
+        } else if (namespaceURI.equals(XMLConstants.XML_NS_URI)) {
+            return XMLConstants.XML_NS_PREFIX;
+        } else if (namespaceURI.equals(XMLConstants.XMLNS_ATTRIBUTE_NS_URI)) {
+            return XMLConstants.XMLNS_ATTRIBUTE;
+        } else {
+            outer: for (int i=bindings-1; i>=0; i--) {
+                if (namespaceURI.equals(uriArray[i])) {
+                    String prefix = prefixArray[i];
+                    // Now check that the prefix is not masked
+                    for (int j=i+1; j<bindings; j++) {
+                        if (prefix.equals(prefixArray[j])) {
+                            continue outer;
+                        }
+                    }
+                    return prefix;
+                }
+            }
+            return null;
+        }
+    }
+
+    public Iterator getPrefixes(final String namespaceURI) {
+        return new Iterator() {
+            private int binding = bindings;
+            private String next;
+
+            public boolean hasNext() {
+                if (next == null) {
+                    outer: while (--binding >= 0) {
+                        if (namespaceURI.equals(uriArray[binding])) {
+                            String prefix = prefixArray[binding];
+                            // Now check that the prefix is not masked
+                            for (int j=binding+1; j<bindings; j++) {
+                                if (prefix.equals(prefixArray[j])) {
+                                    continue outer;
+                                }
+                            }
+                            next = prefix;
+                            break;
+                        }
+                    }
+                }
+                return next != null;
+            }
+
+            public Object next() {
+                if (hasNext()) {
+                    String result = next;
+                    next = null;
+                    return result;
+                } else {
+                    throw new NoSuchElementException();
+                }
+            }
+
+            public void remove() {
+                throw new UnsupportedOperationException();
+            }
+        };
+    }
+}

Propchange: webservices/commons/trunk/modules/axiom/modules/axiom-api/src/main/java/org/apache/axiom/util/namespace/ScopedNamespaceContext.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: webservices/commons/trunk/modules/axiom/modules/axiom-api/src/main/java/org/apache/axiom/util/stax/dialect/BEAStreamWriterWrapper.java
URL: http://svn.apache.org/viewvc/webservices/commons/trunk/modules/axiom/modules/axiom-api/src/main/java/org/apache/axiom/util/stax/dialect/BEAStreamWriterWrapper.java?rev=800144&r1=800143&r2=800144&view=diff
==============================================================================
--- webservices/commons/trunk/modules/axiom/modules/axiom-api/src/main/java/org/apache/axiom/util/stax/dialect/BEAStreamWriterWrapper.java (original)
+++ webservices/commons/trunk/modules/axiom/modules/axiom-api/src/main/java/org/apache/axiom/util/stax/dialect/BEAStreamWriterWrapper.java Sun Aug  2 20:41:46 2009
@@ -22,9 +22,10 @@
 import javax.xml.stream.XMLStreamException;
 import javax.xml.stream.XMLStreamWriter;
 
-import org.apache.axiom.util.stax.wrapper.XMLStreamWriterWrapper;
-
-class BEAStreamWriterWrapper extends XMLStreamWriterWrapper {
+// The stream writer implementation of the reference implementation doesn't handle masked namespace
+// bindings correctly. We extend NamespaceContextCorrectingXMLStreamWriterWrapper to work around
+// this problem.
+class BEAStreamWriterWrapper extends NamespaceContextCorrectingXMLStreamWriterWrapper {
     public BEAStreamWriterWrapper(XMLStreamWriter parent) {
         super(parent);
     }

Added: webservices/commons/trunk/modules/axiom/modules/axiom-api/src/main/java/org/apache/axiom/util/stax/dialect/NamespaceContextCorrectingXMLStreamWriterWrapper.java
URL: http://svn.apache.org/viewvc/webservices/commons/trunk/modules/axiom/modules/axiom-api/src/main/java/org/apache/axiom/util/stax/dialect/NamespaceContextCorrectingXMLStreamWriterWrapper.java?rev=800144&view=auto
==============================================================================
--- webservices/commons/trunk/modules/axiom/modules/axiom-api/src/main/java/org/apache/axiom/util/stax/dialect/NamespaceContextCorrectingXMLStreamWriterWrapper.java (added)
+++ webservices/commons/trunk/modules/axiom/modules/axiom-api/src/main/java/org/apache/axiom/util/stax/dialect/NamespaceContextCorrectingXMLStreamWriterWrapper.java Sun Aug  2 20:41:46 2009
@@ -0,0 +1,135 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.axiom.util.stax.dialect;
+
+import javax.xml.namespace.NamespaceContext;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+
+import org.apache.axiom.util.namespace.ScopedNamespaceContext;
+import org.apache.axiom.util.stax.wrapper.XMLStreamWriterWrapper;
+
+/**
+ * {@link XMLStreamWriter} wrapper that handles namespace bindings on behalf of the underlying
+ * writer. This wrapper can be used to correct two issues found in some stream writer
+ * implementations:
+ * <ol>
+ *   <li>The writer doesn't correctly scope the namespace bindings. According to the StAX
+ *       specifications, the scope of a namespace binding defined using
+ *       {@link XMLStreamWriter#setPrefix(String, String)} or
+ *       {@link XMLStreamWriter#setDefaultNamespace(String)} is limited to
+ *       "the current <tt>START_ELEMENT</tt> / <tt>END_ELEMENT</tt> pair". Some implementations
+ *       such as early versions of XL XP-J don't satisfy this requirement.
+ *   <li>The writer doesn't handle masked prefixes correctly. To ensure consistent behavior
+ *       in the presence of masked prefixes, the {@link XMLStreamWriter#getPrefix(String)} method
+ *       (and the corresponding methods in the namespace context returned by
+ *       {@link XMLStreamWriter#getNamespaceContext()}) must not return a prefix that
+ *       is bound to a different namespace URI in a nested scope. Some implementations such as
+ *       the StAX reference implementation fail to meet this requirement.
+ * </ol>
+ * <p>
+ * Invocations of the following methods will be completely processed by the wrapper, and will never
+ * reach the underlying writer:
+ * <ul>
+ *   <li>{@link XMLStreamWriter#getNamespaceContext()}
+ *   <li>{@link XMLStreamWriter#setNamespaceContext(NamespaceContext)}
+ *   <li>{@link XMLStreamWriter#getPrefix(String)}
+ *   <li>{@link XMLStreamWriter#setDefaultNamespace(String)}
+ *   <li>{@link XMLStreamWriter#setPrefix(String, String)}
+ * </ul>
+ * <p>
+ * The following methods rely on information from the namespace context to choose a the namespace
+ * prefix; the wrapper redirects invocations of these methods to the corresponding variants taking
+ * an explicit prefix parameter:
+ * <ul>
+ *   <li>{@link XMLStreamWriter#writeStartElement(String, String)}
+ *   <li>{@link XMLStreamWriter#writeAttribute(String, String, String)}
+ *   <li>{@link XMLStreamWriter#writeEmptyElement(String, String)}
+ * </ul>
+ * <p>
+ * This implies that if the wrapper is used, these methods will never be called on the underlying
+ * writer.
+ */
+public class NamespaceContextCorrectingXMLStreamWriterWrapper extends XMLStreamWriterWrapper {
+    private final ScopedNamespaceContext namespaceContext = new ScopedNamespaceContext();
+
+    public NamespaceContextCorrectingXMLStreamWriterWrapper(XMLStreamWriter parent) {
+        super(parent);
+    }
+
+    public NamespaceContext getNamespaceContext() {
+        return namespaceContext;
+    }
+
+    public void setNamespaceContext(NamespaceContext context) throws XMLStreamException {
+        // TODO: not sure yet how to implement this method
+        throw new UnsupportedOperationException();
+    }
+
+    public String getPrefix(String uri) throws XMLStreamException {
+        return namespaceContext.getPrefix(uri);
+    }
+
+    public void setDefaultNamespace(String uri) throws XMLStreamException {
+        namespaceContext.setPrefix("", uri);
+    }
+
+    public void setPrefix(String prefix, String uri) throws XMLStreamException {
+        namespaceContext.setPrefix(prefix, uri);
+    }
+
+    public String internalGetPrefix(String namespaceURI) throws XMLStreamException {
+        String prefix = namespaceContext.getPrefix(namespaceURI);
+        if (prefix == null) {
+            throw new XMLStreamException("Unbound namespace URI '" + namespaceURI + "'");
+        } else {
+            return prefix;
+        }
+    }
+    
+    public void writeStartElement(String prefix, String localName, String namespaceURI)
+            throws XMLStreamException {
+        super.writeStartElement(prefix, localName, namespaceURI);
+        namespaceContext.startScope();
+    }
+
+    public void writeStartElement(String namespaceURI, String localName) throws XMLStreamException {
+        super.writeStartElement(internalGetPrefix(namespaceURI), namespaceURI, localName);
+    }
+
+    public void writeStartElement(String localName) throws XMLStreamException {
+        super.writeStartElement(localName);
+        namespaceContext.startScope();
+    }
+
+    public void writeEndElement() throws XMLStreamException {
+        super.writeEndElement();
+        namespaceContext.endScope();
+    }
+
+    public void writeEmptyElement(String namespaceURI, String localName) throws XMLStreamException {
+        super.writeEmptyElement(internalGetPrefix(namespaceURI), namespaceURI, localName);
+    }
+
+    public void writeAttribute(String namespaceURI, String localName, String value)
+            throws XMLStreamException {
+        super.writeAttribute(internalGetPrefix(namespaceURI), namespaceURI, localName, value);
+    }
+}

Propchange: webservices/commons/trunk/modules/axiom/modules/axiom-api/src/main/java/org/apache/axiom/util/stax/dialect/NamespaceContextCorrectingXMLStreamWriterWrapper.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: webservices/commons/trunk/modules/axiom/modules/axiom-api/src/test/java/org/apache/axiom/util/namespace/ScopedNamespaceContextTest.java
URL: http://svn.apache.org/viewvc/webservices/commons/trunk/modules/axiom/modules/axiom-api/src/test/java/org/apache/axiom/util/namespace/ScopedNamespaceContextTest.java?rev=800144&view=auto
==============================================================================
--- webservices/commons/trunk/modules/axiom/modules/axiom-api/src/test/java/org/apache/axiom/util/namespace/ScopedNamespaceContextTest.java (added)
+++ webservices/commons/trunk/modules/axiom/modules/axiom-api/src/test/java/org/apache/axiom/util/namespace/ScopedNamespaceContextTest.java Sun Aug  2 20:41:46 2009
@@ -0,0 +1,101 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.axiom.util.namespace;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import javax.xml.namespace.NamespaceContext;
+
+import junit.framework.TestCase;
+
+public class ScopedNamespaceContextTest extends TestCase {
+    private static Set getPrefixes(NamespaceContext nc, String namespaceURI) {
+        Set result = new HashSet();
+        for (Iterator it = nc.getPrefixes(namespaceURI); it.hasNext(); ) {
+            result.add(it.next());
+        }
+        return result;
+    }
+    
+    public void testSimple() {
+        ScopedNamespaceContext nc = new ScopedNamespaceContext();
+        nc.setPrefix("", "urn:ns1");
+        nc.setPrefix("a", "urn:ns2");
+        nc.setPrefix("b", "urn:ns3");
+        assertEquals("urn:ns1", nc.getNamespaceURI(""));
+        assertEquals("urn:ns2", nc.getNamespaceURI("a"));
+        assertEquals("urn:ns3", nc.getNamespaceURI("b"));
+        assertEquals("", nc.getPrefix("urn:ns1"));
+        assertEquals("a", nc.getPrefix("urn:ns2"));
+        assertEquals("b", nc.getPrefix("urn:ns3"));
+        assertEquals(Collections.singleton(""), getPrefixes(nc, "urn:ns1"));
+        assertEquals(Collections.singleton("a"), getPrefixes(nc, "urn:ns2"));
+        assertEquals(Collections.singleton("b"), getPrefixes(nc, "urn:ns3"));
+    }
+    
+    public void testMultiplePrefixes() {
+        ScopedNamespaceContext nc = new ScopedNamespaceContext();
+        nc.setPrefix("", "urn:ns1");
+        nc.setPrefix("a", "urn:ns2");
+        nc.setPrefix("b", "urn:ns1");
+        String prefix = nc.getPrefix("urn:ns1");
+        assertTrue(prefix.equals("") || prefix.equals("b"));
+        assertEquals(new HashSet(Arrays.asList(new String[] { "", "b" })),
+                     getPrefixes(nc, "urn:ns1"));
+    }
+    
+    public void testScope() {
+        ScopedNamespaceContext nc = new ScopedNamespaceContext();
+        nc.setPrefix("ns1", "urn:ns1");
+        nc.startScope();
+        nc.setPrefix("ns2", "urn:ns2");
+        nc.startScope();
+        nc.setPrefix("ns3", "urn:ns3");
+        assertEquals("urn:ns1", nc.getNamespaceURI("ns1"));
+        assertEquals("urn:ns2", nc.getNamespaceURI("ns2"));
+        assertEquals("urn:ns3", nc.getNamespaceURI("ns3"));
+        nc.endScope();
+        assertEquals("urn:ns1", nc.getNamespaceURI("ns1"));
+        assertEquals("urn:ns2", nc.getNamespaceURI("ns2"));
+        assertNull(nc.getNamespaceURI("ns3"));
+        nc.endScope();
+        assertEquals("urn:ns1", nc.getNamespaceURI("ns1"));
+        assertNull(nc.getNamespaceURI("ns2"));
+        assertNull(nc.getNamespaceURI("ns3"));
+    }
+    
+    public void testMaskedPrefix() {
+        ScopedNamespaceContext nc = new ScopedNamespaceContext();
+        nc.setPrefix("p", "urn:ns1");
+        nc.startScope();
+        nc.setPrefix("p", "urn:ns2");
+        assertEquals("urn:ns2", nc.getNamespaceURI("p"));
+        assertNull(nc.getPrefix("urn:ns1"));
+        assertEquals(Collections.singleton("p"), getPrefixes(nc, "urn:ns2"));
+        assertFalse(nc.getPrefixes("urn:ns1").hasNext());
+        nc.endScope();
+        assertEquals("p", nc.getPrefix("urn:ns1"));
+        assertEquals(Collections.singleton("p"), getPrefixes(nc, "urn:ns1"));
+    }
+}

Propchange: webservices/commons/trunk/modules/axiom/modules/axiom-api/src/test/java/org/apache/axiom/util/namespace/ScopedNamespaceContextTest.java
------------------------------------------------------------------------------
    svn:eol-style = native