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 < 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 < 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 < 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