You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by an...@apache.org on 2011/02/17 16:54:28 UTC

svn commit: r1071660 - in /jackrabbit/trunk/jackrabbit-webdav: ./ src/main/java/org/apache/jackrabbit/webdav/ src/main/java/org/apache/jackrabbit/webdav/header/ src/main/java/org/apache/jackrabbit/webdav/lock/ src/main/java/org/apache/jackrabbit/webdav...

Author: angela
Date: Thu Feb 17 15:54:27 2011
New Revision: 1071660

URL: http://svn.apache.org/viewvc?rev=1071660&view=rev
Log:
JCR-2897 - Remove jcr-commons dependency from jackrabbit-webdav

Added:
    jackrabbit/trunk/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/util/EncodeUtil.java   (with props)
    jackrabbit/trunk/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/xml/ResultHelper.java   (with props)
Modified:
    jackrabbit/trunk/jackrabbit-webdav/pom.xml
    jackrabbit/trunk/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/AbstractLocatorFactory.java
    jackrabbit/trunk/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/header/LabelHeader.java
    jackrabbit/trunk/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/lock/SimpleLockManager.java
    jackrabbit/trunk/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/xml/DomUtil.java

Modified: jackrabbit/trunk/jackrabbit-webdav/pom.xml
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-webdav/pom.xml?rev=1071660&r1=1071659&r2=1071660&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-webdav/pom.xml (original)
+++ jackrabbit/trunk/jackrabbit-webdav/pom.xml Thu Feb 17 15:54:27 2011
@@ -31,7 +31,7 @@
   </parent>
   <artifactId>jackrabbit-webdav</artifactId>
   <name>Jackrabbit WebDAV Library</name>
-  <description>WebDAV library used by the Jackrabbit WebDAV support</description>
+  <description>Generic WebDAV Library</description>
 
   <build>
     <plugins>
@@ -50,11 +50,6 @@
 
   <dependencies>
     <dependency>
-      <groupId>org.apache.jackrabbit</groupId>
-      <artifactId>jackrabbit-jcr-commons</artifactId>
-      <version>2.3-SNAPSHOT</version>
-    </dependency>
-    <dependency>
       <groupId>org.slf4j</groupId>
       <artifactId>slf4j-api</artifactId>
     </dependency>

Modified: jackrabbit/trunk/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/AbstractLocatorFactory.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/AbstractLocatorFactory.java?rev=1071660&r1=1071659&r2=1071660&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/AbstractLocatorFactory.java (original)
+++ jackrabbit/trunk/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/AbstractLocatorFactory.java Thu Feb 17 15:54:27 2011
@@ -16,9 +16,9 @@
  */
 package org.apache.jackrabbit.webdav;
 
+import org.apache.jackrabbit.webdav.util.EncodeUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.apache.jackrabbit.util.Text;
 
 /**
  * <code>AbstractLocatorFactory</code> is an implementation of the DavLocatorFactory
@@ -119,7 +119,7 @@ public abstract class AbstractLocatorFac
             resourcePath = null;
             workspacePath = null;
         } else {
-            resourcePath = Text.unescape(href);
+            resourcePath = EncodeUtil.unescape(href);
             // retrieve wspPath: look for the first slash ignoring the leading one
             int pos = href.indexOf('/', 1);
             if (pos == -1) {
@@ -127,7 +127,7 @@ public abstract class AbstractLocatorFac
                 workspacePath = resourcePath;
             } else {
                 // separate the workspace path from the resource path.
-                workspacePath = Text.unescape(href.substring(0, pos));
+                workspacePath = EncodeUtil.unescape(href.substring(0, pos));
             }
         }
 
@@ -228,7 +228,7 @@ public abstract class AbstractLocatorFac
                 if (!resourcePath.startsWith(workspacePath)) {
                     throw new IllegalArgumentException("Resource path '" + resourcePath + "' does not start with workspace path '" + workspacePath + ".");
                 }
-                buf.append(Text.escapePath(resourcePath));
+                buf.append(EncodeUtil.escapePath(resourcePath));
             }
             int length = buf.length();
             if (length > 0 && buf.charAt(length - 1) != '/') {
@@ -315,7 +315,7 @@ public abstract class AbstractLocatorFac
          * Returns an 'href' consisting of prefix and resource path (which starts
          * with the workspace path). It assures a trailing '/' in case the href
          * is used for collection. Note, that the resource path is
-         * {@link Text#escapePath(String) escaped}.
+         * {@link org.apache.jackrabbit.webdav.util.EncodeUtil#escapePath(String) escaped}.
          *
          * @param isCollection
          * @return href String representing the text of the href element

Modified: jackrabbit/trunk/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/header/LabelHeader.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/header/LabelHeader.java?rev=1071660&r1=1071659&r2=1071660&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/header/LabelHeader.java (original)
+++ jackrabbit/trunk/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/header/LabelHeader.java Thu Feb 17 15:54:27 2011
@@ -16,8 +16,8 @@
  */
 package org.apache.jackrabbit.webdav.header;
 
