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 2009/01/28 14:35:48 UTC

svn commit: r738480 [2/3] - in /jackrabbit/sandbox/spi: ./ spi2davex/ spi2davex/src/ spi2davex/src/main/ spi2davex/src/main/java/ spi2davex/src/main/java/org/ spi2davex/src/main/java/org/apache/ spi2davex/src/main/java/org/apache/jackrabbit/ spi2davex/...

Added: jackrabbit/sandbox/spi/spi2davex/src/main/java/org/apache/jackrabbit/spi/spi2davex/QValueFactoryImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/spi/spi2davex/src/main/java/org/apache/jackrabbit/spi/spi2davex/QValueFactoryImpl.java?rev=738480&view=auto
==============================================================================
--- jackrabbit/sandbox/spi/spi2davex/src/main/java/org/apache/jackrabbit/spi/spi2davex/QValueFactoryImpl.java (added)
+++ jackrabbit/sandbox/spi/spi2davex/src/main/java/org/apache/jackrabbit/spi/spi2davex/QValueFactoryImpl.java Wed Jan 28 13:35:47 2009
@@ -0,0 +1,1088 @@
+/*
+ * 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.spi.spi2davex;
+
+import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.NameFactory;
+import org.apache.jackrabbit.spi.Path;
+import org.apache.jackrabbit.spi.PathFactory;
+import org.apache.jackrabbit.spi.QPropertyDefinition;
+import org.apache.jackrabbit.spi.QValue;
+import org.apache.jackrabbit.spi.QValueFactory;
+import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver;
+import org.apache.jackrabbit.spi.commons.name.NameConstants;
+import org.apache.jackrabbit.spi.commons.name.NameFactoryImpl;
+import org.apache.jackrabbit.spi.commons.name.PathFactoryImpl;
+import org.apache.jackrabbit.util.ISO8601;
+import org.apache.jackrabbit.util.TransientFileFactory;
+import org.apache.jackrabbit.uuid.UUID;
+import org.apache.jackrabbit.value.ValueFactoryImpl;
+import org.apache.jackrabbit.webdav.jcr.ItemResourceConstants;
+import org.apache.jackrabbit.webdav.jcr.property.ValuesProperty;
+import org.apache.jackrabbit.webdav.property.DavProperty;
+import org.apache.jackrabbit.webdav.property.DefaultDavProperty;
+import org.apache.jackrabbit.webdav.xml.DomUtil;
+import org.apache.jackrabbit.webdav.DavException;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.xml.sax.SAXException;
+
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+import javax.jcr.ValueFormatException;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.ParserConfigurationException;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+import java.io.Serializable;
+import java.io.UnsupportedEncodingException;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.TimeZone;
+
+/**
+ * <code>ValueFactoryImpl</code>...
+ */
+class QValueFactoryImpl implements QValueFactory {
+
+    private static final PathFactory PATH_FACTORY = PathFactoryImpl.getInstance();
+    private static final NameFactory NAME_FACTORY = NameFactoryImpl.getInstance();
+    private static final String DEFAULT_ENCODING = "UTF-8";
+    
+    private final NamePathResolver resolver;
+    private final ValueLoader loader;
+
+    public QValueFactoryImpl() {
+        this(null, null);
+    }
+
+    QValueFactoryImpl(NamePathResolver resolver, ValueLoader loader) {
+        this.resolver = resolver;
+        this.loader = loader;
+    }
+
+    /**
+     * Create a BINARY QValue with the given length and the given uri used
+     * to retrieve the value.
+     *
+     * @param length Length of the binary value.
+     * @param uri Uri from which the the binary value can be accessed.
+     * @param index The index of the value within the values array.
+     * @return a new BINARY QValue.
+     */
+    QValue create(long length, String uri, int index) {
+        if (loader == null) {
+            throw new IllegalStateException();
+        }
+        return new BinaryQValue(length, uri, index);
+    }
+
+    /**
+     *
+     * @param uri The Uri from which the type info can be retrieved.
+     * @return the type of the property with the given <code>uri</code>.
+     * @throws IOException If an error occurs.
+     * @throws RepositoryException If an error occurs.
+     */
+    int retrieveType(String uri) throws IOException, RepositoryException {
+        return loader.loadType(uri);
+    }
+
+    //------------------------------------------------------< QValueFactory >---
+    /**
+     * @see QValueFactory#create(String, int)
+     */
+    public QValue create(String value, int type) throws RepositoryException {
+        if (value == null) {
+            throw new IllegalArgumentException("Cannot create QValue from null value.");
+        }
+        try {
+            switch (type) {
+                case PropertyType.BOOLEAN:
+                    return new QValueImpl(Boolean.valueOf(value));
+                case PropertyType.DATE: {
+                        Calendar cal = ISO8601.parse(value);
+                        if (cal == null) {
+                            throw new ValueFormatException("not a valid date: " + value);
+                        }
+                        return new DateQValue(cal);
+                    }
+                case PropertyType.DOUBLE:
+                    return new QValueImpl(Double.valueOf(value));
+                case PropertyType.LONG:
+                    return new QValueImpl(Long.valueOf(value));
+                case PropertyType.PATH:
+                    return new QValueImpl(PATH_FACTORY.create(value));
+                case PropertyType.NAME:
+                    return new QValueImpl(NAME_FACTORY.create(value));
+                case PropertyType.STRING:
+                case PropertyType.REFERENCE:
+                    return new QValueImpl(value, type);
+                case PropertyType.BINARY:
+                    return new BinaryQValue(value.getBytes(DEFAULT_ENCODING));
+                default:
+                    throw new IllegalArgumentException("illegal type");
+            }
+        } catch (NumberFormatException ex) {
+            throw new ValueFormatException(ex);
+        } catch (UnsupportedEncodingException ex) {
+            throw new RepositoryException(ex);
+        }
+    }
+
+    /**
+     * @see QValueFactory#create(Calendar)
+     */
+    public QValue create(Calendar value) {
+        if (value == null) {
+            throw new IllegalArgumentException("Cannot create QValue from null value.");
+        }
+        // Calendar is not constant, must create a clone
+        return new DateQValue((Calendar) value.clone());
+    }
+
+    /**
+     * @see QValueFactory#create(double)
+     */
+    public QValue create(double value) {
+        return new QValueImpl(new Double(value));
+    }
+
+    /**
+     * @see QValueFactory#create(long)
+     */
+    public QValue create(long value) {
+        return new QValueImpl(new Long(value));
+    }
+
+    public QValue create(boolean value) throws RepositoryException {
+        return new QValueImpl(new Boolean(value));
+    }
+
+    /**
+     * @see QValueFactory#create(Name)
+     */
+    public QValue create(Name value) {
+        if (value == null) {
+            throw new IllegalArgumentException("Cannot create QValue from null value.");
+        }
+        return new QValueImpl(value);
+    }
+
+    /**
+     * @see QValueFactory#create(Path)
+     */
+    public QValue create(Path value) {
+        if (value == null) {
+            throw new IllegalArgumentException("Cannot create QValue from null value.");
+        }
+        return new QValueImpl(value);
+    }
+
+    /**
+     * @see QValueFactory#create(byte[])
+     */
+    public QValue create(byte[] value) {
+        if (value == null) {
+            throw new IllegalArgumentException("Cannot create QValue from null value.");
+        }
+        return new BinaryQValue(value);
+    }
+
+    /**
+     * @see QValueFactory#create(InputStream)
+     */
+    public QValue create(InputStream value) throws IOException {
+        if (value == null) {
+            throw new IllegalArgumentException("Cannot create QValue from null value.");
+        }
+        return new BinaryQValue(value);
+    }
+
+    /**
+     * @see QValueFactory#create(File)
+     */
+    public QValue create(File value) throws IOException {
+        if (value == null) {
+            throw new IllegalArgumentException("Cannot create QValue from null value.");
+        }
+        return new BinaryQValue(value);
+    }
+
+    /**
+     * @see QValueFactory#computeAutoValues(QPropertyDefinition)
+     */
+    public QValue[] computeAutoValues(QPropertyDefinition propertyDefinition) throws RepositoryException {
+        Name nodeType = propertyDefinition.getDeclaringNodeType();
+        Name name = propertyDefinition.getName();
+
+        if (NameConstants.NT_HIERARCHYNODE.equals(nodeType) && NameConstants.JCR_CREATED.equals(name)) {
+            return new QValue[] { create(Calendar.getInstance()) };
+        } else if (NameConstants.NT_RESOURCE.equals(nodeType) && NameConstants.JCR_LASTMODIFIED.equals(name)) {
+            return new QValue[] { create(Calendar.getInstance()) };
+        } else if (NameConstants.MIX_REFERENCEABLE.equals(nodeType) && NameConstants.JCR_UUID.equals(name)) {
+            return new QValue[] { create(UUID.randomUUID().toString(), PropertyType.STRING) };
+        } else {
+            throw new RepositoryException("createFromDefinition not implemented for: " + name);
+        }
+    }
+
+    //--------------------------------------------------------< Inner Class >---
+    /**
+     * <code>QValue</code> implementation for all valid <code>PropertyType</code>s
+     * except for BINARY.
+     * @see QValueFactoryImpl.BinaryQValue
+     */
+    private class QValueImpl implements QValue, Serializable {
+
+        private final Object val;
+        private final int type;
+
+        private QValueImpl(String value, int type) {
+            val = value;
+            this.type = type;
+        }
+
+        private QValueImpl(Long value) {
+            val = value;
+            type = PropertyType.LONG;
+        }
+
+        private QValueImpl(Double value) {
+            val = value;
+            type = PropertyType.DOUBLE;
+        }
+
+        private QValueImpl(Boolean value) {
+            val = value;
+            type = PropertyType.BOOLEAN;
+        }
+
+        private QValueImpl(Calendar value) {
+            val = value;
+            this.type = PropertyType.DATE;
+        }
+
+        private QValueImpl(Name value) {
+            val = value;
+            type = PropertyType.NAME;
+        }
+
+        private QValueImpl(Path value) {
+            val = value;
+            type = PropertyType.PATH;
+        }
+
+        protected String getQString(int type) throws RepositoryException {
+            return getString();
+        }
+        //---------------------------------------------------------< QValue >---
+        /**
+         * @see QValue#getType()
+         */
+        public int getType() {
+            return type;
+        }
+
+        /**
+         * @see QValue#getLength()
+         */
+        public long getLength() throws RepositoryException {
+            return getString().length();
+        }
+
+        /**
+         * @see QValue#getString()
+         */
+        public String getString() {
+            return val.toString();
+        }
+
+        /**
+         * @see QValue#getStream()
+         */
+        public InputStream getStream() throws RepositoryException {
+            try {
+                // convert via string
+                return new ByteArrayInputStream(getString().getBytes(QValueFactoryImpl.DEFAULT_ENCODING));
+            } catch (UnsupportedEncodingException e) {
+                throw new RepositoryException(QValueFactoryImpl.DEFAULT_ENCODING + " is not supported encoding on this platform", e);
+            }
+        }
+
+        /**
+         * @see QValue#getCalendar()
+         */
+        public Calendar getCalendar() throws RepositoryException {
+            if (val instanceof Calendar) {
+                return (Calendar) ((Calendar) val).clone();
+            } else if (val instanceof Double) {
+                Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT+00:00"));
+                cal.setTimeInMillis(((Double) val).longValue());
+                return cal;
+            } else if (val instanceof Long) {
+                Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT+00:00"));
+                cal.setTimeInMillis(((Long) val).longValue());
+                return cal;
+            } else {
+                String str = getString();
+                Calendar cal = ISO8601.parse(str);
+                if (cal == null) {
+                    int type = getType();
+                    if (type == PropertyType.LONG) {
+                        cal = Calendar.getInstance(TimeZone.getTimeZone("GMT+00:00"));
+                        cal.setTimeInMillis(new Long(str).longValue());
+                    } else if (type == PropertyType.DOUBLE) {
+                        cal = Calendar.getInstance(TimeZone.getTimeZone("GMT+00:00"));
+                        cal.setTimeInMillis(new Double(str).longValue());
+                    } else {
+                        throw new ValueFormatException("not a date string: " + getString());
+                    }
+                }
+                return cal;
+            }
+        }
+
+        /**
+         * @see QValue#getDouble()
+         */
+        public double getDouble() throws RepositoryException {
+            if (val instanceof Double) {
+                return ((Double) val).doubleValue();
+            } else if (val instanceof Calendar) {
+                return ((Calendar) val).getTimeInMillis();
+            } else {
+                try {
+                    return Double.parseDouble(getString());
+                } catch (NumberFormatException ex) {
+                    int type = getType();
+                    if (type == PropertyType.DATE) {
+                        Calendar cal = ISO8601.parse(getString());
+                        if (cal != null) {
+                            return cal.getTimeInMillis();
+                        }
+                    }
+                    throw new ValueFormatException("not a double: " + getString(), ex);
+                }
+            }
+        }
+
+        /**
+         * @see QValue#getLong()
+         */
+        public long getLong() throws RepositoryException {
+            if (val instanceof Long) {
+                return ((Long) val).longValue();
+            } else if (val instanceof Double) {
+                return ((Double) val).longValue();
+            } else if (val instanceof Calendar) {
+                return ((Calendar) val).getTimeInMillis();
+            } else {
+                String str = getString();
+                try {
+                    return Long.parseLong(str);
+                } catch (NumberFormatException ex) {
+                    int type = getType();
+                    if (type == PropertyType.DOUBLE) {
+                        return new Double(str).longValue();
+                    } else if (type == PropertyType.DATE) {
+                        Calendar cal = ISO8601.parse(getString());
+                        if (cal != null) {
+                            return cal.getTimeInMillis();
+                        }
+                    }
+                    throw new ValueFormatException("not a long: " + getString(), ex);
+                }
+            }
+        }
+
+        public boolean getBoolean() throws RepositoryException {
+            if (val instanceof Boolean) {
+                return ((Boolean) val).booleanValue();
+            } else {
+                try {
+                    return new Boolean(getString()).booleanValue();
+                } catch (NumberFormatException ex) {
+                    throw new ValueFormatException("not a long: " + getString(), ex);
+                }
+            }
+        }
+
+        /**
+         * @see QValue#getName()
+         */
+        public Name getName() throws RepositoryException {
+            if (val instanceof Name) {
+                return (Name) val;
+            } else {
+                return NAME_FACTORY.create(getString());
+            }
+        }
+
+        /**
+         * @see QValue#getPath()
+         */
+        public Path getPath() throws RepositoryException {
+            if (val instanceof Path) {
+                return (Path) val;
+            } else {
+                return PATH_FACTORY.create(getString());
+            }
+        }
+
+        /**
+         * @see QValue#discard()
+         */
+        public void discard() {
+            // nothing to do
+        }
+
+        //---------------------------------------------------------< Object >---
+        /**
+         * Returns the string representation of this internal value.
+         *
+         * @return string representation of this internal value.
+         * @see Object#toString() 
+         */
+        public String toString() {
+            return val.toString();
+        }
+
+        /**
+         * @see Object#equals(Object)
+         */
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (obj instanceof QValueImpl) {
+                QValueImpl other = (QValueImpl) obj;
+                if (type == other.type && type != PropertyType.UNDEFINED) {
+                    return getString().equals(other.getString());
+                }
+                try {
+                    int type = getType();
+                    return type == other.getType() && getQString(type).equals(other.getQString(type));
+                } catch (RepositoryException e) {
+                    // should never get here. return false.
+                }
+            }
+            return false;
+        }
+
+        /**
+         * @return the hashCode of the internal value object.
+         * @see Object#hashCode()
+         */
+        public int hashCode() {
+            return val.hashCode();
+        }
+    }
+
+    //--------------------------------------------------------< Inner Class >---
+    /**
+     * Extension for values of type {@link PropertyType#DATE}.
+     */
+    private class DateQValue extends QValueImpl {
+
+        private final String formattedStr;
+
+        private DateQValue(Calendar value) {
+            super(value);
+            formattedStr = ISO8601.format(value);
+        }
+
+        /**
+         * @return The formatted String of the internal Calendar value.
+         * @see QValue#getString()
+         * @see ISO8601#format(Calendar)
+         */
+        public String getString() {
+            return formattedStr;
+        }
+
+        //---------------------------------------------------------< Object >---
+        /**
+         * @param obj The object to be checked for equality.
+         * @return true if the given Object is a <code>DateQValue</code> with an
+         * equal String representation.
+         * @see Object#equals(Object)
+         */
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (obj instanceof DateQValue) {
+                DateQValue other = (DateQValue) obj;
+                return formattedStr.equals(other.formattedStr);
+            } else if (obj instanceof QValueImpl) {
+                QValueImpl other = (QValueImpl) obj;
+                return formattedStr.equals(other.getString()) &&
+                       other.getType() == PropertyType.DATE;
+            }
+            return false;
+        }
+
+        /**
+         * @return the hashCode of the formatted String of the Calender value.
+         * @see Object#hashCode()
+         */
+        public int hashCode() {
+            return formattedStr.hashCode();
+        }
+    }
+
+    //--------------------------------------------------------< Inner Class >---
+    /**
+     * <code>BinaryQValue</code> represents a binary <code>Value</code> which is
+     * backed by a resource or byte[]. Unlike <code>BinaryValue</code> it has no
+     * state, i.e. the <code>getStream()</code> method always returns a fresh
+     * <code>InputStream</code> instance.
+     */
+    private class BinaryQValue implements QValue, Serializable, ValueLoader.Target {
+        /**
+         * empty array
+         */
+        private final byte[] EMPTY_BYTE_ARRAY = new byte[0];
+
+        /**
+         * max size for keeping tmp data in memory
+         */
+        private static final int MAX_BUFFER_SIZE = 0x10000;
+
+        /**
+         * underlying file
+         */
+        private transient File file;
+
+        /**
+         * flag indicating if this instance represents a <i>temporary</i> value
+         * whose dynamically allocated resources can be explicitly freed on
+         * {@link #discard()}.
+         */
+        private transient boolean temp;
+
+        /**
+         * Buffer for small-sized data
+         */
+        private byte[] buffer;
+
+        /**
+         * Converted text
+         */
+        private transient String text = null;
+
+        /**
+         * URI to retrieve the value from
+         */
+        private String uri;
+        private long length;
+        private int index = -1;
+        private boolean initialized = true;
+
+        private BinaryQValue(long length, String uri, int index) {
+            this.length = length;
+            this.uri = uri;
+            this.index = index;
+            initialized = false;
+        }
+
+        /**
+         * Creates a new <code>BinaryQValue</code> instance from an
+         * <code>InputStream</code>. The contents of the stream is spooled
+         * to a temporary file or to a byte buffer if its size is smaller than
+         * {@link #MAX_BUFFER_SIZE}.
+         * <p/>
+         * The new instance represents a <i>temporary</i> value whose dynamically
+         * allocated resources will be freed explicitly on {@link #discard()}.
+         *
+         * @param in stream to be represented as a <code>BinaryQValue</code> instance
+         * @throws IOException if an error occurs while reading from the stream or
+         *                     writing to the temporary file
+         */
+        private BinaryQValue(InputStream in) throws IOException {
+            init(in, true);
+        }
+
+
+        /**
+         * Creates a new <code>BinaryQValue</code> instance from a
+         * <code>byte[]</code> array.
+         *
+         * @param bytes byte array to be represented as a <code>BinaryQValue</code>
+         *              instance
+         */
+        private BinaryQValue(byte[] bytes) {
+            buffer = bytes;
+            file = null;
+            // this instance is not backed by a temporarily allocated buffer
+            temp = false;
+        }
+
+        /**
+         * Creates a new <code>BinaryQValue</code> instance from a <code>File</code>.
+         *
+         * @param file file to be represented as a <code>BinaryQValue</code> instance
+         * @throws IOException if the file can not be read
+         */
+        private BinaryQValue(File file) throws IOException {
+            String path = file.getCanonicalPath();
+            if (!file.isFile()) {
+                throw new IOException(path + ": the specified file does not exist");
+            }
+            if (!file.canRead()) {
+                throw new IOException(path + ": the specified file can not be read");
+            }
+            // this instance is backed by a 'real' file
+            this.file = file;
+            // this instance is not backed by temporarily allocated resource/buffer
+            temp = false;
+        }
+
+        /**
+         * Creates a new <code>BinaryQValue</code> instance from an
+         * <code>InputStream</code>. The contents of the stream is spooled
+         * to a temporary file or to a byte buffer if its size is smaller than
+         * {@link #MAX_BUFFER_SIZE}.
+         * <p/>
+         * The <code>temp</code> parameter governs whether dynamically allocated
+         * resources will be freed explicitly on {@link #discard()}. Note that any
+         * dynamically allocated resources (temp file/buffer) will be freed
+         * implicitly once this instance has been gc'ed.
+         *
+         * @param in stream to be represented as a <code>BinaryQValue</code> instance
+         * @param temp flag indicating whether this instance represents a
+         *             <i>temporary</i> value whose resources can be explicitly freed
+         *             on {@link #discard()}.
+         * @throws IOException if an error occurs while reading from the stream or
+         *                     writing to the temporary file
+         */
+        private void init(InputStream in, boolean temp) throws IOException {
+            byte[] spoolBuffer = new byte[0x2000];
+            int read;
+            int len = 0;
+            OutputStream out = null;
+            File spoolFile = null;
+            try {
+                while ((read = in.read(spoolBuffer)) > 0) {
+                    if (out != null) {
+                        // spool to temp file
+                        out.write(spoolBuffer, 0, read);
+                        len += read;
+                    } else if (len + read > BinaryQValue.MAX_BUFFER_SIZE) {
+                        // threshold for keeping data in memory exceeded;
+                        // create temp file and spool buffer contents
+                        TransientFileFactory fileFactory = TransientFileFactory.getInstance();
+                        spoolFile = fileFactory.createTransientFile("bin", null, null);
+                        out = new FileOutputStream(spoolFile);
+                        out.write(buffer, 0, len);
+                        out.write(spoolBuffer, 0, read);
+                        buffer = null;
+                        len += read;
+                    } else {
+                        // reallocate new buffer and spool old buffer contents
+                        if (buffer == null) {
+                            buffer = EMPTY_BYTE_ARRAY;
+                        }
+                        byte[] newBuffer = new byte[len + read];
+                        System.arraycopy(buffer, 0, newBuffer, 0, len);
+                        System.arraycopy(spoolBuffer, 0, newBuffer, len, read);
+                        buffer = newBuffer;
+                        len += read;
+                    }
+                }
+            } finally {
+                if (out != null) {
+                    out.close();
+                }
+            }
+
+            if (spoolFile == null && buffer == null) {
+                // input stream was empty -> initialize an empty binary value
+                this.temp = false;
+                buffer = EMPTY_BYTE_ARRAY;
+            } else {
+                // init vars
+                file = spoolFile;
+                this.temp = temp;
+            }
+            initialized = true;
+        }
+
+        //---------------------------------------------------------< QValue >---
+        /**
+         * @see QValue#getType()
+         */
+        public int getType() {
+            return PropertyType.BINARY;
+        }
+
+        /**
+         * Returns the length of this <code>BinaryQValue</code>.
+         *
+         * @return The length, in bytes, of this <code>BinaryQValue</code>,
+         *         or -1L if the length can't be determined.
+         * @see QValue#getLength()
+         */
+        public long getLength() {
+            if (file != null) {
+                // this instance is backed by a 'real' file
+                if (file.exists()) {
+                    return file.length();
+                } else {
+                    return -1;
+                }
+            } else if (buffer != null) {
+                // this instance is backed by an in-memory buffer
+                return buffer.length;
+            } else {
+                // value has not yet been read from the server.
+                return length;
+            }
+        }
+
+        /**
+         * @see QValue#getString()
+         */
+        public String getString() throws RepositoryException {
+            if (text == null) {
+                ByteArrayOutputStream out = new ByteArrayOutputStream();
+                try {
+                    spool(out);
+                    byte[] data = out.toByteArray();
+                    text = new String(data, QValueFactoryImpl.DEFAULT_ENCODING);
+                } catch (UnsupportedEncodingException e) {
+                    throw new RepositoryException(QValueFactoryImpl.DEFAULT_ENCODING
+                        + " not supported on this platform", e);
+                } catch (IOException e) {
+                    throw new ValueFormatException("conversion from stream to string failed", e);
+                } finally {
+                    try {
+                        out.close();
+                    } catch (IOException e) {
+                        // ignore
+                    }
+                }
+            }
+            return text;
+        }
+
+        /**
+         * @see QValue#getStream()
+         */
+        public InputStream getStream() throws RepositoryException {
+            // if the value has not yet been loaded -> retrieve it first in
+            // order to make sure that either 'file' or 'buffer' is set.
+            if (file == null && buffer == null) {
+                try {
+                    loadBinary();
+                } catch (IOException e) {
+                    throw new RepositoryException(e);
+                }
+            }
+
+            // always return a 'fresh' stream
+            if (file != null) {
+                // this instance is backed by a 'real' file
+                try {
+                    return new FileInputStream(file);
+                } catch (FileNotFoundException fnfe) {
+                    throw new RepositoryException("file backing binary value not found",
+                        fnfe);
+                }
+            } else {
+                return new ByteArrayInputStream(buffer);
+            }
+        }
+
+        /**
+         * @see QValue#getName()
+         */
+        public Name getName() throws RepositoryException {
+            throw new UnsupportedOperationException();
+        }
+
+        /**
+         * @see QValue#getCalendar()
+         */
+        public Calendar getCalendar() throws RepositoryException {
+             Calendar cal = ISO8601.parse(getString());
+             if (cal == null) {
+                 throw new ValueFormatException("not a date string: " + getString());
+             } else {
+                 return cal;
+             }
+        }
+
+        /**
+         * @see QValue#getDouble()
+         */
+        public double getDouble() throws RepositoryException {
+            try {
+                return Double.parseDouble(getString());
+            } catch (NumberFormatException ex) {
+                throw new ValueFormatException(ex);
+            }
+        }
+
+        /**
+         * @see QValue#getLong()
+         */
+        public long getLong() throws RepositoryException {
+            try {
+                return Long.parseLong(getString());
+            } catch (NumberFormatException ex) {
+                throw new ValueFormatException(ex);
+            }
+        }
+
+        public boolean getBoolean() throws RepositoryException {
+            return new Boolean(getString()).booleanValue();
+        }
+
+        /**
+         * @see QValue#getPath()
+         */
+        public Path getPath() throws RepositoryException {
+            throw new UnsupportedOperationException();
+        }
+
+        /**
+         * Frees temporarily allocated resources such as temporary file, buffer, etc.
+         * If this <code>BinaryQValue</code> is backed by a persistent resource
+         * calling this method will have no effect.
+         * @see QValue#discard()
+         */
+        public void discard() {
+            if (!temp) {
+                // do nothing if this instance is not backed by temporarily
+                // allocated resource/buffer
+                return;
+            }
+            if (file != null) {
+                // this instance is backed by a temp file
+                file.delete();
+            } else if (buffer != null) {
+                // this instance is backed by an in-memory buffer
+                buffer = EMPTY_BYTE_ARRAY;
+            }
+        }
+
+        //-----------------------------------------------< java.lang.Object >---
+        /**
+         * Returns a string representation of this <code>BinaryQValue</code>
+         * instance. The string representation of a resource backed value is
+         * the path of the underlying resource. If this instance is backed by an
+         * in-memory buffer the generic object string representation of the byte
+         * array will be used instead.
+         *
+         * @return A string representation of this <code>BinaryQValue</code> instance.
+         */
+        public String toString() {
+            if (file != null) {
+                // this instance is backed by a 'real' file
+                return file.toString();
+            } else if (buffer != null) {
+                // this instance is backed by an in-memory buffer
+                return buffer.toString();
+            } else {
+                return super.toString();
+            }
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (obj instanceof BinaryQValue) {
+                BinaryQValue other = (BinaryQValue) obj;
+                // for both the value has not been loaded yet
+                if (!initialized) {
+                    if (other.uri != null) {
+                        return uri.equals(uri);
+                    } else {
+                        // need to load the binary value in order to be able
+                        // to compare the 2 values.
+                        try {
+                            loadBinary();
+                        } catch (RepositoryException e) {
+                            return false;
+                        } catch (IOException e) {
+                            return false;
+                        }
+                    }
+                }
+                // both have been loaded
+                return ((file == null ? other.file == null : file.equals(other.file))
+                    && Arrays.equals(buffer, other.buffer));
+            }
+            return false;
+        }
+
+        /**
+         * Returns zero to satisfy the Object equals/hashCode contract.
+         * This class is mutable and not meant to be used as a hash key.
+         *
+         * @return always zero
+         * @see Object#hashCode()
+         */
+        public int hashCode() {
+            return 0;
+        }
+
+        //----------------------------------------------------------------------
+        /**
+         * Spools the contents of this <code>BinaryQValue</code> to the given
+         * output stream.
+         *
+         * @param out output stream
+         * @throws RepositoryException if the input stream for this
+         *                             <code>BinaryQValue</code> could not be obtained
+         * @throws IOException         if an error occurs while while spooling
+         */
+        private void spool(OutputStream out) throws RepositoryException, IOException {
+            InputStream in;
+            if (file != null) {
+                // this instance is backed by a 'real' file
+                try {
+                    in = new FileInputStream(file);
+                } catch (FileNotFoundException fnfe) {
+                    throw new RepositoryException("file backing binary value not found",
+                        fnfe);
+                }
+            } else if (buffer != null) {
+                // this instance is backed by an in-memory buffer
+                in = new ByteArrayInputStream(buffer);
+            } else {
+                // only uri present:
+                loadBinary();
+                if (buffer == null) {
+                    in = new FileInputStream(file);
+                } else {
+                    in = new ByteArrayInputStream(buffer);
+                }
+            }
+            try {
+                byte[] buffer = new byte[0x2000];
+                int read;
+                while ((read = in.read(buffer)) > 0) {
+                    out.write(buffer, 0, read);
+                }
+            } finally {
+                try {
+                    in.close();
+                } catch (IOException ignore) {
+                }
+            }
+        }
+
+        private synchronized void loadBinary() throws RepositoryException, IOException {
+            if (uri == null) {
+                throw new IllegalStateException();
+            }
+            loader.loadBinary(uri, index, this);
+        }
+
+        //-----------------------------< Serializable >-------------------------
+        private void writeObject(ObjectOutputStream out)
+                throws IOException {
+            out.defaultWriteObject();
+            // write hasFile marker
+            out.writeBoolean(file != null);
+            // then write file if necessary
+            if (file != null) {
+                byte[] buffer = new byte[4096];
+                int bytes;
+                InputStream stream = new FileInputStream(file);
+                while ((bytes = stream.read(buffer)) >= 0) {
+                    // Write a segment of the input stream
+                    if (bytes > 0) {
+                        // just to ensure that no 0 is written
+                        out.writeInt(bytes);
+                        out.write(buffer, 0, bytes);
+                    }
+                }
+                // Write the end of stream marker
+                out.writeInt(0);
+                // close stream
+                stream.close();
+            }
+        }
+
+        private void readObject(ObjectInputStream in)
+                throws IOException, ClassNotFoundException {
+            in.defaultReadObject();
+            boolean hasFile = in.readBoolean();
+            if (hasFile) {
+                file = File.createTempFile("binary-qvalue", "bin");
+
+                OutputStream out = new FileOutputStream(file);
+                byte[] buffer = new byte[4096];
+                for (int bytes = in.readInt(); bytes > 0; bytes = in.readInt()) {
+                    if (buffer.length < bytes) {
+                        buffer = new byte[bytes];
+                    }
+                    in.readFully(buffer, 0, bytes);
+                    out.write(buffer, 0, bytes);
+                }
+                out.close();
+            }
+            // deserialized value is always temp
+            temp = true;
+        }
+
+        //---------------------------------------------------------< Target >---
+        public void setStream(InputStream in) throws IOException {
+            if (index == -1) {
+                init(in, true);
+            } else {
+                // TODO: improve. jcr-server sends XML for multivalued properties
+                try {
+                    DocumentBuilder db = DomUtil.BUILDER_FACTORY.newDocumentBuilder();
+                    Document doc = db.parse(in);
+                    Element prop = DomUtil.getChildElement(doc, ItemResourceConstants.JCR_VALUES.getName(), ItemResourceConstants.JCR_VALUES.getNamespace());
+                    DavProperty p = DefaultDavProperty.createFromXml(prop);
+                    ValuesProperty vp = new ValuesProperty(p, PropertyType.BINARY, ValueFactoryImpl.getInstance());
+
+                    Value[] jcrVs = vp.getJcrValues();
+                    init(jcrVs[index].getStream(), true);
+                } catch (RepositoryException e) {
+                    throw new IOException(e.getMessage());
+                }catch (DavException e) {
+                    throw new IOException(e.getMessage());
+                } catch (SAXException e) {
+                    throw new IOException(e.getMessage());
+                } catch (ParserConfigurationException e) {
+                    throw new IOException(e.getMessage());
+                }
+            }
+        }
+    }
+}
\ No newline at end of file

