Return-Path: Delivered-To: apmail-jackrabbit-commits-archive@www.apache.org Received: (qmail 12033 invoked from network); 24 Oct 2007 12:49:35 -0000 Received: from hermes.apache.org (HELO mail.apache.org) (140.211.11.2) by minotaur.apache.org with SMTP; 24 Oct 2007 12:49:35 -0000 Received: (qmail 31848 invoked by uid 500); 24 Oct 2007 12:49:23 -0000 Delivered-To: apmail-jackrabbit-commits-archive@jackrabbit.apache.org Received: (qmail 31822 invoked by uid 500); 24 Oct 2007 12:49:23 -0000 Mailing-List: contact commits-help@jackrabbit.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@jackrabbit.apache.org Delivered-To: mailing list commits@jackrabbit.apache.org Received: (qmail 31813 invoked by uid 99); 24 Oct 2007 12:49:23 -0000 Received: from athena.apache.org (HELO athena.apache.org) (140.211.11.136) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 24 Oct 2007 05:49:23 -0700 X-ASF-Spam-Status: No, hits=-100.0 required=10.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.3] (HELO eris.apache.org) (140.211.11.3) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 24 Oct 2007 12:49:26 +0000 Received: by eris.apache.org (Postfix, from userid 65534) id 4A8021A9832; Wed, 24 Oct 2007 05:49:06 -0700 (PDT) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r587881 - /jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/xml/WorkspaceContentHandler.java Date: Wed, 24 Oct 2007 12:49:06 -0000 To: commits@jackrabbit.apache.org From: angela@apache.org X-Mailer: svnmailer-1.0.8 Message-Id: <20071024124906.4A8021A9832@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org 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; /** * WorkspaceContentHandler... @@ -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 startPrefixMapping(), but wants them + * also as 'xmlns:' attributes.

+ * The check consists in sending SAX events representing a minimal namespaced document + * with namespaces defined only with calls to startPrefixMapping (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 xmlns: attributes + * in startElement(). + * + * @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 xmlns: attributes + * and add those needed before calling superclass. This is a workaround for a Xalan bug + * (at least in version 2.0.1) : org.apache.xalan.serialize.SerializerToXML + * ignores start/endPrefixMapping(). + */ + 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); + } } }