-import org.apache.jackrabbit.util.Text;
 import org.apache.jackrabbit.webdav.WebdavRequest;
+import org.apache.jackrabbit.webdav.util.EncodeUtil;
 import org.apache.jackrabbit.webdav.version.DeltaVConstants;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -47,7 +47,7 @@ public class LabelHeader implements Head
     }
 
     public String getHeaderValue() {
-        return Text.escape(label);
+        return EncodeUtil.escape(label);
     }
 
     public static LabelHeader parse(WebdavRequest request) {
@@ -55,7 +55,7 @@ public class LabelHeader implements Head
         if (hv == null) {
             return null;
         } else {
-            return new LabelHeader(Text.unescape(hv));
+            return new LabelHeader(EncodeUtil.unescape(hv));
         }
     }
 }
\ No newline at end of file

Modified: jackrabbit/trunk/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/lock/SimpleLockManager.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/lock/SimpleLockManager.java?rev=1071660&r1=1071659&r2=1071660&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/lock/SimpleLockManager.java (original)
+++ jackrabbit/trunk/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/lock/SimpleLockManager.java Thu Feb 17 15:54:27 2011
@@ -16,7 +16,6 @@
  */
 package org.apache.jackrabbit.webdav.lock;
 
-import org.apache.jackrabbit.util.Text;
 import org.apache.jackrabbit.webdav.DavException;
 import org.apache.jackrabbit.webdav.DavResource;
 import org.apache.jackrabbit.webdav.DavResourceIterator;