Propchange: jackrabbit/sandbox/spi/spi2davex/src/main/java/org/apache/jackrabbit/spi/spi2davex/QValueFactoryImpl.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/sandbox/spi/spi2davex/src/main/java/org/apache/jackrabbit/spi/spi2davex/QValueFactoryImpl.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url

Added: jackrabbit/sandbox/spi/spi2davex/src/main/java/org/apache/jackrabbit/spi/spi2davex/RepositoryServiceImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/spi/spi2davex/src/main/java/org/apache/jackrabbit/spi/spi2davex/RepositoryServiceImpl.java?rev=738480&view=auto
==============================================================================
--- jackrabbit/sandbox/spi/spi2davex/src/main/java/org/apache/jackrabbit/spi/spi2davex/RepositoryServiceImpl.java (added)
+++ jackrabbit/sandbox/spi/spi2davex/src/main/java/org/apache/jackrabbit/spi/spi2davex/RepositoryServiceImpl.java Wed Jan 28 13:35:47 2009
@@ -0,0 +1,770 @@
+/*
+ * 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.spi.spi2davex;
+
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.commons.httpclient.HttpException;
+import org.apache.commons.httpclient.HttpMethod;
+import org.apache.commons.httpclient.methods.RequestEntity;
+import org.apache.commons.httpclient.methods.multipart.FilePart;
+import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity;
+import org.apache.commons.httpclient.methods.multipart.Part;
+import org.apache.commons.httpclient.methods.multipart.PartBase;
+import org.apache.commons.httpclient.methods.multipart.StringPart;
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.jackrabbit.spi.Batch;
+import org.apache.jackrabbit.spi.ItemId;
+import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.NodeId;
+import org.apache.jackrabbit.spi.Path;
+import org.apache.jackrabbit.spi.PropertyId;
+import org.apache.jackrabbit.spi.QValue;
+import org.apache.jackrabbit.spi.RepositoryService;
+import org.apache.jackrabbit.spi.SessionInfo;
+import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver;
+import org.apache.jackrabbit.spi.commons.conversion.PathResolver;
+import org.apache.jackrabbit.spi.commons.identifier.IdFactoryImpl;
+import org.apache.jackrabbit.spi.commons.name.NameFactoryImpl;
+import org.apache.jackrabbit.spi.commons.name.PathBuilder;
+import org.apache.jackrabbit.spi.commons.name.PathFactoryImpl;
+import org.apache.jackrabbit.spi2dav.ExceptionConverter;
+import org.apache.jackrabbit.util.Text;
+import org.apache.jackrabbit.webdav.DavException;
+import org.apache.jackrabbit.webdav.DavServletResponse;
+import org.apache.jackrabbit.webdav.header.IfHeader;
+import org.apache.jackrabbit.webdav.jcr.ItemResourceConstants;
+import org.apache.jackrabbit.webdav.jcr.JcrValueType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jcr.Credentials;
+import javax.jcr.ItemNotFoundException;
+import javax.jcr.NamespaceException;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import java.io.IOException;
+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>RepositoryServiceImpl</code>...
+ */
+public class RepositoryServiceImpl extends org.apache.jackrabbit.spi2dav.RepositoryServiceImpl {
+
+    private static Logger log = LoggerFactory.getLogger(RepositoryServiceImpl.class);
+
+    private static final String PARAM_DIFF = ":diff";
+    private static final String PARAM_COPY = ":copy";
+    private static final String PARAM_CLONE = ":clone";
+
+    private static final char SYMBOL_ADD_NODE = '+';
+    private static final char SYMBOL_MOVE = '>';
+    private static final char SYMBOL_REMOVE = '-';
+    private static final char SYMBOL_SET_PROPERTY = '^';
+
+    private static final String ORDER_POSITION_LAST = "#last";
+    private static final String ORDER_POSITION_BEFORE = "#before";
+
+    /**
+     * base uri to the extended jcr-server that can handle the GET and POST
+     * (or PATCH) requests sent by this service implementation.
+     */
+    private final String jcrServerURI;
+
+    /**
+     * the name of the default workspace or <code>null</code>.
+     * NOTE: with JCR-1842 the RepositoryConfiguration doesn't provide the
+     * default workspace name any more. In order to provide backwards
+     * compatibility with jcr-server &lt; 1.5.0 the workspace name can be
+     * passed to the RepositoryService implementation.
+     */
+    private final String defaultWorkspaceName;
+
+    /**
+     * The configuration map used to determine the maximal depth of child
+     * items to be accessed upon a call to {@link #getNodeInfo(SessionInfo, NodeId)}.
+     */
+    private final BatchReadConfig batchReadConfig;
+
+    private final Map qvFactories = new HashMap();
+
+    public RepositoryServiceImpl(String jcrServerURI, BatchReadConfig batchReadConfig) throws RepositoryException {
+        this(jcrServerURI, null, batchReadConfig);
+    }
+
+    public RepositoryServiceImpl(String jcrServerURI, String defaultWorkspaceName, BatchReadConfig batchReadConfig) throws RepositoryException {
+        super(jcrServerURI, IdFactoryImpl.getInstance(), NameFactoryImpl.getInstance(), PathFactoryImpl.getInstance(), new QValueFactoryImpl());
+
+        this.jcrServerURI = jcrServerURI.endsWith("/") ? jcrServerURI : jcrServerURI + "/";
+        this.defaultWorkspaceName = defaultWorkspaceName;
+        if (batchReadConfig == null) {
+            this.batchReadConfig = new BatchReadConfig() {
+                public int getDepth(Path path, PathResolver resolver)
+                        throws NamespaceException {
+                    return 0;
+                }
+            };
+        } else {
+            this.batchReadConfig = batchReadConfig;
+        }
+    }
+
+    private Path getPath(ItemId itemId, SessionInfo sessionInfo) throws RepositoryException {
+        if (itemId.denotesNode()) {
+            Path p = itemId.getPath();
+            String uid = itemId.getUniqueID();
+            if (uid == null) {
+                return p;
+            } else {
+                NamePathResolver resolver = getNamePathResolver(sessionInfo);
+                String uri = super.getItemUri(itemId, sessionInfo);
+                String rootUri = getRootURI(sessionInfo);
+                String jcrPath;
+                if (uri.startsWith(rootUri)) {
+                    jcrPath = uri.substring(rootUri.length());
+                } else {
+                    log.warn("ItemURI " + uri + " doesn't start with rootURI (" + rootUri + ").");
+                    // fallback:
+                    // calculated uri does not start with the rootURI
+                    // -> search /jcr:root and start substring behind.
+                    String rootSegment = Text.escapePath(ItemResourceConstants.ROOT_ITEM_RESOURCEPATH);
+                    jcrPath = uri.substring(uri.indexOf(rootSegment) + rootSegment.length());
+                }
+                jcrPath = Text.unescape(jcrPath);
+                return resolver.getQPath(jcrPath);
+            }
+        } else {
+            PropertyId pId = (PropertyId) itemId;
+            Path parentPath = getPath(pId.getParentId(), sessionInfo);
+            return getPathFactory().create(parentPath, pId.getName(), true);
+        }
+    }
+
+    private String getURI(Path path, SessionInfo sessionInfo) throws RepositoryException {
+        StringBuffer sb = new StringBuffer(getRootURI(sessionInfo));
+        String jcrPath = getNamePathResolver(sessionInfo).getJCRPath(path);
+        sb.append(Text.escapePath(jcrPath));
+        return sb.toString();
+    }
+
+    private String getURI(ItemId itemId, SessionInfo sessionInfo) throws RepositoryException {
+        Path p = getPath(itemId, sessionInfo);
+        if (p == null) {
+            return super.getItemUri(itemId, sessionInfo);
+        } else {
+            return getURI(p, sessionInfo);
+        }
+    }
+
+    private String getRootURI(SessionInfo sessionInfo) {
+        StringBuffer sb = new StringBuffer(getWorkspaceURI(sessionInfo));
+        sb.append(Text.escapePath(ItemResourceConstants.ROOT_ITEM_RESOURCEPATH));
+        return sb.toString();
+    }
+
+    private String getWorkspaceURI(SessionInfo sessionInfo) {
+        StringBuffer sb = new StringBuffer();
+        sb.append(jcrServerURI);
+        sb.append(Text.escape(sessionInfo.getWorkspaceName()));
+        return sb.toString();
+    }
+
+     /**
+     * @see RepositoryService#getQValueFactory()
+     */
+    public QValueFactoryImpl getQValueFactory(SessionInfo sessionInfo) throws RepositoryException {
+         QValueFactoryImpl qv;
+         if (qvFactories.containsKey(sessionInfo)) {
+             qv = (QValueFactoryImpl) qvFactories.get(sessionInfo);
+         } else {
+             ValueLoader loader = new ValueLoader(getClient(sessionInfo));
+             qv = new QValueFactoryImpl(getNamePathResolver(sessionInfo), loader);
+             qvFactories.put(sessionInfo, qv);
+         }
+         return qv;
+     }
+
+    //--------------------------------------------------< RepositoryService >---
+
+    // exists && getPropertyInfo -> to be done
+    // getNodeInfo: omitted for requires list of 'references'
+
+    /**
+     * If the specified <code>workspaceName</code> the default workspace name
+     * is used for backwards compatibility with jackrabbit-jcr-server &lt; 1.5.0
+     *
+     * @see RepositoryService#obtain(Credentials, String)
+     */
+    public SessionInfo obtain(Credentials credentials, String workspaceName)
+            throws  RepositoryException {
+        // for backwards compatibility with jcr-server < 1.5.0
+        String wspName = (workspaceName == null) ? defaultWorkspaceName : workspaceName;
+        return super.obtain(credentials, wspName);
+    }
+
+    /**
+     * If the specified <code>workspaceName</code> the default workspace name
+     * is used for backwards compatibility with jackrabbit-jcr-server &lt; 1.5.0
+     *
+     * @see RepositoryService#obtain(SessionInfo, String)
+     */
+    public SessionInfo obtain(SessionInfo sessionInfo, String workspaceName)
+            throws RepositoryException {
+        // for backwards compatibility with jcr-server < 1.5.0
+        String wspName = (workspaceName == null) ? defaultWorkspaceName : workspaceName;
+        return super.obtain(sessionInfo, wspName);
+    }
+
+    /**
+     * @see RepositoryService#dispose(SessionInfo)
+     */
+    public void dispose(SessionInfo sessionInfo) throws RepositoryException {
+        super.dispose(sessionInfo);
+        // remove the qvalue factory created for the given SessionInfo from the
+        // map of valuefactories.
+        qvFactories.remove(sessionInfo);
+    }
+
+    /**
+     * @see RepositoryService#getItemInfos(SessionInfo, NodeId)
+     */
+    public Iterator getItemInfos(SessionInfo sessionInfo, NodeId nodeId) throws ItemNotFoundException, RepositoryException {
+        Path path = getPath(nodeId, sessionInfo);
+        String uri = getURI(path, sessionInfo);
+        int depth = batchReadConfig.getDepth(path, this.getNamePathResolver(sessionInfo));
+
+        GetMethod method = new GetMethod(uri + "." + depth + ".json");
+        try {
+            int statusCode = getClient(sessionInfo).executeMethod(method);
+            if (statusCode == DavServletResponse.SC_OK) {
+                if (method.getResponseContentLength() == 0) {
+                    // no json response -> no such node on the server
+                    throw new ItemNotFoundException("No such node " + nodeId);
+                }
+
+                NamePathResolver resolver = getNamePathResolver(sessionInfo);
+                NodeInfoImpl nInfo = new NodeInfoImpl(nodeId, path);
+
+                ItemInfoJSONHandler handler = new ItemInfoJSONHandler(resolver, nInfo, getRootURI(sessionInfo), getQValueFactory(sessionInfo), getPathFactory(), getIdFactory());
+                JSONParser ps = new JSONParser(handler);
+                ps.parse(method.getResponseBodyAsStream(), method.getResponseCharSet());
+
+                Iterator it = handler.getItemInfos();
+                if (!it.hasNext()) {
+                    throw new ItemNotFoundException("No such node " + uri);
+                }
+                return handler.getItemInfos();
+            } else {
+                throw ExceptionConverter.generate(new DavException(statusCode, "Unable to retrieve NodeInfo for " + uri), method);
+            }
+        } catch (HttpException e) {
+            throw ExceptionConverter.generate(new DavException(method.getStatusCode(), "Unable to retrieve NodeInfo for " + uri));
+        } catch (IOException e) {
+            log.error("Internal error while retrieving NodeInfo.",e);
+            throw new RepositoryException(e);
+        } catch (Exception e) {
+            log.error("Internal error while retrieving NodeInfo.",e);
+            throw new RepositoryException(e);
+        } finally {
+            method.releaseConnection();
+        }
+    }
+
+    public Batch createBatch(SessionInfo sessionInfo, ItemId itemId) throws RepositoryException {
+        return new BatchImpl(itemId, sessionInfo);
+    }
+
+    public void submit(Batch batch) throws RepositoryException {
+        if (!(batch instanceof BatchImpl)) {
+            throw new RepositoryException("Unknown Batch implementation.");
+        }
+        BatchImpl batchImpl = (BatchImpl) batch;
+        try {
+            if (!batchImpl.isEmpty()) {
+                batchImpl.start();
+            }
+        } finally {
+            batchImpl.dispose();
+        }
+    }
+
+    public void copy(SessionInfo sessionInfo, String srcWorkspaceName, NodeId srcNodeId, NodeId destParentNodeId, Name destName) throws RepositoryException {
+        if (srcWorkspaceName.equals(sessionInfo.getWorkspaceName())) {
+            super.copy(sessionInfo, srcWorkspaceName, srcNodeId, destParentNodeId, destName);
+            return;
+        }
+        PostMethod method = null;
+        try {
+            method = new PostMethod(getWorkspaceURI(sessionInfo));
+            NamePathResolver resolver = getNamePathResolver(sessionInfo);
+
+            StringBuffer args = new StringBuffer();
+            args.append(srcWorkspaceName);
+            args.append(",");
+            args.append(resolver.getJCRPath(getPath(srcNodeId, sessionInfo)));
+            args.append(",");
+            String destParentPath = resolver.getJCRPath(getPath(destParentNodeId, sessionInfo));
+            String destPath = destParentPath + "/" + resolver.getJCRName(destName);
+            args.append(destPath);
+
+            method.addParameter(PARAM_COPY, args.toString());
+            addIfHeader(sessionInfo, method);
+            getClient(sessionInfo).executeMethod(method);
+
+            method.checkSuccess();
+        } catch (IOException e) {
+            throw new RepositoryException(e);
+        } catch (DavException e) {
+            throw ExceptionConverter.generate(e);
+        } finally {
+            if (method != null) {
+                method.releaseConnection();
+            }
+        }
+    }
+
+    public void clone(SessionInfo sessionInfo, String srcWorkspaceName, NodeId srcNodeId, NodeId destParentNodeId, Name destName, boolean removeExisting) throws RepositoryException {
+        PostMethod method = null;
+        try {
+            method = new PostMethod(getWorkspaceURI(sessionInfo));
+
+            NamePathResolver resolver = getNamePathResolver(sessionInfo);
+            StringBuffer args = new StringBuffer();
+            args.append(srcWorkspaceName);
+            args.append(",");
+            args.append(resolver.getJCRPath(getPath(srcNodeId, sessionInfo)));
+            args.append(",");
+            String destParentPath = resolver.getJCRPath(getPath(destParentNodeId, sessionInfo));
+            String destPath = destParentPath + "/" + resolver.getJCRName(destName);
+            args.append(destPath);
+            args.append(",");
+            args.append(Boolean.toString(removeExisting));
+
+            method.addParameter(PARAM_CLONE, args.toString());
+            addIfHeader(sessionInfo, method);
+            getClient(sessionInfo).executeMethod(method);
+
+            method.checkSuccess();
+
+        } catch (IOException e) {
+            throw new RepositoryException(e);
+        } catch (DavException e) {
+            throw ExceptionConverter.generate(e);
+        } finally {
+            if (method != null) {
+                method.releaseConnection();
+            }
+        }
+    }
+
+    private static void addIfHeader(SessionInfo sInfo, HttpMethod method) {
+        String[] locktokens = sInfo.getLockTokens();
+        if (locktokens != null && locktokens.length > 0) {
+            IfHeader ifH = new IfHeader(locktokens);
+            method.setRequestHeader(ifH.getHeaderName(), ifH.getHeaderValue());
+        }
+    }
+
+    //--------------------------------------------------------------------------
+
+    private class BatchImpl implements Batch {
+
+        private final ItemId targetId;
+        private final SessionInfo sessionInfo;
+        private final List parts;
+        private final List diff;
+        /*
+          If this batch needs to remove multiple same-name-siblings starting
+          from lower index, the index of the following siblings must be reset
+          in order to avoid PathNotFoundException.
+        */
+        private final Map removed = new HashMap();
+
+        private PostMethod method; // TODO: use PATCH request instead.
+        private boolean isConsumed;
+
+        private BatchImpl(ItemId targetId, SessionInfo sessionInfo) {
+            this.targetId = targetId;
+            this.sessionInfo = sessionInfo;
+            parts = new ArrayList();
+            diff = new ArrayList();
+        }
+
+        private void start() throws RepositoryException {
+            checkConsumed();
+
+            // add locktokens
+            addIfHeader(sessionInfo, method);
+
+            // insert the content of 'batchMap' part containing the ordered list
+            // of methods to be executed:
+            StringBuffer buf = new StringBuffer();
+            for (Iterator it = diff.iterator(); it.hasNext();) {
+                buf.append(it.next().toString());
+                if (it.hasNext()) {
+                    buf.append("\r");
+                }
+            }
+
+            if (parts.isEmpty()) {
+                // only a diff part. no multipart required.
+                method.addParameter(PARAM_DIFF, buf.toString());
+            } else {
+                // other parts are present -> add the diff part
+                addPart(PARAM_DIFF, buf.toString());
+                // ... and create multipart-entity (and set it to method)
+                Part[] partArr = (Part[]) parts.toArray(new Part[parts.size()]);
+                RequestEntity entity = new MultipartRequestEntity(partArr, method.getParams());
+                method.setRequestEntity(entity);
+            }
+
+            HttpClient client = getClient(sessionInfo);
+            try {
+                client.executeMethod(method);
+                method.checkSuccess();
+            }  catch (IOException e) {
+                throw new RepositoryException(e);
+            } catch (DavException e) {
+                throw ExceptionConverter.generate(e);
+            } finally {
+                method.releaseConnection();
+            }
+        }
+
+        private void dispose() {
+            method = null;
+            isConsumed = true;
+        }
+
+        private void checkConsumed() {
+            if (isConsumed) {
+                throw new IllegalStateException("Batch has already been consumed.");
+            }
+        }
+
+        private boolean isEmpty() {
+            return method == null;
+        }
+
+        private void assertMethod() throws RepositoryException {
+            if (method == null) {
+                String uri = getURI(targetId, sessionInfo);
+                method = new PostMethod(uri);
+                // ship lock-tokens as if-header to cirvumvent problems with
+                // locks created by this session.
+                String[] locktokens = sessionInfo.getLockTokens();
+                if (locktokens != null && locktokens.length > 0) {
+                    IfHeader ifH = new IfHeader(locktokens);
+                    method.setRequestHeader(ifH.getHeaderName(), ifH.getHeaderValue());
+                }
+            }
+        }
+
+        //----------------------------------------------------------< Batch >---
+        /**
+         * @inheritDoc
+         */
+        public void addNode(NodeId parentId, Name nodeName, Name nodetypeName,
+                            String uuid) throws RepositoryException {
+            assertMethod();
+
+            NamePathResolver resolver = getNamePathResolver(sessionInfo);
+            Path p = getPathFactory().create(getPath(parentId, sessionInfo), nodeName, true);
+            String jcrPath = resolver.getJCRPath(p);
+
+            StringWriter wr = new StringWriter();
+            wr.write('{');
+            wr.write(getJSONKey(JcrConstants.JCR_PRIMARYTYPE));
+            wr.write(Text.getJSONString(getNamePathResolver(sessionInfo).getJCRName(nodetypeName)));
+            if (uuid != null) {
+                wr.write(',');
+                wr.write(getJSONKey(JcrConstants.JCR_UUID));
+                wr.write(Text.getJSONString(uuid));
+            }
+            wr.write('}');
+            appendDiff(SYMBOL_ADD_NODE, jcrPath, wr.toString());
+        }
+
+        /**
+         * @inheritDoc
+         */
+        public void addProperty(NodeId parentId, Name propertyName, QValue value) throws RepositoryException {
+            assertMethod();
+            Path p = getPathFactory().create(getPath(parentId, sessionInfo), propertyName, true);
+            setProperty(p, value);
+        }
+
+        /**
+         * @inheritDoc
+         */
+        public void addProperty(NodeId parentId, Name propertyName, QValue[] values) throws RepositoryException {
+            assertMethod();
+            Path p = getPathFactory().create(getPath(parentId, sessionInfo), propertyName, true);
+            setProperty(p, values);
+        }
+
+        /**
+         * @inheritDoc
+         */
+        public void setValue(PropertyId propertyId, QValue value) throws RepositoryException {
+            assertMethod();
+            setProperty(getPath(propertyId, sessionInfo), value);
+        }
+
+        /**
+         * @inheritDoc
+         */
+        public void setValue(PropertyId propertyId, QValue[] values) throws RepositoryException {
+            assertMethod();
+            Path p = getPath(propertyId, sessionInfo);
+            setProperty(p, values);
+        }
+
+        /**
+         * @inheritDoc
+         */
+        public void remove(ItemId itemId) throws RepositoryException {
+            assertMethod();
+
+            Path rmPath = getPath(itemId, sessionInfo);
+            if (itemId.denotesNode()) {
+                rmPath = calcRemovePath(rmPath);
+            }
+            String rmJcrPath = getNamePathResolver(sessionInfo).getJCRPath(rmPath);
+            appendDiff(SYMBOL_REMOVE, rmJcrPath, null);
+        }
+
+        /**
+         * @inheritDoc
+         */
+        public void reorderNodes(NodeId parentId, NodeId srcNodeId, NodeId beforeNodeId) throws RepositoryException {
+            assertMethod();
+
+            // TODO: multiple reorder of SNS nodes requires readjustment of path -> see remove()
+            String srcPath = getNamePathResolver(sessionInfo).getJCRPath(getPath(srcNodeId, sessionInfo));
+
+            StringBuffer val = new StringBuffer();
+            if (beforeNodeId != null) {
+                Path beforePath = getPath(beforeNodeId, sessionInfo);
+                String beforeJcrPath = getNamePathResolver(sessionInfo).getJCRPath(beforePath);
+                val.append(Text.getName(beforeJcrPath));
+                val.append(ORDER_POSITION_BEFORE);
+            } else {
+                val.append(ORDER_POSITION_LAST);
+            }
+            appendDiff(SYMBOL_MOVE, srcPath, val.toString());
+        }
+
+        /**
+         * @inheritDoc
+         */
+        public void setMixins(NodeId nodeId, Name[] mixinNodeTypeNames) throws RepositoryException {
+            assertMethod();
+
+            NamePathResolver resolver = getNamePathResolver(sessionInfo);
+            QValue[] vs = new QValue[mixinNodeTypeNames.length];
+            for (int i = 0; i < mixinNodeTypeNames.length; i++) {
+                vs[i] = getQValueFactory(sessionInfo).create(mixinNodeTypeNames[i]);
+            }
+            addProperty(nodeId, resolver.getQName(JcrConstants.JCR_MIXINTYPES), vs);
+        }
+
+        /**
+         * @inheritDoc
+         */
+        public void move(NodeId srcNodeId, NodeId destParentNodeId, Name destName) throws RepositoryException {
+            assertMethod();
+
+            String srcPath = getNamePathResolver(sessionInfo).getJCRPath(getPath(srcNodeId, sessionInfo));
+            Path destPath = getPathFactory().create(getPath(destParentNodeId, sessionInfo), destName, true);
+            String destJcrPath = getNamePathResolver(sessionInfo).getJCRPath(destPath);
+
+            appendDiff(SYMBOL_MOVE, srcPath, destJcrPath);
+        }
+
+        /**
+         *
+         * @param symbol
+         * @param targetPath
+         * @param value
+         */
+        private void appendDiff(char symbol, String targetPath, String value) {
+            StringBuffer bf = new StringBuffer();
+            bf.append(symbol).append(targetPath).append(" : ");
+            if (value != null) {
+                bf.append(value);
+            }
+            diff.add(bf.toString());
+        }
+
+        /**
+         *
+         * @param propPath
+         * @param value
+         * @throws RepositoryException
+         */
+        private void setProperty(Path propPath, QValue value) throws RepositoryException {
+            NamePathResolver resolver = getNamePathResolver(sessionInfo);
+            String jcrPropPath = resolver.getJCRPath(propPath);
+            clearPreviousSetProperty(jcrPropPath);
+
+            String strValue = getJSONString(value);
+            appendDiff(SYMBOL_SET_PROPERTY, jcrPropPath, strValue);
+            if (strValue == null) {
+                addPart(jcrPropPath, value, resolver);
+            }
+        }
+
+        private void setProperty(Path propPath, QValue[] values) throws RepositoryException {
+            NamePathResolver resolver = getNamePathResolver(sessionInfo);
+            String jcrPropPath = resolver.getJCRPath(propPath);
+            clearPreviousSetProperty(jcrPropPath);
+
+            StringBuffer strVal = new StringBuffer("[");
+            for (int i = 0; i < values.length; i++) {
+                String str = getJSONString(values[i]);
+                if (str == null) {
+                    addPart(jcrPropPath, values[i], resolver);
+                } else {
+                    String delim = (i == 0) ? "" : ",";
+                    strVal.append(delim).append(str);
+                }
+            }
+            strVal.append("]");
+            appendDiff(SYMBOL_SET_PROPERTY, jcrPropPath, strVal.toString());
+        }
+
+        private void clearPreviousSetProperty(String jcrPropPath) {
+            String key = SYMBOL_SET_PROPERTY + jcrPropPath + " : ";
+            // make sure that multiple calls to setProperty for a given path
+            // are only reflected once in the multipart, otherwise this will
+            // cause consistency problems as the various calls cannot be separated
+            // (missing unique identifier for the parts).
+            for (Iterator it = diff.iterator(); it.hasNext();) {
+                String entry = it.next().toString();
+                if (entry.startsWith(key)) {
+                    it.remove();
+                    removeParts(jcrPropPath);
+                    return;
+                }
+            }
+        }
+
+        /**
+         *
+         * @param paramName
+         * @param value
+         */
+        private void addPart(String paramName, String value) {
+            parts.add(new StringPart(paramName, value));
+        }
+
+        /**
+         *
+         * @param paramName
+         * @param value
+         * @param resolver
+         * @throws RepositoryException
+         */
+        private void addPart(String paramName, QValue value, NamePathResolver resolver) throws RepositoryException {
+            Part part;
+            switch (value.getType()) {
+                case PropertyType.BINARY:
+                    part = new FilePart(paramName, new BinaryPartSource(value));
+                    break;
+                case PropertyType.NAME:
+                    part = new StringPart(paramName, resolver.getJCRName(value.getName()));
+                    break;
+                case PropertyType.PATH:
+                    part = new StringPart(paramName, resolver.getJCRPath(value.getPath()));
+                    break;
+                default:
+                    part = new StringPart(paramName, value.getString());
+            }
+            String ctype = JcrValueType.contentTypeFromType(value.getType());
+            ((PartBase) part).setContentType(ctype);
+
+            parts.add(part);
+        }
+
+        private void removeParts(String paramName) {
+            for (Iterator it = parts.iterator(); it.hasNext();) {
+                Part part = (Part) it.next();
+                if (part.getName().equals(paramName)) {
+                    it.remove();
+                }
+            }
+        }
+
+        private String getJSONKey(String str) {
+            return Text.getJSONString(str) + ":";
+        }
+
+        private String getJSONString(QValue value) throws RepositoryException {
+            String str;
+            switch (value.getType()) {
+                case PropertyType.STRING:
+                    str = Text.getJSONString(value.getString());
+                    break;
+                case PropertyType.BOOLEAN:
+                case PropertyType.LONG:
+                    str = value.getString();
+                    break;
+                case PropertyType.DOUBLE:
+                    str = value.getString();
+                    if (str.indexOf('.') == -1) {
+                        str += ".0";
+                    }
+                    break;
+                default:
+                    // JSON cannot specifically handle this property type...
+                    str = null;
+            }
+            return str;
+        }
+
+        private Path calcRemovePath(Path removedNodePath) throws RepositoryException {
+            removed.put(removedNodePath, removedNodePath);
+            Name name = removedNodePath.getNameElement().getName();
+            int index = removedNodePath.getNameElement().getNormalizedIndex();
+            if (index > Path.INDEX_DEFAULT) {
+                Path.Element[] elems = removedNodePath.getElements();
+                PathBuilder pb = new PathBuilder();
+                for (int i = 0; i <= elems.length - 2; i++) {
+                    pb.addLast(elems[i]);
+                }
+                Path parent = pb.getPath();
+                while (index > Path.INDEX_UNDEFINED) {
+                    Path siblingP = getPathFactory().create(parent, name, --index, true);
+                    if (removed.containsKey(siblingP)) {
+                        // as the previous sibling has been remove -> the same index
+                        // must be used to remove the node at removedNodePath.
+                        siblingP = (Path) removed.get(siblingP);
+                        removed.put(removedNodePath, siblingP);
+                        return siblingP;
+                    }
+                }
+            }
+            // first of siblings or no sibling at all
+            return removedNodePath;
+        }
+    }
+}

