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