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