Propchange: jackrabbit/sandbox/spi/spi2davex/src/main/java/org/apache/jackrabbit/spi/spi2davex/RepositoryServiceImpl.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/sandbox/spi/spi2davex/src/main/java/org/apache/jackrabbit/spi/spi2davex/RepositoryServiceImpl.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url

Added: jackrabbit/sandbox/spi/spi2davex/src/main/java/org/apache/jackrabbit/spi/spi2davex/ValueLoader.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/spi/spi2davex/src/main/java/org/apache/jackrabbit/spi/spi2davex/ValueLoader.java?rev=738480&view=auto
==============================================================================
--- jackrabbit/sandbox/spi/spi2davex/src/main/java/org/apache/jackrabbit/spi/spi2davex/ValueLoader.java (added)
+++ jackrabbit/sandbox/spi/spi2davex/src/main/java/org/apache/jackrabbit/spi/spi2davex/ValueLoader.java Wed Jan 28 13:35:47 2009
@@ -0,0 +1,106 @@
+/*
+ * 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.spi.spi2davex;
+
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.commons.httpclient.methods.GetMethod;
+import org.apache.jackrabbit.spi2dav.ExceptionConverter;
+import org.apache.jackrabbit.webdav.DavConstants;
+import org.apache.jackrabbit.webdav.DavException;
+import org.apache.jackrabbit.webdav.DavServletResponse;
+import org.apache.jackrabbit.webdav.MultiStatusResponse;
+import org.apache.jackrabbit.webdav.client.methods.DavMethodBase;
+import org.apache.jackrabbit.webdav.client.methods.PropFindMethod;
+import org.apache.jackrabbit.webdav.jcr.ItemResourceConstants;
+import org.apache.jackrabbit.webdav.property.DavProperty;
+import org.apache.jackrabbit.webdav.property.DavPropertyNameSet;
+import org.apache.jackrabbit.webdav.property.DavPropertySet;
+
+import javax.jcr.ItemNotFoundException;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * <code>ValueLoader</code>...
+ */
+class ValueLoader {
+
+    private final HttpClient client;
+
+    ValueLoader(HttpClient client) {
+        this.client = client;
+    }
+
+    void loadBinary(String uri, int index, Target target) throws RepositoryException, IOException {
+        GetMethod method = new GetMethod(uri);
+        try {
+            int statusCode = client.executeMethod(method);
+            if (statusCode == DavServletResponse.SC_OK) {
+                target.setStream(method.getResponseBodyAsStream());
+            } else {
+                throw ExceptionConverter.generate(new DavException(statusCode, ("Unable to load binary. Status line = " + method.getStatusLine().toString())));
+            }
+        } finally {
+            method.releaseConnection();
+        }
+    }
+
+    int loadType(String uri) throws RepositoryException, IOException {
+        DavPropertyNameSet nameSet = new DavPropertyNameSet();
+        nameSet.add(ItemResourceConstants.JCR_TYPE);
+
+        DavMethodBase method = null;
+        try {
+            method = new PropFindMethod(uri, nameSet, DavConstants.DEPTH_0);
+            client.executeMethod(method);
+            method.checkSuccess();
+
+            MultiStatusResponse[] responses = method.getResponseBodyAsMultiStatus().getResponses();
+            if (responses.length == 1) {
+                DavPropertySet props = responses[0].getProperties(DavServletResponse.SC_OK);
+                DavProperty type = props.get(ItemResourceConstants.JCR_TYPE);
+                if (type != null) {
+                    return PropertyType.valueFromName(type.getValue().toString());
+                } else {
+                    throw new RepositoryException("Internal error. Cannot retrieve property type at " + uri);
+                }
+            } else {
+                throw new ItemNotFoundException("Internal error. Cannot retrieve property type at " + uri);
+            }
+        } catch (DavException e) {
+            throw ExceptionConverter.generate(e);
+        } finally {
+            if (method != null) {
+                method.releaseConnection();
+            }
+        }
+    }
+
+    //--------------------------------------------------------------------------
+    /**
+     * Internal inteface
+     */
+    interface Target {
+        /**
+         * @param in
+         * @throws IOException
+         */
+        void setStream(InputStream in) throws IOException;
+    }
+}
\ No newline at end of file

Propchange: jackrabbit/sandbox/spi/spi2davex/src/main/java/org/apache/jackrabbit/spi/spi2davex/ValueLoader.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/sandbox/spi/spi2davex/src/main/java/org/apache/jackrabbit/spi/spi2davex/ValueLoader.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url