Author: angela
Date: Wed Oct 24 05:49:05 2007
New Revision: 587881
URL: http://svn.apache.org/viewvc?rev=587881&view=rev
Log:
JCR-1086 JCR2SPI: Workspace.getImportHandler creates a handler which doesn't work properly
under JDK 1.4
(code from Cocoon project. thanks carsten.)
Modified:
jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/xml/WorkspaceContentHandler.java
Modified: jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/xml/WorkspaceContentHandler.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/xml/WorkspaceContentHandler.java?rev=587881&r1=587880&r2=587881&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/xml/WorkspaceContentHandler.java
(original)
+++ jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/xml/WorkspaceContentHandler.java
Wed Oct 24 05:49:05 2007
@@ -20,7 +20,9 @@
import org.xml.sax.Locator;
import org.xml.sax.Attributes;
import org.xml.sax.helpers.DefaultHandler;
+import org.xml.sax.helpers.AttributesImpl;
import org.apache.jackrabbit.util.Text;
+import org.apache.jackrabbit.spi.Name;
import org.slf4j.LoggerFactory;
import org.slf4j.Logger;
@@ -30,11 +32,17 @@
import javax.xml.transform.sax.TransformerHandler;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.TransformerException;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.FileInputStream;
+import java.io.StringWriter;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.List;
+import java.util.ArrayList;
/**
* <code>WorkspaceContentHandler</code>...
@@ -62,14 +70,22 @@
SAXTransformerFactory stf = (SAXTransformerFactory) SAXTransformerFactory.newInstance();
TransformerHandler th = stf.newTransformerHandler();
th.setResult(new StreamResult(new FileOutputStream(tmpFile)));
- this.delegatee = th;
+ if (NamespaceFixingContentHandler.isRequired(stf)) {
+ this.delegatee = new NamespaceFixingContentHandler(th);
+ } else {
+ this.delegatee = th;
+ }
} catch (FileNotFoundException e) {
throw new RepositoryException(e);
} catch (IOException e) {
throw new RepositoryException(e);
} catch (TransformerConfigurationException e) {
throw new RepositoryException(e);
+ } catch (TransformerException e) {
+ throw new RepositoryException(e);
+ } catch (SAXException e) {
+ throw new RepositoryException(e);
}
}
@@ -124,5 +140,293 @@
public void startElement(String namespaceURI, String localName, String qName, Attributes
atts) throws SAXException {
delegatee.startElement(namespaceURI, localName, qName, atts);
+ }
+
+ //--------------------------------------------------------< inner class >---
+ /**
+ * A ContentHandler implementation that ensures that all namespace prefixes
+ * are also present as 'xmlns:' attributes. This used to circumvent Xalan's
+ * serialization behaviour which ignores namespaces if they're not present
+ * as 'xmlns:xxx' attributes.
+ * The problem arises with SAX implementations such as the default present
+ * with JDK 1.4.2.
+ */
+ private static class NamespaceFixingContentHandler implements ContentHandler {
+
+ /** The wrapped content handler. */
+ private final ContentHandler contentHandler;
+
+ /**
+ * The prefixes of startPrefixMapping() declarations for the coming element.
+ */
+ private final List prefixList = new ArrayList();
+
+ /**
+ * The URIs of startPrefixMapping() declarations for the coming element.
+ */
+ private final List uriList = new ArrayList();
+
+ /**
+ * Maps of URI<->prefix mappings. Used to work around a bug in the Xalan
+ * serializer.
+ */
+ private final Map uriToPrefixMap = new HashMap();
+ private final Map prefixToUriMap = new HashMap();
+
+ /**
+ * True if there has been some startPrefixMapping() for the coming element.
+ */
+ private boolean hasMappings = false;
+
+ /**
+ * Create an instance of this ContentHandler implementation wrapping
+ * the given ContentHandler.
+ *
+ * @param ch ContentHandler to be wrapped.
+ */
+ private NamespaceFixingContentHandler(ContentHandler ch) {
+ this.contentHandler = ch;
+ }
+
+ /**
+ * Checks if the used TransformerHandler implementation correctly handles
+ * namespaces set using <code>startPrefixMapping()</code>, but wants
them
+ * also as 'xmlns:' attributes.<p/>
+ * The check consists in sending SAX events representing a minimal namespaced document
+ * with namespaces defined only with calls to <code>startPrefixMapping</code>
(no
+ * xmlns:xxx attributes) and check if they are present in the resulting text.
+ */
+ private static boolean isRequired(SAXTransformerFactory factory)
+ throws TransformerException, SAXException {
+ // Serialize a minimal document to check how namespaces are handled.
+ StringWriter writer = new StringWriter();
+
+ String uri = "namespaceuri";
+ String prefix = "nsp";
+ String check = "xmlns:" + prefix + "='" + uri + "'";
+
+ TransformerHandler handler = factory.newTransformerHandler();
+ handler.setResult(new StreamResult(writer));
+
+ // Output a single element
+ handler.startDocument();
+ handler.startPrefixMapping(prefix, uri);
+ handler.startElement(uri, "element", "element", new AttributesImpl());
+ handler.endElement(uri, "element", "element");
+ handler.endPrefixMapping(prefix);
+ handler.endDocument();
+
+ String text = writer.toString();
+
+ // Check if the namespace is there (replace " by ' to be sure of what we search
in)
+ return (text.replace('"', '\'').indexOf(check) == -1);
+ }
+
+ private void clearMappings() {
+ hasMappings = false;
+ prefixList.clear();
+ uriList.clear();
+ }
+
+ //-------------------------------------------------< ContentHandler >---
+ /**
+ * @see ContentHandler#startDocument()
+ */
+ public void startDocument() throws SAXException {
+ uriToPrefixMap.clear();
+ prefixToUriMap.clear();
+ clearMappings();
+ contentHandler.startDocument();
+ }
+
+ /**
+ * Track mappings to be able to add <code>xmlns:</code> attributes
+ * in <code>startElement()</code>.
+ *
+ * @see org.xml.sax.ContentHandler#startPrefixMapping(String, String)
+ */
+ public void startPrefixMapping(String prefix, String uri) throws SAXException {
+ // Store the mappings to reconstitute xmlns:attributes
+ // except prefixes starting with "xml": these are reserved
+ // VG: (uri != null) fixes NPE in startElement
+ if (uri != null && !prefix.startsWith("xml")) {
+ hasMappings = true;
+ prefixList.add(prefix);
+ uriList.add(uri);
+
+ // append the prefix colon now, in order to save concatenations
+ // later, but only for non-empty prefixes.
+ if (prefix.length() > 0) {
+ uriToPrefixMap.put(uri, prefix + ":");
+ } else {
+ uriToPrefixMap.put(uri, prefix);
+ }
+ prefixToUriMap.put(prefix, uri);
+ }
+ contentHandler.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>
+ * ignores <code>start/endPrefixMapping()</code>.
+ */
+ public void startElement(String eltUri, String eltLocalName, String eltQName, Attributes
attrs)
+ throws SAXException {
+
+ // try to restore the qName. The map already contains the colon
+ if (null != eltUri && eltUri.length() != 0 && uriToPrefixMap.containsKey(eltUri))
{
+ eltQName = uriToPrefixMap.get(eltUri) + eltLocalName;
+ }
+ if (!hasMappings) {
+ // no mappings present -> simply delegate.
+ contentHandler.startElement(eltUri, eltLocalName, eltQName, attrs);
+ } else {
+ // add xmlns* attributes where needed
+ AttributesImpl newAttrs = null;
+ int attrCount = attrs.getLength();
+
+ // check if namespaces present in the uri/prefix list are present
+ // in the attributes and eventually add them to the attributes.
+ for (int mapping = 0; mapping < prefixList.size(); mapping++) {
+
+ // Build infos for the namespace
+ String uri = (String) uriList.get(mapping);
+ String prefix = (String) prefixList.get(mapping);
+ String qName = prefix.length() == 0 ? "xmlns" : ("xmlns:" + prefix);
+
+ // Search for the corresponding xmlns* attribute
+ boolean found = false;
+ for (int attr = 0; attr < attrCount; attr++) {
+ if (qName.equals(attrs.getQName(attr))) {
+ // Check if mapping and attribute URI match
+ if (!uri.equals(attrs.getValue(attr))) {
+ throw new SAXException("URI in prefix mapping and attribute
do not match");
+ }
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ // Need to add the namespace
+ if (newAttrs == null) {
+ // test if attrs passed into this call is empty in
+ // order to avoid an infinite loop (known SAX bug)
+ if (attrCount == 0) {
+ newAttrs = new AttributesImpl();
+ } else {
+ newAttrs = new AttributesImpl(attrs);
+ }
+ }
+
+ if (prefix.length() == 0) {
+ newAttrs.addAttribute(Name.NS_XML_URI, "xmlns", "xmlns", "CDATA",
uri);
+ } else {
+ newAttrs.addAttribute(Name.NS_XML_URI, prefix, qName, "CDATA",
uri);
+ }
+ }
+ } // end for mapping
+
+ // cleanup mappings for the next element
+ clearMappings();
+
+ // delegate startElement call to the wrapped content handler
+ // with new attributes if adjustment for namespaces was made.
+ contentHandler.startElement(eltUri, eltLocalName, eltQName, newAttrs == null
? attrs : newAttrs);
+ }
+ }
+
+
+ /**
+ * Receive notification of the end of an element.
+ * Try to restore the element qName.
+ *
+ * @see org.xml.sax.ContentHandler#endElement(String, String, String)
+ */
+ public void endElement(String eltUri, String eltLocalName, String eltQName) throws
SAXException {
+ // try to restore the qName. The map already contains the colon
+ if (null != eltUri && eltUri.length() != 0 && uriToPrefixMap.containsKey(eltUri))
{
+ eltQName = uriToPrefixMap.get(eltUri) + eltLocalName;
+ }
+ contentHandler.endElement(eltUri, eltLocalName, eltQName);
+ }
+
+ /**
+ * End the scope of a prefix-URI mapping:
+ * remove entry from mapping tables.
+ *
+ * @see org.xml.sax.ContentHandler#endPrefixMapping(String)
+ */
+ public void endPrefixMapping(String prefix) throws SAXException {
+ // remove mappings for xalan-bug-workaround.
+ // Unfortunately, we're not passed the uri, but the prefix here,
+ // so we need to maintain maps in both directions.
+ if (prefixToUriMap.containsKey(prefix)) {
+ uriToPrefixMap.remove(prefixToUriMap.get(prefix));
+ prefixToUriMap.remove(prefix);
+ }
+
+ if (hasMappings) {
+ // most of the time, start/endPrefixMapping calls have an element event between
them,
+ // which will clear the hasMapping flag and so this code will only be executed
in the
+ // rather rare occasion when there are start/endPrefixMapping calls with
no element
+ // event in between. If we wouldn't remove the items from the prefixList
and uriList here,
+ // the namespace would be incorrectly declared on the next element following
the
+ // endPrefixMapping call.
+ int pos = prefixList.lastIndexOf(prefix);
+ if (pos != -1) {
+ prefixList.remove(pos);
+ uriList.remove(pos);
+ }
+ }
+ contentHandler.endPrefixMapping(prefix);
+ }
+
+ /**
+ * @see org.xml.sax.ContentHandler#endDocument()
+ */
+ public void endDocument() throws SAXException {
+ uriToPrefixMap.clear();
+ prefixToUriMap.clear();
+ clearMappings();
+ contentHandler.endDocument();
+ }
+
+ /**
+ * @see org.xml.sax.ContentHandler#characters(char[], int, int)
+ */
+ public void characters(char[] ch, int start, int length) throws SAXException {
+ contentHandler.characters(ch, start, length);
+ }
+
+ /**
+ * @see org.xml.sax.ContentHandler#ignorableWhitespace(char[], int, int)
+ */
+ public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException
{
+ contentHandler.ignorableWhitespace(ch, start, length);
+ }
+
+ /**
+ * @see org.xml.sax.ContentHandler#processingInstruction(java.lang.String, java.lang.String)
+ */
+ public void processingInstruction(String target, String data) throws SAXException
{
+ contentHandler.processingInstruction(target, data);
+ }
+
+ /**
+ * @see org.xml.sax.ContentHandler#setDocumentLocator(org.xml.sax.Locator)
+ */
+ public void setDocumentLocator(Locator locator) {
+ contentHandler.setDocumentLocator(locator);
+ }
+
+ /**
+ * @see org.xml.sax.ContentHandler#skippedEntity(java.lang.String)
+ */
+ public void skippedEntity(String name) throws SAXException {
+ contentHandler.skippedEntity(name);
+ }
}
}
|