@@ -82,7 +81,7 @@ public class SimpleLockManager implement
         if (lock == null) {
             // check, if child of deep locked parent
             if (!path.equals("/")) {
-                ActiveLock parentLock = getLock(Text.getRelativeParent(path, 1));
+                ActiveLock parentLock = getLock(getParentPath(path));
                 if (parentLock != null && parentLock.isDeep()) {
                     lock = parentLock;
                 }
@@ -118,12 +117,12 @@ public class SimpleLockManager implement
         // collection or with a lock present on any member resource.
         for (String key : locks.keySet()) {
             // TODO: is check for lock on internal-member correct?
-            if (Text.isDescendant(key, resourcePath)) {
+            if (isDescendant(key, resourcePath)) {
                 ActiveLock l = locks.get(key);
-                if (l.isDeep() || (key.equals(Text.getRelativeParent(resourcePath, 1)) && !resource.isCollection())) {
+                if (l.isDeep() || (key.equals(getParentPath(resourcePath)) && !resource.isCollection())) {
                     throw new DavException(DavServletResponse.SC_LOCKED, "Resource '" + resource.getResourcePath() + "' already inherits a lock by its collection.");
                 }
-            } else if (Text.isDescendant(resourcePath, key)) {
+            } else if (isDescendant(resourcePath, key)) {
                 if (lockInfo.isDeep() || isInternalMember(resource, key)) {
                     throw new DavException(DavServletResponse.SC_CONFLICT, "Resource '" + resource.getResourcePath() + "' cannot be locked due to a lock present on the member resource '" + key + "'.");
                 }
@@ -185,7 +184,7 @@ public class SimpleLockManager implement
      * @return
      */
     private static boolean isInternalMember(DavResource resource, String memberPath) {
-        if (resource.getResourcePath().equals(Text.getRelativeParent(memberPath, 1))) {
+        if (resource.getResourcePath().equals(getParentPath(memberPath))) {
             // find the member with the given path
             DavResourceIterator it = resource.getMembers();
             while (it.hasNext()) {
@@ -198,5 +197,37 @@ public class SimpleLockManager implement
         }
         return false;
     }
+
+    /**
+     * @param path Path to retrieve the parent path for.
+     * @return empty string if the specified path contains no '/', "/" if the
+     * last index of '/' is zero. Otherwise the last segment is cut off the
+     * specified path.
+     */
+    private static String getParentPath(String path) {
+        int idx = path.lastIndexOf('/');
+        switch (idx) {
+            case -1:
+                return "";
+            case 0:
+                return "/";
+            default:
+                return path.substring(0, idx);
+        }
+    }
+
+    /**
+     * Determines if the <code>descendant</code> path is hierarchical a
+     * descendant of <code>path</code>.
+     *
+     * @param path     the current path
+     * @param descendant the potential descendant
+     * @return <code>true</code> if the <code>descendant</code> is a descendant;
+     *         <code>false</code> otherwise.
+     */
+    private static boolean isDescendant(String path, String descendant) {
+        String pattern = path.endsWith("/") ? path : path + "/";
+        return !pattern.equals(descendant) && descendant.startsWith(pattern);
+    }
 }
 

Added: jackrabbit/trunk/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/util/EncodeUtil.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/util/EncodeUtil.java?rev=1071660&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/util/EncodeUtil.java (added)
+++ jackrabbit/trunk/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/util/EncodeUtil.java Thu Feb 17 15:54:27 2011
@@ -0,0 +1,228 @@
+/*
+ * 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.jackrabbit.webdav.util;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.BitSet;
+
+/**
+ * <code>EncodeUtil</code> provides helper methods for URL encoding and decoding
+ * (copied from jcr-commons jackrabbit.util.Text).
+ *
+ * @see <a href="https://issues.apache.org/jira/browse/JCR-2897">JCR-2897</a>.
+ */
+public final class EncodeUtil {
+
+    /**
+     * logger instance
+     */
+    private static final Logger log = LoggerFactory.getLogger(EncodeUtil.class);
+
+    /**
+     * hextable used for {@link #escape(String, char, boolean)}
+     */
+    public static final char[] hexTable = "0123456789abcdef".toCharArray();
+
+    /**
+     * The list of characters that are not encoded by the <code>escape()</code>
+     * and <code>unescape()</code> METHODS. They contains the characters as
+     * defined 'unreserved' in section 2.3 of the RFC 2396 'URI generic syntax':
+     * <p/>
+     * <pre>
+     * unreserved  = alphanum | mark
+     * mark        = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
+     * </pre>
+     */
+    private static BitSet URISave;
+
+    /**
+     * Same as {@link #URISave} but also contains the '/'
+     */
+    private static BitSet URISaveEx;
+
+    static {
+        URISave = new BitSet(256);
+        int i;
+        for (i = 'a'; i <= 'z'; i++) {
+            URISave.set(i);
+        }
+        for (i = 'A'; i <= 'Z'; i++) {
+            URISave.set(i);
+        }
+        for (i = '0'; i <= '9'; i++) {
+            URISave.set(i);
+        }
+        URISave.set('-');
+        URISave.set('_');
+        URISave.set('.');
+        URISave.set('!');
+        URISave.set('~');
+        URISave.set('*');
+        URISave.set('\'');
+        URISave.set('(');
+        URISave.set(')');
+
+        URISaveEx = (BitSet) URISave.clone();
+        URISaveEx.set('/');
+    }
+
+    /**
+     * Does a URL encoding of the <code>string</code>. The characters that
+     * don't need encoding are those defined 'unreserved' in section 2.3 of
+     * the 'URI generic syntax' RFC 2396.
+     *
+     * @param string the string to encode
+     * @return the escaped string
+     * @throws NullPointerException if <code>string</code> is <code>null</code>.
+     */
+    public static String escape(String string) {
+        return escape(string, '%', false);
+    }
+
+    /**
+     * Does a URL encoding of the <code>path</code>. The characters that
+     * don't need encoding are those defined 'unreserved' in section 2.3 of
+     * the 'URI generic syntax' RFC 2396. In contrast to the
+     * {@link #escape(String)} method, not the entire path string is escaped,
+     * but every individual part (i.e. the slashes are not escaped).
+     *
+     * @param path the path to encode
+     * @return the escaped path
+     * @throws NullPointerException if <code>path</code> is <code>null</code>.
+     */
+    public static String escapePath(String path) {
+        return escape(path, '%', true);
+    }
+
+    /**
+     * Does an URL encoding of the <code>string</code> using the
+     * <code>escape</code> character. The characters that don't need encoding
+     * are those defined 'unreserved' in section 2.3 of the 'URI generic syntax'
+     * RFC 2396, but without the escape character. If <code>isPath</code> is
+     * <code>true</code>, additionally the slash '/' is ignored, too.
+     *
+     * @param string the string to encode.
+     * @param escape the escape character.
+     * @param isPath if <code>true</code>, the string is treated as path
+     * @return the escaped string
+     * @throws NullPointerException if <code>string</code> is <code>null</code>.
+     */
+    private static String escape(String string, char escape, boolean isPath) {
+        try {
+            BitSet validChars = isPath ? URISaveEx : URISave;
+            byte[] bytes = string.getBytes("utf-8");
+            StringBuffer out = new StringBuffer(bytes.length);
+            for (byte aByte : bytes) {
+                int c = aByte & 0xff;
+                if (validChars.get(c) && c != escape) {
+                    out.append((char) c);
+                } else {
+                    out.append(escape);
+                    out.append(hexTable[(c >> 4) & 0x0f]);
+                    out.append(hexTable[(c) & 0x0f]);
+                }
+            }
+            return out.toString();
+        } catch (UnsupportedEncodingException e) {
+            throw new InternalError(e.toString());
+        }
+    }
+
+    /**
+     * Does a URL decoding of the <code>string</code>. Please note that in
+     * opposite to the {@link java.net.URLDecoder} it does not transform the +
+     * into spaces.
+     *
+     * @param string the string to decode
+     * @return the decoded string
+     * @throws NullPointerException           if <code>string</code> is <code>null</code>.
+     * @throws ArrayIndexOutOfBoundsException if not enough character follow an
+     *                                        escape character
+     * @throws IllegalArgumentException       if the 2 characters following the escape
+     *                                        character do not represent a hex-number.
+     */
+    public static String unescape(String string) {
+        return unescape(string, '%');
+    }
+    
+    /**
+     * Does a URL decoding of the <code>string</code> using the
+     * <code>escape</code> character. Please note that in opposite to the
+     * {@link java.net.URLDecoder} it does not transform the + into spaces.
+     *
+     * @param string the string to decode
+     * @param escape the escape character
+     * @return the decoded string
+     * @throws NullPointerException           if <code>string</code> is <code>null</code>.
+     * @throws IllegalArgumentException       if the 2 characters following the escape
+     *                                        character do not represent a hex-number
+     *                                        or if not enough characters follow an
+     *                                        escape character
+     */
+    private static String unescape(String string, char escape)  {
+        try {
+            byte[] utf8 = string.getBytes("utf-8");
+
+            // Check whether escape occurs at invalid position
+            if ((utf8.length >= 1 && utf8[utf8.length - 1] == escape) ||
+                (utf8.length >= 2 && utf8[utf8.length - 2] == escape)) {
+                throw new IllegalArgumentException("Premature end of escape sequence at end of input");
+            }
+
+            ByteArrayOutputStream out = new ByteArrayOutputStream(utf8.length);
+            for (int k = 0; k < utf8.length; k++) {
+                byte b = utf8[k];
+                if (b == escape) {
+                    out.write((decodeDigit(utf8[++k]) << 4) + decodeDigit(utf8[++k]));
+                }
+                else {
+                    out.write(b);
+                }
+            }
+
+            return new String(out.toByteArray(), "utf-8");
+        }
+        catch (UnsupportedEncodingException e) {
+            throw new InternalError(e.toString());
+        }
+    }
+    
+    private static byte decodeDigit(byte b) {
+        if (b >= 0x30 && b <= 0x39) {
+            return (byte) (b - 0x30);
+        }
+        else if (b >= 0x41 && b <= 0x46) {
+            return (byte) (b - 0x37);
+        }
+        else if (b >= 0x61 && b <= 0x66) {
+            return (byte) (b - 0x57);
+        }
+        else {
+            throw new IllegalArgumentException("Escape sequence is not hexadecimal: " + (char)b);
+        }
+    }
+
+    /**
+     * Private constructor: avoid instantiation.
+     */
+    private EncodeUtil() {
+    }
+}
\ No newline at end of file

Propchange: jackrabbit/trunk/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/util/EncodeUtil.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/util/EncodeUtil.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision Rev URL

Modified: jackrabbit/trunk/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/xml/DomUtil.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/xml/DomUtil.java?rev=1071660&r1=1071659&r2=1071660&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/xml/DomUtil.java (original)
+++ jackrabbit/trunk/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/xml/DomUtil.java Thu Feb 17 15:54:27 2011
@@ -16,7 +16,6 @@
  */
 package org.apache.jackrabbit.webdav.xml;
 
-import org.apache.jackrabbit.commons.xml.SerializingContentHandler;
 import org.apache.jackrabbit.webdav.DavConstants;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -41,7 +40,6 @@ import javax.xml.transform.Transformer;
 import javax.xml.transform.TransformerException;
 import javax.xml.transform.TransformerFactory;
 import javax.xml.transform.dom.DOMSource;
-import javax.xml.transform.sax.SAXResult;
 import javax.xml.transform.stream.StreamResult;
 
 import java.io.IOException;
@@ -733,7 +731,7 @@ public class DomUtil {
      */
     public static void transformDocument(Document xmlDoc, Writer writer) throws TransformerException, SAXException {
         Transformer transformer = TRANSFORMER_FACTORY.newTransformer();
-        transformer.transform(new DOMSource(xmlDoc), new SAXResult(SerializingContentHandler.getSerializer(new StreamResult(writer))));
+        transformer.transform(new DOMSource(xmlDoc), ResultHelper.getResult(new StreamResult(writer)));
     }
 
     /**
@@ -748,7 +746,7 @@ public class DomUtil {
      */
     public static void transformDocument(Document xmlDoc, OutputStream out) throws TransformerException, SAXException {
         Transformer transformer = TRANSFORMER_FACTORY.newTransformer();
-        transformer.transform(new DOMSource(xmlDoc), new SAXResult(SerializingContentHandler.getSerializer(new StreamResult(out))));
+        transformer.transform(new DOMSource(xmlDoc), ResultHelper.getResult(new StreamResult(out)));
     }
 
     /**

Added: jackrabbit/trunk/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/xml/ResultHelper.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/xml/ResultHelper.java?rev=1071660&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/xml/ResultHelper.java (added)
+++ jackrabbit/trunk/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/xml/ResultHelper.java Thu Feb 17 15:54:27 2011
@@ -0,0 +1,458 @@
+/*
+ * 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.jackrabbit.webdav.xml;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+import org.xml.sax.helpers.DefaultHandler;
+
+import javax.xml.transform.OutputKeys;
+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.SAXResult;
+import javax.xml.transform.sax.SAXTransformerFactory;
+import javax.xml.transform.sax.TransformerHandler;
+import javax.xml.transform.stream.StreamResult;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * <code>ResultHelper</code> is a utility to assert proper namespace handling
+ * due to misbehavior of certain implementations with respect to xmlns attributes.
+ * The code is copied and slightly modified from jcr-commons
+ * <code>SerializingContentHandler</code>
+ *
+ * @see <a href="https://issues.apache.org/jira/browse/JCR-2897">JCR-2897</a>.
+ */
+public final class ResultHelper {
+
+    /**
+     * logger instance
+     */
+    private static final Logger log = LoggerFactory.getLogger(ResultHelper.class);
+
+    /** The URI for xml namespaces */
+    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");
+        }
+    }
+
+    /**
+     * In case the underlying XML library doesn't properly handle xmlns attributes
+     * this method creates new content handler dealing with the misbehavior and
+     * returns an new instance of SAXResult. Otherwise the passed result
+     * is returned back.
+     *
+     * @param result
+     * @return A instance of <code>Result</code> that properly handles xmlns attributes.
+     * @throws SAXException
+     */
+    public static Result getResult(Result result) throws SAXException {
+        try {
+            TransformerHandler handler = FACTORY.newTransformerHandler();
+            handler.setResult(result);
+
+            // Specify the output properties to avoid surprises especially in
+            // character encoding or the output method (might be html for some
+            // documents!)
+            Transformer transformer = handler.getTransformer();
+            transformer.setOutputProperty(OutputKeys.METHOD, "xml");
+            transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
+            transformer.setOutputProperty(OutputKeys.INDENT, "no");
+
+            if (NEEDS_XMLNS_ATTRIBUTES) {
+                // The serializer does not output xmlns declarations,
+                // so we need to do it explicitly with this wrapper
+                return new SAXResult(new SerializingContentHandler(handler));
+            } else {
+                return result;
+            }
+        } catch (TransformerConfigurationException e) {
+            throw new SAXException("Failed to initialize XML serializer", e);
+        }
+    }
+
+    /**
+     * private constructor to avoid instantiation
+     */
+    private ResultHelper() {
+    }
+
+    /**
+     * Special content handler fixing issues with xmlns attraibutes handling.
+     */
+    private static final class SerializingContentHandler extends DefaultHandler {
+        /**
+         * The prefixes of startPrefixMapping() declarations for the coming element.
+         */
+        private List prefixList = new ArrayList();
+
+        /**
+         * The URIs of startPrefixMapping() declarations for the coming element.
+         */
+        private List uriList = new ArrayList();
+
+        /**
+         * Maps of URI<->prefix mappings. Used to work around a bug in the Xalan
+         * serializer.
+         */
+        private Map uriToPrefixMap = new HashMap();
+        private Map prefixToUriMap = new HashMap();
+
+        /**
+         * True if there has been some startPrefixMapping() for the coming element.
+         */
+        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, org.xml.sax.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();
+
+        /**
+         * The adapted content handler instance.
+         */
+        private final ContentHandler handler;
+
+        private SerializingContentHandler(ContentHandler handler) {
+            this.handler = handler;
+        }
+
+        @Override
+        public void characters(char[] ch, int start, int length) throws SAXException {
+            handler.characters(ch, start, length);
+        }
+
+        @Override        
+        public void startDocument() throws SAXException {
+            // Cleanup
+            this.uriToPrefixMap.clear();
+            this.prefixToUriMap.clear();
+            clearMappings();
+            
+            handler.startDocument();
+        }
+
+        /**
+         * Track mappings to be able to add <code>xmlns:</code> attributes
+         * in <code>startElement()</code>.
+         */
+        @Override
+        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")) {
+                this.hasMappings = true;
+                this.prefixList.add(prefix);
+                this.uriList.add(uri);
+
+                // append the prefix colon now, in order to save concatenations later, but
+                // only for non-empty prefixes.
+                if (prefix.length() > 0) {
+                    this.uriToPrefixMap.put(uri, prefix + ":");
+                } else {
+                    this.uriToPrefixMap.put(uri, prefix);
+                }
+
+                this.prefixToUriMap.put(prefix, uri);
+            }
+            
+            handler.startPrefixMapping(prefix, uri);
+        }
+
+        /**
+         * 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 delegating the startElement method on the
+         * specified <code>handler</code>. 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>.
+         */
+        @Override
+        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)) {
+                eltQName = this.uriToPrefixMap.get(eltUri) + eltLocalName;
+            }
+            if (this.hasMappings) {
+                // Add xmlns* attributes where needed
+
+                // New Attributes if we have to add some.
+                AttributesImpl newAttrs = null;
+
+                int mappingCount = this.prefixList.size();
+                int attrCount = attrs.getLength();
+
+                for (int mapping = 0; mapping < mappingCount; mapping++) {
+
+                    // Build infos for this namespace
+                    String uri = (String) this.uriList.get(mapping);
+                    String prefix = (String) this.prefixList.get(mapping);
+                    String qName = prefix.equals("") ? "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 this namespace
+                        if (newAttrs == null) {
+                            // Need to test if attrs is empty or we go into an infinite loop...
+                            // Well know SAX bug which I spent 3 hours to remind of :-(
+                            if (attrCount == 0) {
+                                newAttrs = new AttributesImpl();
+                            } else {
+                                newAttrs = new AttributesImpl(attrs);
+                            }
+                        }
+
+                        if (prefix.equals("")) {
+                            newAttrs.addAttribute(XML, qName, qName, "CDATA", uri);
+                        } else {
+                            newAttrs.addAttribute(XML, prefix, qName, "CDATA", uri);
+                        }
+                    }
+                } // end for mapping
+
+                // Cleanup for the next element
+                clearMappings();
+
+                // Start element with new attributes, if any
+                handler.startElement(eltUri, eltLocalName, eltQName, newAttrs == null ? attrs : newAttrs);                
+            } else {
+                // Normal job
+                handler.startElement(eltUri, eltLocalName, eltQName, attrs);
+            }
+        }
+
+
+        /**
+         * Receive notification of the end of an element.
+         * Try to restore the element qName.
+         */
+        @Override
+        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 && this.uriToPrefixMap.containsKey(eltUri)) {
+                eltQName = this.uriToPrefixMap.get(eltUri) + eltLocalName;
+            }
+
+            handler.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());
+                }
+            }
+        }
+
+        /**
+         * End the scope of a prefix-URI mapping:
+         * remove entry from mapping tables.
+         */
+        @Override
+        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 (this.prefixToUriMap.containsKey(prefix)) {
+                this.uriToPrefixMap.remove(this.prefixToUriMap.get(prefix));
+                this.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);
+                }
+            }
+
+            handler.endPrefixMapping(prefix);
+        }
+
+        @Override
+        public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
+            handler.ignorableWhitespace(ch, start, length);            
+        }
+
+        @Override
+        public void processingInstruction(String target, String data) throws SAXException {
+            handler.processingInstruction(target, data);
+        }
+
+        @Override
+        public void setDocumentLocator(Locator locator) {
+            handler.setDocumentLocator(locator);
+        }
+
+        @Override
+        public void skippedEntity(String name) throws SAXException {
+            handler.skippedEntity(name);            
+        }
+
+        @Override
+        public void endDocument() throws SAXException {
+            // Cleanup
+            this.uriToPrefixMap.clear();
+            this.prefixToUriMap.clear();
+            clearMappings();
+            
+            handler.endDocument();
+        }
+
+        private void clearMappings() {
+            this.hasMappings = false;
+            this.prefixList.clear();
+            this.uriList.clear();
+        }
+    }
+}
\ No newline at end of file

Propchange: jackrabbit/trunk/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/xml/ResultHelper.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/xml/ResultHelper.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision Rev URL