You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by dp...@apache.org on 2007/01/11 15:40:01 UTC

svn commit: r495239 [2/2] - /jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/FileRecordLog.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/FileRecordLog.java?view=diff&rev=495239&r1=495238&r2=495239
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/FileRecordLog.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/FileRecordLog.java Thu Jan 11 06:40:00 2007
@@ -16,6 +16,9 @@
  */
 package org.apache.jackrabbit.core.cluster;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import java.io.File;
 import java.io.DataInputStream;
 import java.io.IOException;
@@ -23,14 +26,43 @@
 import java.io.BufferedInputStream;
 import java.io.DataOutputStream;
 import java.io.FileOutputStream;
+import java.io.InputStream;
 
 /**
- * A file record log is a file containing {@link FileRecord}s. Internally,
- * the first 8 bytes contain the revision this log starts with.
+ * A file record log is a file containing {@link FileRecord}s. Physically,
+ * the first 4 bytes contain a signature, followed by a major and minor version
+ * (2 bytes each). The next 8 bytes contain the revision this log starts with.
+ * After this, zero or more <code>FileRecord</code>s follow.
  */
 class FileRecordLog {
 
     /**
+     * Logger.
+     */
+    private static Logger log = LoggerFactory.getLogger(FileRecordLog.class);
+
+    /**
+     * Record log signature.
+     */
+    private static final byte[] SIGNATURE = { 'J', 'L', 'O', 'G' };
+
+    /**
+     * Known major version.
+     */
+    private static final short MAJOR_VERSION = 1;
+
+    /**
+     * Known minor version.
+     */
+    private static final short MINOR_VERSION = 0;
+
+    /**
+     * Header size. This is the size of {@link #SIGNATURE}, {@link #MAJOR_VERSION},
+     * {@link #MINOR_VERSION} and first revision (8 bytes).
+     */
+    private static final int HEADER_SIZE = 4 + 2 + 2 + 8;
+
+    /**
      * Underlying file.
      */
     private File file;
@@ -68,8 +100,9 @@
             DataInputStream in = new DataInputStream(new FileInputStream(file));
 
             try {
+                readHeader(in);
                 minRevision = in.readLong();
-                maxRevision = minRevision + file.length() - 8;
+                maxRevision = minRevision + file.length() - HEADER_SIZE;
             } finally {
                 in.close();
             }
@@ -117,12 +150,23 @@
      */
     public void seek(long revision) throws IOException {
         if (in != null) {
-            String msg = "Seek allowed exactly once.";
+            String msg = "Stream already open: seek() only allowed once.";
             throw new IllegalStateException(msg);
         }
-        open();
+        in = new DataInputStream(new BufferedInputStream(
+                new FileInputStream(file)));
+        skip(revision - minRevision + HEADER_SIZE);
+    }
 
-        long skiplen = revision - minRevision + 8;
+    /**
+     * Skip exactly <code>n</code> bytes. Throws if less bytes are skipped.
+     *
+     * @param n bytes to skip
+     * @throws IOException if an I/O error occurs, or less that <code>n</code> bytes
+     *                     were skipped.
+     */
+    private void skip(long n) throws IOException {
+        long skiplen = n;
         while (skiplen > 0) {
             long skipped = in.skip(skiplen);
             if (skipped <= 0) {
@@ -137,54 +181,143 @@
     }
 
     /**
-     * Append a record to this log.
+     * Read the file record at the current seek position.
+     *
+     * @return file record
+     * @throws IOException if an I/O error occurs
+     */
+    public FileRecord read() throws IOException {
+        byte[] creator = new byte[in.readUnsignedShort()];
+        in.readFully(creator);
+        int length = in.readInt();
+        return new FileRecord(creator, length, in);
+    }
+
+    /**
+     * Append a record to this log. Returns the revision following this record.
      *
      * @param record record to add
+     * @return next available revision
      * @throws IOException if an I/O error occurs
      */
-    public void append(FileRecord record) throws IOException {
+    public long append(long revision, byte[] creator, File record) throws IOException {
         DataOutputStream out = new DataOutputStream(new FileOutputStream(file, true));
         try {
+            int recordLength = (int) record.length();
             if (isNew) {
-                out.writeLong(record.getRevision());
+                writeHeader(out);
+                out.writeLong(revision);
             }
-            record.append(out);
+            out.writeShort(creator.length);
+            out.write(creator);
+            out.writeInt(recordLength);
+            append(record, out);
+            return revision + getRecordSize(creator, recordLength);
         } finally {
             out.close();
         }
     }
 
     /**
-     * Open this log.
-     *
-     * @throws IOException if an I/O error occurs
+     * Close this log.
      */
-    private void open() throws IOException {
-        in = new DataInputStream(new BufferedInputStream(
-                new FileInputStream(file)));
+    public void close() {
+        try {
+            if (in != null) {
+                in.close();
+            }
+        } catch (IOException e) {
+            String msg = "Error while closing record log: " + e.getMessage();
+            log.warn(msg);
+        }
     }
 
     /**
-     * Return the underlying input stream.
+     * Return the size of a stored record . A stored record's size is the size of
+     * the length-prefixed creator string plus the size of the length-prefixed data.
      *
-     * @return underlying input stream
+     * @param creator creator string
+     * @param length data length
+     * @return size of a stored record
      */
-    protected DataInputStream getInputStream() {
-        if (in == null) {
-            String msg = "Input stream not open.";
-            throw new IllegalStateException(msg);
+    public static int getRecordSize(byte[] creator, int length) {
+        return 2 + creator.length + 4 + length;
+    }
+
+    /**
+     * Read signature and major/minor version of file and verify.
+     *
+     * @param in input stream
+     * @throws IOException if an I/O error occurs or the file does
+     *                     not have a valid header.
+     */
+    private void readHeader(DataInputStream in) throws IOException {
+        byte[] signature = new byte[SIGNATURE.length];
+        in.readFully(signature);
+
+        for (int i = 0; i < SIGNATURE.length; i++) {
+            if (signature[i] != SIGNATURE[i]) {
+                String msg = "Record log '" + file.getPath() +
+                        "' has wrong signature: " + toHexString(signature);
+                throw new IOException(msg);
+            }
+        }
+
+        short major = in.readShort();
+        in.readShort(); // minor version not used yet
+
+        if (major > MAJOR_VERSION) {
+            String msg = "Record log '" + file.getPath() +
+                    "' has incompatible major version: " + major;
+            throw new IOException(msg);
         }
-        return in;
     }
 
     /**
-     * Close this log.
+     * Write signature and major/minor.
      *
-     * @throws IOException if an I/O error occurs
+     * @param out input stream
+     * @throws IOException if an I/O error occurs.
      */
-    public void close() throws IOException {
-        if (in != null) {
+    private void writeHeader(DataOutputStream out) throws IOException {
+        out.write(SIGNATURE);
+        out.writeShort(MAJOR_VERSION);
+        out.writeShort(MINOR_VERSION);
+    }
+
+    /**
+     * Append a record to this log's output stream.
+     *
+     * @param record record to append
+     * @param out where to append to
+     */
+    private static void append(File record, DataOutputStream out) throws IOException {
+        byte[] buffer = new byte[8192];
+        int len;
+
+        InputStream in = new BufferedInputStream(new FileInputStream(record));
+        try {
+            while ((len = in.read(buffer)) > 0) {
+                out.write(buffer, 0, len);
+            }
+            out.flush();
+        } finally {
             in.close();
         }
+    }
+
+    /**
+     * Convert a byte array to its hexadecimal string representation.
+     */
+    private static String toHexString(byte[] b) {
+        StringBuffer buf = new StringBuffer();
+        for (int i = 0; i < b.length; i++) {
+            String s = Integer.toHexString(b[i] & 0xff).toUpperCase();
+            if (s.length() == 1) {
+                buf.append('0');
+            }
+            buf.append(s);
+        }
+        return buf.toString();
     }
 }

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/FileRevision.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/FileRevision.java?view=diff&rev=495239&r1=495238&r2=495239
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/FileRevision.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/FileRevision.java Thu Jan 11 06:40:00 2007
@@ -133,7 +133,7 @@
         lock(true);
 
         try {
-            long value = 0;
+            long value = 0L;
             if (raf.length() > 0) {
                 raf.seek(0L);
                 value = raf.readLong();

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/Record.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/Record.java?view=auto&rev=495239
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/Record.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/Record.java Thu Jan 11 06:40:00 2007
@@ -0,0 +1,34 @@
+/*
+ * 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.core.cluster;
+
+/**
+ * Contains definitions common to both <code>RecordInput</code> and
+ * <code>RecordOutput</code>.
+ */
+interface Record {
+
+    /**
+     * Indicator for a literal UUID.
+     */
+    public static final byte UUID_LITERAL = 'L';
+
+    /**
+     * Indicator for a UUID index.
+     */
+    public static final byte UUID_INDEX = 'I';
+}
\ No newline at end of file

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/RecordInput.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/RecordInput.java?view=auto&rev=495239
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/RecordInput.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/RecordInput.java Thu Jan 11 06:40:00 2007
@@ -0,0 +1,266 @@
+/*
+ * 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.core.cluster;
+
+import org.apache.jackrabbit.core.NodeId;
+import org.apache.jackrabbit.core.PropertyId;
+import org.apache.jackrabbit.core.nodetype.NodeTypeDef;
+import org.apache.jackrabbit.core.nodetype.compact.CompactNodeTypeDefReader;
+import org.apache.jackrabbit.core.nodetype.compact.ParseException;
+import org.apache.jackrabbit.name.NameException;
+import org.apache.jackrabbit.name.NamespaceResolver;
+import org.apache.jackrabbit.name.QName;
+import org.apache.jackrabbit.name.NameFormat;
+import org.apache.jackrabbit.name.Path;
+import org.apache.jackrabbit.name.PathFormat;
+import org.apache.jackrabbit.name.MalformedPathException;
+import org.apache.jackrabbit.uuid.Constants;
+import org.apache.jackrabbit.uuid.UUID;
+
+import java.io.IOException;
+import java.io.DataInputStream;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Allows reading data from a <code>DataInputStream</code>.
+ */
+class RecordInput {
+
+    /**
+     * Underlying input stream.
+     */
+    private final DataInputStream in;
+
+    /**
+     * Name resolver.
+     */
+    private final NamespaceResolver resolver;
+
+    /**
+     * UUID index.
+     */
+    private final ArrayList uuidIndex = new ArrayList();
+
+    /**
+     * Flag indicating whether this input is closed.
+     */
+    private boolean closed;
+
+    /**
+     * Open an existing file record.
+     *
+     * @param in       underlying input stream
+     * @param resolver namespace resolver
+     */
+    public RecordInput(DataInputStream in, NamespaceResolver resolver) {
+        this.in = in;
+        this.resolver = resolver;
+    }
+
+    /**
+     * Read a byte from the underlying stream.
+     *
+     * @return byte
+     * @throws IOException if an I/O error occurs
+     */
+    public byte readByte() throws IOException {
+        checkOpen();
+
+        return in.readByte();
+    }
+
+    /**
+     * Read a character from the underlying stream.
+     *
+     * @return character
+     * @throws IOException if an I/O error occurs
+     */
+    public char readChar() throws IOException {
+        checkOpen();
+
+        return in.readChar();
+    }
+
+    /**
+     * Read a boolean from the underlying stream.
+     *
+     * @return boolean
+     * @throws IOException if an I/O error occurs
+     */
+    public boolean readBoolean() throws IOException {
+        checkOpen();
+
+        return in.readBoolean();
+    }
+
+    /**
+     * Read an integer from the underlying stream.
+     *
+     * @return integer
+     * @throws IOException if an I/O error occurs
+     */
+    public int readInt() throws IOException {
+        checkOpen();
+
+        return in.readInt();
+    }
+
+    /**
+     * Read a string from the underlying stream.
+     *
+     * @return string or <code>null</code>
+     * @throws IOException if an I/O error occurs
+     */
+    public String readString() throws IOException {
+        checkOpen();
+
+        boolean isNull = in.readBoolean();
+        if (isNull) {
+            return null;
+        } else {
+            return in.readUTF();
+        }
+    }
+
+    /**
+     * Read a <code>QName</code>.
+     *
+     * @return name
+     * @throws IOException if an I/O error occurs
+     * @throws NameException if the name retrieved is illegal
+     */
+    public QName readQName() throws IOException, NameException {
+        checkOpen();
+
+        return NameFormat.parse(readString(), resolver);
+    }
+
+    /**
+     * Read a <code>PathElement</code>.
+     *
+     * @return path element
+     * @throws IOException if an I/O error occurs
+     * @throws NameException if the name retrieved is illegal
+     */
+    public Path.PathElement readPathElement() throws IOException, NameException {
+        checkOpen();
+
+        QName name = NameFormat.parse(readString(), resolver);
+        int index = readInt();
+        if (index != 0) {
+            return Path.PathElement.create(name, index);
+        } else {
+            return Path.PathElement.create(name);
+        }
+    }
+
+    /**
+     * Read a <code>Path</code>.
+     *
+     * @return path
+     * @throws IOException if an I/O error occurs
+     * @throws MalformedPathException if the path is malformed
+     */
+    public Path readPath() throws IOException, MalformedPathException {
+        checkOpen();
+
+        return PathFormat.parse(readString(), resolver);
+    }
+
+    /**
+     * Read a <code>NodeId</code>
+     *
+     * @return node id
+     * @throws IOException if an I/O error occurs
+     */
+    public NodeId readNodeId() throws IOException {
+        checkOpen();
+
+        byte uuidType = readByte();
+        if (uuidType == Record.UUID_INDEX) {
+            int index = readInt();
+            if (index == -1) {
+                return null;
+            } else {
+                return (NodeId) uuidIndex.get(index);
+            }
+        } else if (uuidType == Record.UUID_LITERAL) {
+            byte[] b = new byte[Constants.UUID_BYTE_LENGTH];
+            in.readFully(b);
+            NodeId nodeId = new NodeId(new UUID(b));
+            uuidIndex.add(nodeId);
+            return nodeId;
+        } else {
+            String msg = "UUID type unknown: " + uuidType;
+            throw new IOException(msg);
+        }
+    }
+
+    /**
+     * Read a <code>PropertyId</code>
+     *
+     * @return property id
+     * @throws IOException if an I/O error occurs
+     * @throws NameException if the name retrieved is illegal
+     */
+    public PropertyId readPropertyId() throws IOException, NameException  {
+        checkOpen();
+
+        return new PropertyId(readNodeId(), readQName());
+    }
+
+    /**
+     * Read a <code>NodeTypeDef</code>
+     */
+    public NodeTypeDef readNodeTypeDef() throws IOException, ParseException {
+        checkOpen();
+
+        StringReader sr = new StringReader(readString());
+
+        CompactNodeTypeDefReader reader = new CompactNodeTypeDefReader(sr, "(internal)");
+        List ntds = reader.getNodeTypeDefs();
+        if (ntds.size() != 1) {
+            throw new IOException("Expected one node type definition: got " + ntds.size());
+        }
+        return (NodeTypeDef) ntds.get(0);
+    }
+
+
+
+    /**
+     * Close this input. Does not close underlying stream as this is a shared resource.
+     */
+    public void close() {
+        checkOpen();
+
+        closed = true;
+    }
+
+    /**
+     * Check that this input is open, throw otherwise.
+     *
+     * @throws IllegalStateException if input is closed.
+     */
+    private void checkOpen() throws IllegalStateException {
+        if (closed) {
+            throw new IllegalStateException("Input closed.");
+        }
+    }
+
+}

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/RecordOutput.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/RecordOutput.java?view=auto&rev=495239
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/RecordOutput.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/RecordOutput.java Thu Jan 11 06:40:00 2007
@@ -0,0 +1,269 @@
+/*
+ * 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.core.cluster;
+
+import org.apache.jackrabbit.core.NodeId;
+import org.apache.jackrabbit.core.PropertyId;
+import org.apache.jackrabbit.core.nodetype.NodeTypeDef;
+import org.apache.jackrabbit.core.nodetype.compact.CompactNodeTypeDefWriter;
+import org.apache.jackrabbit.name.NamespaceResolver;
+import org.apache.jackrabbit.name.QName;
+import org.apache.jackrabbit.name.NameFormat;
+import org.apache.jackrabbit.name.Path;
+import org.apache.jackrabbit.name.PathFormat;
+import org.apache.jackrabbit.name.NoPrefixDeclaredException;
+
+import java.io.IOException;
+import java.io.DataOutputStream;
+import java.io.StringWriter;
+import java.util.ArrayList;
+
+/**
+ * Allows writing data to a <code>DataOutputStream</code>.
+ */
+class RecordOutput {
+
+    /**
+     * Underlying output stream.
+     */
+    private final DataOutputStream out;
+
+    /**
+     * Name resolver.
+     */
+    private final NamespaceResolver resolver;
+
+    /**
+     * UUID index.
+     */
+    private final ArrayList uuidIndex = new ArrayList();
+
+    /**
+     * Flag indicating whether this output is closed.
+     */
+    private boolean closed;
+
+    /**
+     * Create a new file record.
+     *
+     * @param out      outputstream to write to
+     * @param resolver namespace resolver
+     */
+    public RecordOutput(DataOutputStream out, NamespaceResolver resolver) {
+        this.out = out;
+        this.resolver = resolver;
+    }
+
+    /**
+     * Write a byte to the underlying stream.
+     *
+     * @param n byte
+     * @throws IOException if an I/O error occurs
+     */
+    public void writeByte(int n) throws IOException {
+        checkOpen();
+
+        out.writeByte(n);
+    }
+
+    /**
+     * Write a character to the underlying stream.
+     *
+     * @param c character
+     * @throws IOException if an I/O error occurs
+     */
+    public void writeChar(char c) throws IOException {
+        checkOpen();
+
+        out.writeChar(c);
+    }
+
+    /**
+     * Write a boolean from the underlying stream.
+     *
+     * @param b boolean
+     * @throws IOException if an I/O error occurs
+     */
+    public void writeBoolean(boolean b) throws IOException {
+        checkOpen();
+
+        out.writeBoolean(b);
+    }
+
+    /**
+     * Write an integer to the underlying stream.
+     *
+     * @param n integer
+     * @throws IOException if an I/O error occurs
+     */
+    public void writeInt(int n) throws IOException {
+        checkOpen();
+
+        out.writeInt(n);
+    }
+
+    /**
+     * Write a string from the underlying stream.
+     *
+     * @param s string, may be <code>null</code>
+     * @throws IOException if an I/O error occurs
+     */
+    public void writeString(String s) throws IOException {
+        checkOpen();
+
+        if (s == null) {
+            out.writeBoolean(true);
+        } else {
+            out.writeBoolean(false);
+            out.writeUTF(s);
+        }
+    }
+
+    /**
+     * Write a <code>QName</code>.
+     *
+     * @param name name
+     * @throws IOException if an I/O error occurs
+     * @throws NoPrefixDeclaredException if the prefix is not declared
+     */
+    public void writeQName(QName name) throws IOException, NoPrefixDeclaredException {
+        checkOpen();
+
+        writeString(NameFormat.format(name, resolver));
+    }
+
+    /**
+     * Write a <code>PathElement</code>.
+     *
+     * @param element path element
+     * @throws IOException if an I/O error occurs
+     * @throws NoPrefixDeclaredException if the prefix is not declared
+     */
+    public void writePathElement(Path.PathElement element) throws IOException, NoPrefixDeclaredException {
+        checkOpen();
+
+        writeQName(element.getName());
+        writeInt(element.getIndex());
+    }
+
+    /**
+     * Write a <code>Path</code>.
+     *
+     * @param path path
+     * @throws IOException if an I/O error occurs
+     * @throws NoPrefixDeclaredException if the prefix is not declared
+     */
+    public void writePath(Path path) throws IOException, NoPrefixDeclaredException {
+        checkOpen();
+
+        writeString(PathFormat.format(path, resolver));
+    }
+
+    /**
+     * Write a <code>NodeId</code>. Since the same node ids are likely to appear multiple times,
+     * only the first one will actually be literally appended, while all other reference the
+     * previous entry's index.
+     *
+     * @param nodeId node id
+     * @throws IOException if an I/O error occurs
+     */
+    public void writeNodeId(NodeId nodeId) throws IOException {
+        checkOpen();
+
+        if (nodeId == null) {
+            writeByte(Record.UUID_INDEX);
+            writeInt(-1);
+        } else {
+            int index = getOrCreateIndex(nodeId);
+            if (index != -1) {
+                writeByte(Record.UUID_INDEX);
+                writeInt(index);
+            } else {
+                writeByte(Record.UUID_LITERAL);
+                out.write(nodeId.getUUID().getRawBytes());
+            }
+        }
+    }
+
+    /**
+     * Write a <code>PropertyId</code>
+     *
+     * @param propertyId property id
+     * @throws IOException if an I/O error occurs
+     * @throws NoPrefixDeclaredException if the prefix is not declared
+     */
+    public void writePropertyId(PropertyId propertyId) throws IOException, NoPrefixDeclaredException {
+        checkOpen();
+
+        writeNodeId(propertyId.getParentId());
+        writeQName(propertyId.getName());
+    }
+
+    /**
+     * Write a <code>NodeTypeDef</code>
+     */
+    public void writeNodeTypeDef(NodeTypeDef ntd) throws IOException {
+        checkOpen();
+
+        StringWriter sw = new StringWriter();
+        CompactNodeTypeDefWriter writer = new CompactNodeTypeDefWriter(sw, resolver, true);
+        writer.write(ntd);
+        writer.close();
+
+        writeString(sw.toString());
+    }
+
+    /**
+     * Close this output.
+     *
+     * @throws IOException if an I/O error occurs
+     */
+    public void close() throws IOException {
+        checkOpen();
+
+        try {
+            out.close();
+        } finally {
+            closed = true;
+        }
+    }
+
+    /**
+     * Get a <code>NodeId</code>'s existing cache index, creating a new entry if necesary.
+     *
+     * @param nodeId nodeId to lookup
+     * @return cache index of existing entry or <code>-1</code> to indicate the entry was added
+     */
+    private int getOrCreateIndex(NodeId nodeId) {
+        int index = uuidIndex.indexOf(nodeId);
+        if (index == -1) {
+            uuidIndex.add(nodeId);
+        }
+        return index;
+    }
+
+    /**
+     * Check that this output is open, throw otherwise.
+     *
+     * @throws IllegalStateException if output is closed.
+     */
+    private void checkOpen() throws IllegalStateException {
+        if (closed) {
+            throw new IllegalStateException("Output closed.");
+        }
+    }
+}

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/default.ddl
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/default.ddl?view=auto&rev=495239
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/default.ddl (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/default.ddl Thu Jan 11 06:40:00 2007
@@ -0,0 +1,21 @@
+#  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.
+create table ${schemaObjectPrefix}JOURNAL (REVISION_ID BIGINT NOT NULL, REVISION_CREATOR varchar(255), REVISION_DATA varbinary)
+create unique index ${schemaObjectPrefix}JOURNAL_IDX on ${schemaObjectPrefix}JOURNAL (REVISION_ID)
+create table ${schemaObjectPrefix}GLOBAL_REVISION (REVISION_ID BIGINT NOT NULL)
+create unique index ${schemaObjectPrefix}GLOBAL_REVISION_IDX on ${schemaObjectPrefix}GLOBAL_REVISION (REVISION_ID)
+
+# Inserting the one and only revision counter record now helps avoiding race conditions
+insert into ${schemaObjectPrefix}GLOBAL_REVISION VALUES(0)

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/derby.ddl
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/derby.ddl?view=auto&rev=495239
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/derby.ddl (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/derby.ddl Thu Jan 11 06:40:00 2007
@@ -0,0 +1,21 @@
+#  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.
+create table ${schemaObjectPrefix}JOURNAL (REVISION_ID BIGINT NOT NULL, REVISION_CREATOR varchar(255), REVISION_DATA blob)
+create unique index ${schemaObjectPrefix}JOURNAL_IDX on ${schemaObjectPrefix}JOURNAL (REVISION_ID)
+create table ${schemaObjectPrefix}GLOBAL_REVISION (REVISION_ID BIGINT NOT NULL)
+create unique index ${schemaObjectPrefix}GLOBAL_REVISION_IDX on ${schemaObjectPrefix}GLOBAL_REVISION (REVISION_ID)
+
+# Inserting the one and only revision counter record now helps avoiding race conditions
+insert into ${schemaObjectPrefix}GLOBAL_REVISION VALUES(0)

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/h2.ddl
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/h2.ddl?view=auto&rev=495239
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/h2.ddl (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/h2.ddl Thu Jan 11 06:40:00 2007
@@ -0,0 +1,23 @@
+#  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.
+#
+#  DDL script for the H2 database engine (http://www.h2database.com)
+# 
+set max_length_inplace_lob 4096
+create table ${schemaObjectPrefix}JOURNAL (REVISION_ID bigint primary key, REVISION_CREATOR varchar(255), REVISION_DATA blob)
+create table ${schemaObjectPrefix}GLOBAL_REVISION (REVISION_ID bigint primary key)
+
+# Inserting the one and only revision counter record now helps avoiding race conditions 
+insert into ${schemaObjectPrefix}GLOBAL_REVISION VALUES(0)

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/oracle.ddl
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/oracle.ddl?view=auto&rev=495239
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/oracle.ddl (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/oracle.ddl Thu Jan 11 06:40:00 2007
@@ -0,0 +1,21 @@
+#  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.
+create table ${schemaObjectPrefix}JOURNAL (REVISION_ID number(20,0) NOT NULL, REVISION_CREATOR varchar(255), REVISION_DATA blob)
+create unique index ${schemaObjectPrefix}JOURNAL_IDX on ${schemaObjectPrefix}JOURNAL (REVISION_ID)
+create table ${schemaObjectPrefix}GLOBAL_REVISION (REVISION_ID number(20,0) NOT NULL)
+create unique index ${schemaObjectPrefix}GLOBAL_REVISION_IDX on ${schemaObjectPrefix}GLOBAL_REVISION (REVISION_ID)
+
+# Inserting the one and only revision counter record now helps avoiding race conditions
+insert into ${schemaObjectPrefix}GLOBAL_REVISION VALUES(0)