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 2008/10/07 14:43:57 UTC

svn commit: r702459 [1/2] - in /jackrabbit/trunk/jackrabbit-core/src: main/java/org/apache/jackrabbit/core/cluster/ main/java/org/apache/jackrabbit/core/journal/ main/java/org/apache/jackrabbit/core/state/ test/java/org/apache/jackrabbit/core/cluster/ ...

Author: dpfister
Date: Tue Oct  7 05:43:55 2008
New Revision: 702459

URL: http://svn.apache.org/viewvc?rev=702459&view=rev
Log:
JCR-1789 - Provide access to cluster records
- introduce classes that provide structured access to record contents
- add tests that verify correct serialization/deserialization
- change base class of ClusterException to Exception
- remove obsolete (Item|Node|Property)Operation classes

Added:
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/ChangeLogRecord.java   (with props)
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/ClusterRecord.java   (with props)
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/ClusterRecordDeserializer.java   (with props)
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/ClusterRecordProcessor.java   (with props)
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/DefaultClusterOperation.java   (with props)
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/LockRecord.java   (with props)
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/NamespaceRecord.java   (with props)
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/NodeTypeRecord.java   (with props)
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/cluster/ClusterRecordTest.java   (with props)
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/cluster/SimpleEventListener.java   (with props)
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/journal/MemoryJournal.java
      - copied, changed from r701066, jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/cluster/MemoryJournal.java
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/journal/MemoryRevision.java
      - copied, changed from r701066, jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/cluster/MemoryRevision.java
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/journal/TestAll.java   (with props)
Removed:
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/AbstractClusterOperation.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/ItemOperation.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/LockOperation.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/NodeAddedOperation.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/NodeDeletedOperation.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/NodeModifiedOperation.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/NodeOperation.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/PropertyAddedOperation.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/PropertyDeletedOperation.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/PropertyModifiedOperation.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/PropertyOperation.java
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/cluster/MemoryJournal.java
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/cluster/MemoryRevision.java
Modified:
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/ClusterException.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/ClusterNode.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/ClusterSession.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/Update.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/journal/ReadRecord.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/SharedItemStateManager.java
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/cluster/ClusterTest.java
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/cluster/SimpleClusterContext.java
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/cluster/TestAll.java
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/nodetype/xml/SimpleNamespaceRegistry.java

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/ChangeLogRecord.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/ChangeLogRecord.java?rev=702459&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/ChangeLogRecord.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/ChangeLogRecord.java Tue Oct  7 05:43:55 2008
@@ -0,0 +1,405 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import javax.jcr.Session;
+import javax.jcr.observation.Event;
+
+import org.apache.jackrabbit.core.NodeId;
+import org.apache.jackrabbit.core.journal.JournalException;
+import org.apache.jackrabbit.core.journal.Record;
+import org.apache.jackrabbit.core.observation.EventState;
+import org.apache.jackrabbit.core.state.ChangeLog;
+import org.apache.jackrabbit.core.state.ItemState;
+import org.apache.jackrabbit.core.state.NodeState;
+import org.apache.jackrabbit.core.state.PropertyState;
+import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.Path;
+
+/**
+ * Cluster record representing a workspace or version update.
+ */
+public class ChangeLogRecord extends ClusterRecord {
+
+    /**
+     * Identifier: NODE.
+     */
+    static final char NODE_IDENTIFIER = 'N';
+
+    /**
+     * Identifier: PROPERTY.
+     */
+    static final char PROPERTY_IDENTIFIER = 'P';
+
+    /**
+     * Identifier: EVENT.
+     */
+    static final char EVENT_IDENTIFIER = 'E';
+
+    /**
+     * Operation type: added.
+     */
+    private static final int ADDED = 1;
+
+    /**
+     * Operation type: modified.
+     */
+    private static final int MODIFIED = 2;
+
+    /**
+     * Operation type: deleted.
+     */
+    private static final int DELETED = 3;
+
+    /**
+     * Changes.
+     */
+    private ChangeLog changes;
+
+    /**
+     * List of <code>EventState</code>s.
+     */
+    private List events;
+
+    /**
+     * First identifier read.
+     */
+    private int identifier;
+
+    /**
+     * Last used session for event sources.
+     */
+    private Session lastSession;
+
+    /**
+     * Create a new instance of this class. Used when serializing.
+     *
+     * @param changes changes
+     * @param list of <code>EventState</code>s
+     * @param record record
+     * @param workspace workspace
+     */
+    public ChangeLogRecord(ChangeLog changes, List events,
+                           Record record, String workspace) {
+        super(record, workspace);
+
+        this.changes = changes;
+        this.events = events;
+    }
+
+    /**
+     * Create a new instance of this class. Used when deserializing.
+     *
+     * @param identifier first identifier read
+     * @param record record
+     * @param workspace workspace
+     */
+    ChangeLogRecord(int identifier, Record record, String workspace) {
+        super(record, workspace);
+
+        this.identifier = identifier;
+        this.changes = new ChangeLog();
+        this.events = new ArrayList();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    protected void doRead() throws JournalException {
+        int identifier = this.identifier;
+
+        while (identifier != END_MARKER) {
+            switch (identifier) {
+            case NODE_IDENTIFIER:
+                readNodeRecord();
+                break;
+            case PROPERTY_IDENTIFIER:
+                readPropertyRecord();
+                break;
+            case EVENT_IDENTIFIER:
+                readEventRecord();
+                break;
+            default:
+                String msg = "Unknown identifier: " + identifier;
+                throw new JournalException(msg);
+            }
+            identifier = record.readChar();
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    protected void readEndMarker() throws JournalException {
+        // This record type uses the end marker itself to indicate that
+        // no more node/property/event records are available, so
+        // do not try read it twice
+    }
+
+    /**
+     * Read a node record.
+     *
+     * @throws JournalException if an error occurs
+     */
+    private void readNodeRecord() throws JournalException {
+        int operation = record.readByte();
+        NodeState state = new NodeState(record.readNodeId(), null, null,
+                ItemState.STATUS_NEW, false);
+
+        apply(operation, state);
+    }
+
+    /**
+     * Read a property record.
+     *
+     * @throws JournalException if an error occurs
+     */
+    private void readPropertyRecord() throws JournalException {
+        int operation = record.readByte();
+        PropertyState state = new PropertyState(record.readPropertyId(),
+                ItemState.STATUS_NEW, false);
+
+        apply(operation, state);
+    }
+
+    /**
+     * Apply an item state to the internal change log.
+     *
+     * @param operation operation
+     * @param state item state
+     * @throws JournalException if an error occurs
+     */
+    private void apply(int operation, ItemState state) throws JournalException {
+        switch (operation) {
+        case ADDED:
+            state.setStatus(ItemState.STATUS_EXISTING);
+            changes.added(state);
+            break;
+        case DELETED:
+            state.setStatus(ItemState.STATUS_EXISTING_REMOVED);
+            changes.deleted(state);
+            break;
+        case MODIFIED:
+            state.setStatus(ItemState.STATUS_EXISTING_MODIFIED);
+            changes.modified(state);
+            break;
+        default:
+            String msg = "Unknown item operation: " + operation;
+            throw new JournalException(msg);
+        }
+    }
+
+    /**
+     * Read an event record.
+     *
+     * @throws JournalException if an error occurs
+     */
+    private void readEventRecord() throws JournalException {
+        int type = record.readByte();
+        NodeId parentId = record.readNodeId();
+        Path parentPath = record.readPath();
+        NodeId childId = record.readNodeId();
+        Path.Element childRelPath = record.readPathElement();
+        Name ntName = record.readQName();
+
+        Set mixins = new HashSet();
+        int mixinCount = record.readInt();
+        for (int i = 0; i < mixinCount; i++) {
+            mixins.add(record.readQName());
+        }
+        String userId = record.readString();
+        events.add(createEventState(type, parentId, parentPath, childId,
+                childRelPath, ntName, mixins, userId));
+    }
+
+    /**
+     * Create an event state.
+     *
+     * @param type event type
+     * @param parentId parent id
+     * @param parentPath parent path
+     * @param childId child id
+     * @param childRelPath child relative path
+     * @param ntName node type name
+     * @param mixins mixins
+     * @param userId user id
+     * @return event state
+     */
+    private EventState createEventState(int type, NodeId parentId, Path parentPath,
+                                        NodeId childId, Path.Element childRelPath,
+                                        Name ntName, Set mixins, String userId) {
+        switch (type) {
+        case Event.NODE_ADDED:
+            return EventState.childNodeAdded(parentId, parentPath, childId, childRelPath,
+                    ntName, mixins, getOrCreateSession(userId), true);
+        case Event.NODE_REMOVED:
+            return EventState.childNodeRemoved(parentId, parentPath, childId, childRelPath,
+                    ntName, mixins, getOrCreateSession(userId), true);
+        case Event.PROPERTY_ADDED:
+            return EventState.propertyAdded(parentId, parentPath, childRelPath,
+                    ntName, mixins, getOrCreateSession(userId), true);
+        case Event.PROPERTY_CHANGED:
+            return EventState.propertyChanged(parentId, parentPath, childRelPath,
+                    ntName, mixins, getOrCreateSession(userId), true);
+        case Event.PROPERTY_REMOVED:
+            return EventState.propertyRemoved(parentId, parentPath, childRelPath,
+                    ntName, mixins, getOrCreateSession(userId), true);
+        default:
+            String msg = "Unexpected event type: " + type;
+            throw new IllegalArgumentException(msg);
+        }
+    }
+
+    /**
+     * Return a session matching a certain user id.
+     *
+     * @param userId user id
+     * @return session
+     */
+    private Session getOrCreateSession(String userId) {
+        if (lastSession == null || !lastSession.getUserID().equals(userId)) {
+            lastSession = new ClusterSession(userId);
+        }
+        return lastSession;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    protected void doWrite() throws JournalException {
+        Iterator deletedStates = changes.deletedStates();
+        while (deletedStates.hasNext()) {
+            ItemState state = (ItemState) deletedStates.next();
+            if (state.isNode()) {
+                writeNodeRecord(DELETED, (NodeState) state);
+            } else {
+                writePropertyRecord(DELETED, (PropertyState) state);
+            }
+        }
+        Iterator modifiedStates = changes.modifiedStates();
+        while (modifiedStates.hasNext()) {
+            ItemState state = (ItemState) modifiedStates.next();
+            if (state.isNode()) {
+                writeNodeRecord(MODIFIED, (NodeState) state);
+            } else {
+                writePropertyRecord(MODIFIED, (PropertyState) state);
+            }
+        }
+        Iterator addedStates = changes.addedStates();
+        while (addedStates.hasNext()) {
+            ItemState state = (ItemState) addedStates.next();
+            if (state.isNode()) {
+                writeNodeRecord(ADDED, (NodeState) state);
+            } else {
+                writePropertyRecord(ADDED, (PropertyState) state);
+            }
+        }
+
+        Iterator iter = events.iterator();
+        while (iter.hasNext()) {
+            EventState event = (EventState) iter.next();
+            writeEventRecord(event);
+        }
+    }
+
+    /**
+     * Write a node record
+     *
+     * @param operation operation
+     * @param state node state
+     * @throws JournalException if an error occurs
+     */
+    private void writeNodeRecord(int operation, NodeState state)
+            throws JournalException {
+
+        record.writeChar(NODE_IDENTIFIER);
+        record.writeByte(operation);
+        record.writeNodeId(state.getNodeId());
+    }
+
+    /**
+     * Write a property record
+     *
+     * @param operation operation
+     * @param state property state
+     * @throws JournalException if an error occurs
+     */
+    private void writePropertyRecord(int operation, PropertyState state)
+            throws JournalException {
+
+        record.writeChar(PROPERTY_IDENTIFIER);
+        record.writeByte(operation);
+        record.writePropertyId(state.getPropertyId());
+    }
+
+    /**
+     * Write an event record
+     *
+     * @param event event state
+     * @throws JournalException if an error occurs
+     */
+    private void writeEventRecord(EventState event) throws JournalException {
+        record.writeChar(EVENT_IDENTIFIER);
+        record.writeByte(event.getType());
+        record.writeNodeId(event.getParentId());
+        record.writePath(event.getParentPath());
+        record.writeNodeId(event.getChildId());
+        record.writePathElement(event.getChildRelPath());
+        record.writeQName(event.getNodeType());
+
+        Set mixins = event.getMixinNames();
+        record.writeInt(mixins.size());
+        Iterator iter = mixins.iterator();
+        while (iter.hasNext()) {
+            record.writeQName((Name) iter.next());
+        }
+        record.writeString(event.getUserId());
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void process(ClusterRecordProcessor processor) {
+        processor.process(this);
+    }
+
+    /**
+     * Return the changes.
+     *
+     * @return changes
+     */
+    public ChangeLog getChanges() {
+        return changes;
+    }
+
+    /**
+     * Return the events.
+     *
+     * @return events
+     * @return
+     */
+    public List getEvents() {
+        return Collections.unmodifiableList(events);
+    }
+}

Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/ChangeLogRecord.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/ChangeLogRecord.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision Rev Url

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/ClusterException.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/ClusterException.java?rev=702459&r1=702458&r2=702459&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/ClusterException.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/ClusterException.java Tue Oct  7 05:43:55 2008
@@ -16,12 +16,10 @@
  */
 package org.apache.jackrabbit.core.cluster;
 
-import org.apache.jackrabbit.BaseException;
-
 /**
  * The <code>ClusterException</code> signals an error within a cluster operation.
  */
-public class ClusterException extends BaseException {
+public class ClusterException extends Exception {
 
     /**
      * Constructs a new instance of this class with the specified detail

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/ClusterNode.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/ClusterNode.java?rev=702459&r1=702458&r2=702459&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/ClusterNode.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/ClusterNode.java Tue Oct  7 05:43:55 2008
@@ -29,40 +29,29 @@
 import org.apache.jackrabbit.core.journal.Record;
 import org.apache.jackrabbit.core.journal.JournalException;
 import org.apache.jackrabbit.core.journal.InstanceRevision;
+import org.apache.jackrabbit.core.journal.RecordProducer;
 import org.apache.jackrabbit.core.nodetype.InvalidNodeTypeDefException;
 import org.apache.jackrabbit.core.nodetype.NodeTypeDef;
-import org.apache.jackrabbit.core.observation.EventState;
-import org.apache.jackrabbit.core.observation.EventStateCollection;
 import org.apache.jackrabbit.core.state.ChangeLog;
-import org.apache.jackrabbit.core.state.ItemState;
-import org.apache.jackrabbit.core.state.NodeState;
-import org.apache.jackrabbit.core.state.PropertyState;
-import org.apache.jackrabbit.spi.Name;
-import org.apache.jackrabbit.spi.Path;
 import org.apache.jackrabbit.uuid.UUID;
 
 import EDU.oswego.cs.dl.util.concurrent.Mutex;
 
 import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.observation.Event;
 
 import java.io.File;
 import java.io.IOException;
-import java.util.List;
-import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Collection;
-import java.util.Iterator;
-import java.util.Set;
-import java.util.HashSet;
 
 /**
  * Default clustered node implementation.
  */
 public class ClusterNode implements Runnable,
-        NamespaceEventChannel, NodeTypeEventChannel, RecordConsumer  {
+        NamespaceEventChannel, NodeTypeEventChannel, RecordConsumer,
+        ClusterRecordProcessor  {
 
     /**
      * System property specifying a node id to use.
@@ -95,26 +84,6 @@
     private static final int STOPPED = 2;
 
     /**
-     * Bit indicating this is a registration operation.
-     */
-    private static final int NTREG_REGISTER = 0;
-
-    /**
-     * Bit indicating this is a reregistration operation.
-     */
-    private static final int NTREG_REREGISTER = (1 << 30);
-
-    /**
-     * Bit indicating this is an unregistration operation.
-     */
-    private static final int NTREG_UNREGISTER = (1 << 31);
-
-    /**
-     * Mask used in node type registration operations.
-     */
-    private static final int NTREG_MASK = (NTREG_REREGISTER | NTREG_UNREGISTER);
-
-    /**
      * Logger.
      */
     private static Logger log = LoggerFactory.getLogger(ClusterNode.class);
@@ -180,24 +149,14 @@
     private InstanceRevision instanceRevision;
 
     /**
-     * Workspace name used when consuming records.
-     */
-    private String workspace;
-
-    /**
-     * Change log used when consuming records.
+     * Our record producer.
      */
-    private ChangeLog changeLog;
+    private RecordProducer producer;
 
     /**
-     * List of recorded events; used when consuming records.
+     * Record deserializer.
      */
-    private List events;
-
-    /**
-     * Last used session for event sources.
-     */
-    private Session lastSession;
+    private ClusterRecordDeserializer deserializer = new ClusterRecordDeserializer();
 
     /**
      * Initialize this cluster node.
@@ -227,6 +186,7 @@
             journal.init(clusterNodeId, clusterContext.getNamespaceResolver());
             instanceRevision = journal.getInstanceRevision();
             journal.register(this);
+            producer = journal.getProducer(PRODUCER_ID);
         } catch (ConfigurationException e) {
             throw new ClusterException(e.getMessage(), e.getCause());
         } catch (JournalException e) {
@@ -408,14 +368,12 @@
             log.info("not started: namespace operation ignored.");
             return;
         }
-        Record record = null;
+        ClusterRecord record = null;
         boolean succeeded = false;
 
         try {
-            record = journal.getProducer(PRODUCER_ID).append();
-            record.writeString(null);
-            write(record, oldPrefix, newPrefix, uri);
-            record.writeChar('\0');
+            record = new NamespaceRecord(oldPrefix, newPrefix, uri, producer.append());
+            record.write();
             record.update();
             setRevision(record.getRevision());
             succeeded = true;
@@ -446,14 +404,12 @@
             log.info("not started: nodetype operation ignored.");
             return;
         }
-        Record record = null;
+        ClusterRecord record = null;
         boolean succeeded = false;
 
         try {
-            record = journal.getProducer(PRODUCER_ID).append();
-            record.writeString(null);
-            write(record, ntDefs, true);
-            record.writeChar('\0');
+            record = new NodeTypeRecord(ntDefs, true, producer.append());
+            record.write();
             record.update();
             setRevision(record.getRevision());
             succeeded = true;
@@ -478,14 +434,12 @@
             log.info("not started: nodetype operation ignored.");
             return;
         }
-        Record record = null;
+        ClusterRecord record = null;
         boolean succeeded = false;
 
         try {
-            record = journal.getProducer(PRODUCER_ID).append();
-            record.writeString(null);
-            write(record, ntDef);
-            record.writeChar('\0');
+            record = new NodeTypeRecord(ntDef, producer.append());
+            record.write();
             record.update();
             setRevision(record.getRevision());
             succeeded = true;
@@ -510,14 +464,12 @@
             log.info("not started: nodetype operation ignored.");
             return;
         }
-        Record record = null;
+        ClusterRecord record = null;
         boolean succeeded = false;
 
         try {
-            record = journal.getProducer(PRODUCER_ID).append();
-            record.writeString(null);
-            write(record, qnames, false);
-            record.writeChar('\0');
+            record = new NodeTypeRecord(qnames, false, producer.append());
+            record.write();
             record.update();
             setRevision(record.getRevision());
             succeeded = true;
@@ -574,7 +526,7 @@
                 return;
             }
             try {
-                Record record = journal.getProducer(PRODUCER_ID).append();
+                Record record = producer.append();
                 update.setAttribute(ATTRIBUTE_RECORD, record);
             } catch (JournalException e) {
                 String msg = "Unable to create log entry.";
@@ -600,14 +552,14 @@
                 return;
             }
 
-            EventStateCollection events = update.getEvents();
+            List events = update.getEvents();
             ChangeLog changes = update.getChanges();
             boolean succeeded = false;
 
             try {
-                record.writeString(workspace);
-                write(record, changes, events);
-                record.writeChar('\0');
+                ChangeLogRecord clr = new ChangeLogRecord(changes, events,
+                        record, workspace);
+                clr.write();
                 succeeded = true;
             } catch (JournalException e) {
                 String msg = "Unable to create log entry: " + e.getMessage();
@@ -710,9 +662,9 @@
                 return null;
             }
             try {
-                Record record = journal.getProducer(PRODUCER_ID).append();
-                return new LockOperation(ClusterNode.this, workspace, record,
-                        nodeId, deep, owner);
+                ClusterRecord record = new LockRecord(nodeId, deep, owner,
+                        producer.append(), workspace);
+                return new DefaultClusterOperation(ClusterNode.this, record);
             } catch (JournalException e) {
                 String msg = "Unable to create log entry: " + e.getMessage();
                 log.error(msg);
@@ -733,9 +685,9 @@
                 return null;
             }
             try {
-                Record record = journal.getProducer(PRODUCER_ID).append();
-                return new LockOperation(ClusterNode.this, workspace, record,
-                        nodeId);
+                ClusterRecord record = new LockRecord(nodeId, producer.append(),
+                        workspace);
+                return new DefaultClusterOperation(ClusterNode.this, record);
             } catch (JournalException e) {
                 String msg = "Unable to create log entry: " + e.getMessage();
                 log.error(msg);
@@ -758,175 +710,60 @@
         }
     }
 
-    /**
-     * Invoked when a record starts.
-     *
-     * @param workspace workspace, may be <code>null</code>
-     */
-    private void start(String workspace) {
-        this.workspace = workspace;
-
-        changeLog = new ChangeLog();
-        events = new ArrayList();
-    }
-
-    /**
-     * Process an update operation.
-     *
-     * @param operation operation to process
-     */
-    private void process(ItemOperation operation) {
-        operation.apply(changeLog);
-    }
+    //-------------------------------------------------------< RecordConsumer >
 
     /**
-     * Process an event.
-     *
-     * @param event event
+     * {@inheritDoc}
      */
-    private void process(EventState event) {
-        events.add(event);
+    public String getId() {
+        return PRODUCER_ID;
     }
 
     /**
-     * Process a lock operation.
-     *
-     * @param nodeId node id
-     * @param isDeep flag indicating whether lock is deep
-     * @param owner lock owner
+     * {@inheritDoc}
      */
-    private void process(NodeId nodeId, boolean isDeep, String owner) {
-        LockEventListener listener = (LockEventListener) wspLockListeners.get(workspace);
-        if (listener == null) {
-            try {
-                clusterContext.lockEventsReady(workspace);
-            } catch (RepositoryException e) {
-                String msg = "Unable to make lock listener for workspace " +
-                        workspace + " online: " + e.getMessage();
-                log.warn(msg);
-            }
-            listener = (LockEventListener) wspLockListeners.get(workspace);
-            if (listener ==  null) {
-                String msg = "Lock channel unavailable for workspace: " + workspace;
-                log.error(msg);
-                return;
-            }
-        }
+    public long getRevision() {
         try {
-            listener.externalLock(nodeId, isDeep, owner);
-        } catch (RepositoryException e) {
-            String msg = "Unable to deliver lock event: " + e.getMessage();
-            log.error(msg);
+            return instanceRevision.get();
+        } catch (JournalException e) {
+            log.warn("Unable to return current revision.", e);
+            return Long.MAX_VALUE;
         }
     }
 
     /**
-     * Process an unlock operation.
-     *
-     * @param nodeId node id
+     * {@inheritDoc}
      */
-    private void process(NodeId nodeId) {
-        LockEventListener listener = (LockEventListener) wspLockListeners.get(workspace);
-        if (listener == null) {
-            try {
-                clusterContext.lockEventsReady(workspace);
-            } catch (RepositoryException e) {
-                String msg = "Unable to make lock listener for workspace " +
-                        workspace + " online: " + e.getMessage();
-                log.warn(msg);
-            }
-            listener = (LockEventListener) wspLockListeners.get(workspace);
-            if (listener ==  null) {
-                String msg = "Lock channel unavailable for workspace: " + workspace;
-                log.error(msg);
-                return;
-            }
-        }
-        try {
-            listener.externalUnlock(nodeId);
-        } catch (RepositoryException e) {
-            String msg = "Unable to deliver lock event: " + e.getMessage();
-            log.error(msg);
-        }
-    }
+    public void consume(Record record) {
+        log.info("Processing revision: " + record.getRevision());
 
-    /**
-     * Process a namespace operation.
-     *
-     * @param oldPrefix old prefix. if <code>null</code> this is a fresh mapping
-     * @param newPrefix new prefix. if <code>null</code> this is an unmap operation
-     * @param uri uri to map prefix to
-     */
-    private void process(String oldPrefix, String newPrefix, String uri) {
-        if (namespaceListener == null) {
-            String msg = "Namespace listener unavailable.";
-            log.error(msg);
-            return;
-        }
         try {
-            namespaceListener.externalRemap(oldPrefix, newPrefix, uri);
-        } catch (RepositoryException e) {
-            String msg = "Unable to deliver namespace operation: " + e.getMessage();
-            log.error(msg);
+            deserializer.deserialize(record).process(this);
+        } catch (JournalException e) {
+            String msg = "Unable to read revision '" + record.getRevision() + "'.";
+            log.error(msg, e);
         }
     }
 
     /**
-     * Process one or more node type registrations.
-     *
-     * @param c collection of node type definitions, if this is a register
-     *          operation; collection of <code>Name</code>s if this is
-     *          an unregister operation
-     * @param register <code>true</code>, if this is a register operation;
-     *                 <code>false</code> otherwise
+     * {@inheritDoc}
      */
-    private void process(Collection c, boolean register) {
-        if (nodeTypeListener == null) {
-            String msg = "NodeType listener unavailable.";
-            log.error(msg);
-            return;
-        }
+    public void setRevision(long revision) {
         try {
-            if (register) {
-                nodeTypeListener.externalRegistered(c);
-            } else {
-                nodeTypeListener.externalUnregistered(c);
-            }
-        } catch (InvalidNodeTypeDefException e) {
-            String msg = "Unable to deliver node type operation: " + e.getMessage();
-            log.error(msg);
-        } catch (RepositoryException e) {
-            String msg = "Unable to deliver node type operation: " + e.getMessage();
-            log.error(msg);
+            instanceRevision.set(revision);
+        } catch (JournalException e) {
+            log.warn("Unable to set current revision to " + revision + ".", e);
         }
     }
 
-    /**
-     * Process a node type re-registration.
-     *
-     * @param ntDef node type definition
-     */
-    private void process(NodeTypeDef ntDef) {
-        if (nodeTypeListener == null) {
-            String msg = "NodeType listener unavailable.";
-            log.error(msg);
-            return;
-        }
-        try {
-            nodeTypeListener.externalReregistered(ntDef);
-        } catch (InvalidNodeTypeDefException e) {
-            String msg = "Unable to deliver node type operation: " + e.getMessage();
-            log.error(msg);
-        } catch (RepositoryException e) {
-            String msg = "Unable to deliver node type operation: " + e.getMessage();
-            log.error(msg);
-        }
-    }
+    //--------------------------------------------------- ClusterRecordProcessor
 
     /**
-     * Invoked when a record ends.
+     * {@inheritDoc}
      */
-    private void end() {
+    public void process(ChangeLogRecord record) {
+        String workspace = record.getWorkspace();
+
         UpdateEventListener listener = null;
         if (workspace != null) {
             listener = (UpdateEventListener) wspUpdateListeners.get(workspace);
@@ -955,128 +792,44 @@
             }
         }
         try {
-            listener.externalUpdate(changeLog, events);
+            listener.externalUpdate(record.getChanges(), record.getEvents());
         } catch (RepositoryException e) {
             String msg = "Unable to deliver update events: " + e.getMessage();
             log.error(msg);
         }
     }
 
-    //-------------------------------------------------------< RecordConsumer >
-
     /**
      * {@inheritDoc}
      */
-    public String getId() {
-        return PRODUCER_ID;
-    }
+    public void process(LockRecord record) {
+        String workspace = record.getWorkspace();
 
-    /**
-     * {@inheritDoc}
-     */
-    public long getRevision() {
-        try {
-            return instanceRevision.get();
-        } catch (JournalException e) {
-            log.warn("Unable to return current revision.", e);
-            return Long.MAX_VALUE;
+        LockEventListener listener = (LockEventListener) wspLockListeners.get(workspace);
+        if (listener == null) {
+            try {
+                clusterContext.lockEventsReady(workspace);
+            } catch (RepositoryException e) {
+                String msg = "Unable to make lock listener for workspace " +
+                        workspace + " online: " + e.getMessage();
+                log.warn(msg);
+            }
+            listener = (LockEventListener) wspLockListeners.get(workspace);
+            if (listener ==  null) {
+                String msg = "Lock channel unavailable for workspace: " + workspace;
+                log.error(msg);
+                return;
+            }
         }
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public void consume(Record record) {
-        log.info("Processing revision: " + record.getRevision());
-
-        String workspace = null;
-
         try {
-            workspace = record.readString();
-            start(workspace);
-
-            for (;;) {
-                char c = record.readChar();
-                if (c == '\0') {
-                    break;
-                }
-                if (c == 'N') {
-                    NodeOperation operation = NodeOperation.create(record.readByte());
-                    operation.setId(record.readNodeId());
-                    process(operation);
-                } else if (c == 'P') {
-                    PropertyOperation operation = PropertyOperation.create(record.readByte());
-                    operation.setId(record.readPropertyId());
-                    process(operation);
-                } else if (c == 'E') {
-                    int type = record.readByte();
-                    NodeId parentId = record.readNodeId();
-                    Path parentPath = record.readPath();
-                    NodeId childId = record.readNodeId();
-                    Path.Element childRelPath = record.readPathElement();
-                    Name ntName = record.readQName();
-
-                    Set mixins = new HashSet();
-                    int mixinCount = record.readInt();
-                    for (int i = 0; i < mixinCount; i++) {
-                        mixins.add(record.readQName());
-                    }
-                    String userId = record.readString();
-                    process(createEventState(type, parentId, parentPath, childId,
-                            childRelPath, ntName, mixins, userId));
-                } else if (c == 'L') {
-                    NodeId nodeId = record.readNodeId();
-                    boolean isLock = record.readBoolean();
-                    if (isLock) {
-                        boolean isDeep = record.readBoolean();
-                        String owner = record.readString();
-                        process(nodeId, isDeep, owner);
-                    } else {
-                        process(nodeId);
-                    }
-                } else if (c == 'S') {
-                    String oldPrefix = record.readString();
-                    String newPrefix = record.readString();
-                    String uri = record.readString();
-                    process(oldPrefix, newPrefix, uri);
-                } else if (c == 'T') {
-                    int size = record.readInt();
-                    int opcode = size & NTREG_MASK;
-                    size &= ~NTREG_MASK;
-
-                    switch (opcode) {
-                        case NTREG_REGISTER:
-                            HashSet ntDefs = new HashSet();
-                            for (int i = 0; i < size; i++) {
-                                ntDefs.add(record.readNodeTypeDef());
-                            }
-                            process(ntDefs, true);
-                            break;
-                        case NTREG_REREGISTER:
-                            process(record.readNodeTypeDef());
-                            break;
-                        case NTREG_UNREGISTER:
-                            HashSet ntNames = new HashSet();
-                            for (int i = 0; i < size; i++) {
-                                ntNames.add(record.readQName());
-                            }
-                            process(ntNames, false);
-                            break;
-                        default:
-                            throw new IllegalArgumentException("Unknown opcode: " + opcode);
-                    }
-                } else {
-                    throw new IllegalArgumentException("Unknown entry type: " + c);
-                }
+            if (record.isLock()) {
+                listener.externalLock(record.getNodeId(), record.isDeep(),
+                        record.getUserId());
+            } else {
+                listener.externalUnlock(record.getNodeId());
             }
-            end();
-
-        } catch (JournalException e) {
-            String msg = "Unable to read revision '" + record.getRevision() + "'.";
-            log.error(msg, e);
-        } catch (IllegalArgumentException e) {
-            String msg = "Error while processing revision " +
-                    record.getRevision() + ": " + e.getMessage();
+        } catch (RepositoryException e) {
+            String msg = "Unable to deliver lock event: " + e.getMessage();
             log.error(msg);
         }
     }
@@ -1084,187 +837,53 @@
     /**
      * {@inheritDoc}
      */
-    public void setRevision(long revision) {
-        try {
-            instanceRevision.set(revision);
-        } catch (JournalException e) {
-            log.warn("Unable to set current revision to " + revision + ".", e);
+    public void process(NamespaceRecord record) {
+        if (namespaceListener == null) {
+            String msg = "Namespace listener unavailable.";
+            log.error(msg);
+            return;
         }
-    }
-
-    /**
-     * Create an event state.
-     *
-     * @param type event type
-     * @param parentId parent id
-     * @param parentPath parent path
-     * @param childId child id
-     * @param childRelPath child relative path
-     * @param ntName ndoe type name
-     * @param userId user id
-     * @return event
-     */
-    private EventState createEventState(int type, NodeId parentId, Path parentPath,
-                                        NodeId childId, Path.Element childRelPath,
-                                        Name ntName, Set mixins, String userId) {
-        switch (type) {
-            case Event.NODE_ADDED:
-                return EventState.childNodeAdded(parentId, parentPath, childId, childRelPath,
-                        ntName, mixins, getOrCreateSession(userId), true);
-            case Event.NODE_REMOVED:
-                return EventState.childNodeRemoved(parentId, parentPath, childId, childRelPath,
-                        ntName, mixins, getOrCreateSession(userId), true);
-            case Event.PROPERTY_ADDED:
-                return EventState.propertyAdded(parentId, parentPath, childRelPath,
-                        ntName, mixins, getOrCreateSession(userId), true);
-            case Event.PROPERTY_CHANGED:
-                return EventState.propertyChanged(parentId, parentPath, childRelPath,
-                        ntName, mixins, getOrCreateSession(userId), true);
-            case Event.PROPERTY_REMOVED:
-                return EventState.propertyRemoved(parentId, parentPath, childRelPath,
-                        ntName, mixins, getOrCreateSession(userId), true);
-            default:
-                String msg = "Unexpected event type: " + type;
-                throw new IllegalArgumentException(msg);
+        try {
+            namespaceListener.externalRemap(record.getOldPrefix(),
+                    record.getNewPrefix(), record.getUri());
+        } catch (RepositoryException e) {
+            String msg = "Unable to deliver namespace operation: " + e.getMessage();
+            log.error(msg);
         }
     }
 
     /**
-     * Return a session matching a certain user id.
-     *
-     * @param userId user id
-     * @return session
+     * {@inheritDoc}
      */
-    private Session getOrCreateSession(String userId) {
-        if (lastSession == null || !lastSession.getUserID().equals(userId)) {
-            lastSession = new ClusterSession(userId);
-        }
-        return lastSession;
-    }
-
-    //-----------------------------------------------< Record writing methods >
-
-    private static void write(Record record, ChangeLog changeLog, EventStateCollection esc)
-            throws JournalException {
-
-        Iterator deletedStates = changeLog.deletedStates();
-        while (deletedStates.hasNext()) {
-            ItemState state = (ItemState) deletedStates.next();
-            if (state.isNode()) {
-                write(record, NodeDeletedOperation.create((NodeState) state));
-            } else {
-                write(record, PropertyDeletedOperation.create((PropertyState) state));
-            }
-        }
-        Iterator modifiedStates = changeLog.modifiedStates();
-        while (modifiedStates.hasNext()) {
-            ItemState state = (ItemState) modifiedStates.next();
-            if (state.isNode()) {
-                write(record, NodeModifiedOperation.create((NodeState) state));
-            } else {
-                write(record, PropertyModifiedOperation.create((PropertyState) state));
-            }
-        }
-        Iterator addedStates = changeLog.addedStates();
-        while (addedStates.hasNext()) {
-            ItemState state = (ItemState) addedStates.next();
-            if (state.isNode()) {
-                write(record, NodeAddedOperation.create((NodeState) state));
-            } else {
-                write(record, PropertyAddedOperation.create((PropertyState) state));
-            }
-        }
-
-        Iterator events = esc.getEvents().iterator();
-        while (events.hasNext()) {
-            EventState event = (EventState) events.next();
-            write(record, event);
-        }
-    }
-
-    private static void write(Record record, String oldPrefix, String newPrefix, String uri)
-            throws JournalException {
-
-        record.writeChar('S');
-        record.writeString(oldPrefix);
-        record.writeString(newPrefix);
-        record.writeString(uri);
-    }
-
-    private static void write(Record record, Collection c, boolean register)
-            throws JournalException {
-
-        record.writeChar('T');
-
-        int size = c.size();
-        if (!register) {
-            size |= NTREG_UNREGISTER;
+    public void process(NodeTypeRecord record) {
+        if (nodeTypeListener == null) {
+            String msg = "NodeType listener unavailable.";
+            log.error(msg);
+            return;
         }
-        record.writeInt(size);
-
-        Iterator iter = c.iterator();
-        while (iter.hasNext()) {
-            if (register) {
-                record.writeNodeTypeDef((NodeTypeDef) iter.next());
-            } else {
-                record.writeQName((Name) iter.next());
+        Collection coll = record.getCollection();
+        try {
+            switch (record.getOperation()) {
+            case NodeTypeRecord.REGISTER:
+                nodeTypeListener.externalRegistered(coll);
+                break;
+            case NodeTypeRecord.UNREGISTER:
+                nodeTypeListener.externalUnregistered(coll);
+                break;
+            case NodeTypeRecord.REREGISTER:
+                NodeTypeDef ntd = (NodeTypeDef) coll.iterator().next();
+                nodeTypeListener.externalReregistered(ntd);
+                break;
             }
+        } catch (InvalidNodeTypeDefException e) {
+            String msg = "Unable to deliver node type operation: " + e.getMessage();
+            log.error(msg);
+        } catch (RepositoryException e) {
+            String msg = "Unable to deliver node type operation: " + e.getMessage();
+            log.error(msg);
         }
     }
 
-    private static void write(Record record, NodeTypeDef ntDef)
-            throws JournalException {
-
-        record.writeChar('T');
-
-        int size = 1;
-        size |= NTREG_REREGISTER;
-        record.writeInt(size);
-
-        record.writeNodeTypeDef(ntDef);
-    }
-
-    private static void write(Record record, PropertyOperation operation)
-            throws JournalException {
-
-        record.writeChar('P');
-        record.writeByte(operation.getOperationType());
-        record.writePropertyId(operation.getId());
-    }
-
-    private static void write(Record record, NodeOperation operation)
-            throws JournalException {
-
-        record.writeChar('N');
-        record.writeByte(operation.getOperationType());
-        record.writeNodeId(operation.getId());
-    }
-
-    /**
-     * Log an event. Subclass responsibility.
-     *
-     * @param event event to log
-     */
-    private static void write(Record record, EventState event)
-            throws JournalException {
-
-        record.writeChar('E');
-        record.writeByte(event.getType());
-        record.writeNodeId(event.getParentId());
-        record.writePath(event.getParentPath());
-        record.writeNodeId(event.getChildId());
-        record.writePathElement(event.getChildRelPath());
-        record.writeQName(event.getNodeType());
-
-        Set mixins = event.getMixinNames();
-        record.writeInt(mixins.size());
-        Iterator iter = mixins.iterator();
-        while (iter.hasNext()) {
-            record.writeQName((Name) iter.next());
-        }
-        record.writeString(event.getUserId());
-    }
-
     /**
      * Invoked when a cluster operation has ended. If <code>successful</code>,
      * attempts to fill the journal record and update it, otherwise cancels
@@ -1275,16 +894,13 @@
      *                   the journal record should be updated;
      *                   <code>false</code> to revoke changes
      */
-    public void ended(AbstractClusterOperation operation, boolean successful) {
-        Record record = operation.getRecord();
+    public void ended(DefaultClusterOperation operation, boolean successful) {
+        ClusterRecord record = operation.getRecord();
         boolean succeeded = false;
 
         try {
             if (successful) {
-                record = operation.getRecord();
-                record.writeString(operation.getWorkspace());
-                operation.write();
-                record.writeChar('\0');
+                record.write();
                 record.update();
                 setRevision(record.getRevision());
                 succeeded = true;

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/ClusterRecord.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/ClusterRecord.java?rev=702459&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/ClusterRecord.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/ClusterRecord.java Tue Oct  7 05:43:55 2008
@@ -0,0 +1,160 @@
+/*
+ * 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.journal.JournalException;
+import org.apache.jackrabbit.core.journal.Record;
+
+/**
+ * Base cluster record. Used to serialize and deserialize cluster operations
+ * using journal records.
+ */
+public abstract class ClusterRecord {
+
+    /**
+     * End marker.
+     */
+    protected static final char END_MARKER = '\0';
+
+    /**
+     * Journal record.
+     */
+    protected final Record record;
+
+    /**
+     * Workspace name.
+     */
+    protected String workspace;
+
+    /**
+     * Create a new instance of this class.
+     *
+     * @param record journal record
+     * @param workspace workspace
+     */
+    protected ClusterRecord(Record record, String workspace) {
+        this.record = record;
+        this.workspace = workspace;
+    }
+
+    /**
+     * Create a new instance of this class. Used for records that do not
+     * have a workspace name.
+     *
+     * @param record journal record
+     */
+    protected ClusterRecord(Record record) {
+        this(record, null);
+    }
+
+    /**
+     * Deserialize this record.
+     *
+     * @throws JournalException if an error occurs
+     */
+    public final void read() throws JournalException {
+        doRead();
+
+        readEndMarker();
+    }
+
+    /**
+     * Deserialize this record. Subclass responsibility.
+     *
+     * @throws JournalException if an error occurs
+     */
+    protected abstract void doRead() throws JournalException;
+
+    /**
+     * Serialize this record.
+     *
+     * @throws JournalException if an error occurs
+     */
+    public final void write() throws JournalException {
+        record.writeString(workspace);
+
+        doWrite();
+
+        record.writeChar(END_MARKER);
+    }
+
+    /**
+     * Serialize this record. Subclass responsibility.
+     *
+     * @throws JournalException if an error occurs
+     */
+    protected abstract void doWrite() throws JournalException;
+
+    /**
+     * Read end marker.
+     *
+     * @throws JournalException if an error occurs
+     */
+    protected void readEndMarker() throws JournalException {
+        char c = record.readChar();
+        if (c != END_MARKER) {
+            String msg = "Expected end marker, found: " + c;
+            throw new JournalException(msg);
+        }
+    }
+
+    /**
+     * Process this record, calling the appropriate <code>process</code>
+     * method.
+     *
+     * @param processor processor
+     */
+    public abstract void process(ClusterRecordProcessor processor);
+
+    /**
+     * Update the record.
+     *
+     * @throws JournalException if an error occurs
+     * @see Record#update()
+     */
+    public void update() throws JournalException {
+        record.update();
+    }
+
+    /**
+     * Cancel updating the record.
+     *
+     * @see Record#cancelUpdate()
+     */
+    public void cancelUpdate() {
+        record.cancelUpdate();
+    }
+
+    /**
+     * Return the record revision.
+     *
+     * @return record revision
+     * @see Record#getRevision()
+     */
+    public long getRevision() {
+        return record.getRevision();
+    }
+
+    /**
+     * Return the workspace name.
+     *
+     * @return workspace name
+     */
+    public String getWorkspace() {
+        return workspace;
+    }
+}

Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/ClusterRecord.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/ClusterRecord.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision Rev Url

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/ClusterRecordDeserializer.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/ClusterRecordDeserializer.java?rev=702459&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/ClusterRecordDeserializer.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/ClusterRecordDeserializer.java Tue Oct  7 05:43:55 2008
@@ -0,0 +1,64 @@
+/*
+ * 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.journal.JournalException;
+import org.apache.jackrabbit.core.journal.Record;
+
+/**
+ * Deserialize a record written by a <code>ClusterNode</code>.
+ */
+public class ClusterRecordDeserializer {
+
+    /**
+     * Deserialize a cluster record.
+     *
+     * @param record basic record containing a cluster record
+     * @return deserialized cluster record
+     * @throws JournalException if an error occurs
+     */
+    public ClusterRecord deserialize(Record record) throws JournalException {
+        ClusterRecord clusterRecord;
+
+        String workspace = record.readString();
+        char c = record.readChar();
+        switch (c) {
+        case ChangeLogRecord.NODE_IDENTIFIER:
+        case ChangeLogRecord.PROPERTY_IDENTIFIER:
+        case ChangeLogRecord.EVENT_IDENTIFIER:
+            clusterRecord = new ChangeLogRecord(c, record, workspace);
+            clusterRecord.read();
+            break;
+        case LockRecord.IDENTIFIER:
+            clusterRecord = new LockRecord(record, workspace);
+            clusterRecord.read();
+            break;
+        case NamespaceRecord.IDENTIFIER:
+            clusterRecord = new NamespaceRecord(record);
+            clusterRecord.read();
+            break;
+        case NodeTypeRecord.IDENTIFIER:
+            clusterRecord = new NodeTypeRecord(record);
+            clusterRecord.read();
+            break;
+        default:
+            String msg = "Unknown record identifier: " + c;
+            throw new JournalException(msg);
+        }
+        return clusterRecord;
+    }
+}

Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/ClusterRecordDeserializer.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/ClusterRecordDeserializer.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision Rev Url

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/ClusterRecordProcessor.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/ClusterRecordProcessor.java?rev=702459&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/ClusterRecordProcessor.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/ClusterRecordProcessor.java Tue Oct  7 05:43:55 2008
@@ -0,0 +1,55 @@
+/*
+ * 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;
+
+/**
+ * Cluster record processor. Pass an implementation of this interface to a
+ * <code>ClusterRecord</code> and it will call back the appropriate
+ * <code>process</code> method.
+ *
+ * @see ClusterRecord#process(ClusterRecordProcessor)
+ */
+public interface ClusterRecordProcessor {
+
+    /**
+     * Process a change log record.
+     *
+     * @param record change log record
+     */
+    public void process(ChangeLogRecord record);
+
+    /**
+     * Process a lock record.
+     *
+     * @param record lock record
+     */
+    public void process(LockRecord record);
+
+    /**
+     * Process a namespace record.
+     *
+     * @param record namespace record
+     */
+    public void process(NamespaceRecord record);
+
+    /**
+     * Process a node type record
+     *
+     * @param record node type record
+     */
+    public void process(NodeTypeRecord record);
+}

Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/ClusterRecordProcessor.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/ClusterRecordProcessor.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision Rev Url

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/ClusterSession.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/ClusterSession.java?rev=702459&r1=702458&r2=702459&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/ClusterSession.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/ClusterSession.java Tue Oct  7 05:43:55 2008
@@ -30,8 +30,8 @@
 import java.io.OutputStream;
 
 /**
- * Represents the session that has made some changes on another node in the cluster. The only method currently
- * implemented is {@link #getUserID()}.
+ * Represents the session that has made some changes on another node in the
+ * cluster. The only method currently implemented is {@link #getUserID()}.
  */
 class ClusterSession implements Session {
 
@@ -271,4 +271,22 @@
      */
     public void removeLockToken(String s) {
     }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean equals(Object obj) {
+        if (obj instanceof ClusterSession) {
+            ClusterSession other = (ClusterSession) obj;
+            return userId.equals(other.userId);
+        }
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int hashCode() {
+        return userId.hashCode();
+    }
 }

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/DefaultClusterOperation.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/DefaultClusterOperation.java?rev=702459&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/DefaultClusterOperation.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/DefaultClusterOperation.java Tue Oct  7 05:43:55 2008
@@ -0,0 +1,62 @@
+/*
+ * 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;
+
+/**
+ * Default cluster operation implementation.
+ */
+public class DefaultClusterOperation implements ClusterOperation {
+
+    /**
+     * Cluster node.
+     */
+    private final ClusterNode clusterNode;
+
+    /**
+     * Cluster record.
+     */
+    private final ClusterRecord record;
+
+    /**
+     * Create an instance of this class.
+     *
+     * @param clusterNode cluster node
+     * @param record cluster record
+     */
+    public DefaultClusterOperation(ClusterNode clusterNode,
+                                   ClusterRecord record) {
+
+        this.clusterNode = clusterNode;
+        this.record = record;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void ended(boolean successful) {
+        clusterNode.ended(this, successful);
+    }
+
+    /**
+     * Return the record.
+     *
+     * @return the record
+     */
+    public ClusterRecord getRecord() {
+        return record;
+    }
+}

Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/DefaultClusterOperation.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/DefaultClusterOperation.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision Rev Url

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/LockRecord.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/LockRecord.java?rev=702459&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/LockRecord.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/LockRecord.java Tue Oct  7 05:43:55 2008
@@ -0,0 +1,168 @@
+/*
+ * 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.journal.JournalException;
+import org.apache.jackrabbit.core.journal.Record;
+
+/**
+ * Cluster record representing a lock or unlock operation.
+ */
+public class LockRecord extends ClusterRecord {
+
+    /**
+     * Identifier: LOCK.
+     */
+    static final char IDENTIFIER = 'L';
+
+    /**
+     * Node id.
+     */
+    private NodeId nodeId;
+
+    /**
+     * Flag indicating whether this is a lock or an unlock.
+     */
+    private boolean isLock;
+
+    /**
+     * Flag indicating whether the lock is deep.
+     */
+    private boolean isDeep;
+
+    /**
+     * User id.
+     */
+    private String userId;
+
+    /**
+     * Create a new instance of this class. Used when a lock operation should
+     * be serialized.
+     *
+     * @param nodeId node id
+     * @param isDeep flag indicating whether the lock is deep
+     * @param userId user id
+     * @param record journal record
+     * @param workspace workspace
+     */
+    public LockRecord(NodeId nodeId, boolean isDeep, String userId,
+                      Record record, String workspace) {
+        super(record, workspace);
+
+        this.nodeId = nodeId;
+        this.isLock = true;
+        this.isDeep = isDeep;
+        this.userId = userId;
+    }
+
+    /**
+     * Create a new instance of this class. Used when an unlock operation should
+     * be serialized.
+     *
+     * @param nodeId node id
+     * @param record journal record
+     * @param workspace workspace
+     */
+    public LockRecord(NodeId nodeId, Record record, String workspace) {
+        super(record, workspace);
+
+        this.nodeId = nodeId;
+        this.isLock = false;
+    }
+
+    /**
+     * Create a new instance of this class. Used when a deserializing either a
+     * lock or an unlock operation.
+     *
+     * @param record journal record
+     * @param workspace workspace
+     */
+    LockRecord(Record record, String workspace) {
+        super(record, workspace);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    protected void doRead() throws JournalException {
+        nodeId = record.readNodeId();
+        isLock = record.readBoolean();
+        if (isLock) {
+            isDeep = record.readBoolean();
+            userId = record.readString();
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    protected void doWrite() throws JournalException {
+        record.writeChar(IDENTIFIER);
+        record.writeNodeId(nodeId);
+        record.writeBoolean(isLock);
+        if (isLock) {
+            record.writeBoolean(isDeep);
+            record.writeString(userId);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void process(ClusterRecordProcessor processor) {
+        processor.process(this);
+    }
+
+    /**
+     * Return the node id.
+     *
+     * @return node id
+     */
+    public NodeId getNodeId() {
+        return nodeId;
+    }
+
+    /**
+     * Return a flag indicating whether this is a lock or an unlock operation.
+     *
+     * @return <code>true</code> if this is a lock operation;
+     *         <code>false</code> if this is an unlock operation
+     */
+    public boolean isLock() {
+        return isLock;
+    }
+
+    /**
+     * Return a flag indicating whether the lock is deep.
+     *
+     * @return <code>true</code> if the lock is deep;
+     *         <code>false</code> otherwise
+     */
+    public boolean isDeep() {
+        return isDeep;
+    }
+
+    /**
+     * Return the user id associated with the lock operation.
+     *
+     * @return user id
+     */
+    public String getUserId() {
+        return userId;
+    }
+}

Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/LockRecord.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/LockRecord.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision Rev Url

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/NamespaceRecord.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/NamespaceRecord.java?rev=702459&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/NamespaceRecord.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/NamespaceRecord.java Tue Oct  7 05:43:55 2008
@@ -0,0 +1,126 @@
+/*
+ * 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.journal.JournalException;
+import org.apache.jackrabbit.core.journal.Record;
+
+/**
+ * Cluster record representing a namespace registration, reregistration or
+ * unregistration.
+ */
+public class NamespaceRecord extends ClusterRecord {
+
+    /**
+     * Identifier: NAMESPACE.
+     */
+    static final char IDENTIFIER = 'S';
+
+    /**
+     * Old prefix.
+     */
+    private String oldPrefix;
+
+    /**
+     * New prefix.
+     */
+    private String newPrefix;
+
+    /**
+     * URI.
+     */
+    private String uri;
+
+    /**
+     * Create a new instance of this class. Used when serializing a namespace
+     * operation.
+     *
+     * @param oldPrefix old prefix
+     * @param newPrefix new prefix
+     * @param uri URI
+     * @param record journal record
+     */
+    public NamespaceRecord(String oldPrefix, String newPrefix, String uri,
+                           Record record) {
+        super(record);
+
+        this.oldPrefix = oldPrefix;
+        this.newPrefix = newPrefix;
+        this.uri = uri;
+    }
+
+    /**
+     * Create a new instance of this class. Used when deserializing.
+     *
+     * @param record journal record
+     */
+    NamespaceRecord(Record record) {
+        super(record);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    protected void doRead() throws JournalException {
+        oldPrefix = record.readString();
+        newPrefix = record.readString();
+        uri = record.readString();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    protected void doWrite() throws JournalException {
+        record.writeChar(IDENTIFIER);
+        record.writeString(oldPrefix);
+        record.writeString(newPrefix);
+        record.writeString(uri);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void process(ClusterRecordProcessor processor) {
+        processor.process(this);
+    }
+
+    /**
+     * Return the old prefix.
+     *
+     * @return old prefix
+     */
+    public String getOldPrefix() {
+        return oldPrefix;
+    }
+
+    /**
+     * Return the new prefix.
+     *
+     * @return new prefix
+     */
+    public String getNewPrefix() {
+        return newPrefix;
+    }
+
+    /**
+     * Return the URI.
+     * @return URI
+     */
+    public String getUri() {
+        return uri;
+    }
+}

Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/NamespaceRecord.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/NamespaceRecord.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision Rev Url

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/NodeTypeRecord.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/NodeTypeRecord.java?rev=702459&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/NodeTypeRecord.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/NodeTypeRecord.java Tue Oct  7 05:43:55 2008
@@ -0,0 +1,222 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+
+import org.apache.jackrabbit.core.journal.JournalException;
+import org.apache.jackrabbit.core.journal.Record;
+import org.apache.jackrabbit.core.nodetype.NodeTypeDef;
+import org.apache.jackrabbit.spi.Name;
+
+/**
+ * Cluster record representing a node type registration, re-registration or
+ * unregistration.
+ */
+public class NodeTypeRecord extends ClusterRecord {
+
+    /**
+     * Operation type: registration.
+     */
+    public static final int REGISTER = 1;
+
+    /**
+     * Operation type: re-registration.
+     */
+    public static final int REREGISTER = 2;
+
+    /**
+     * Operation type: unregistration.
+     */
+    public static final int UNREGISTER = 3;
+
+    /**
+     * Identifier: NODETYPE.
+     */
+    static final char IDENTIFIER = 'T';
+
+    /**
+     * Bit indicating this is a registration operation.
+     */
+    private static final int NTREG_REGISTER = 0;
+
+    /**
+     * Bit indicating this is a reregistration operation.
+     */
+    private static final int NTREG_REREGISTER = (1 << 30);
+
+    /**
+     * Bit indicating this is an unregistration operation.
+     */
+    private static final int NTREG_UNREGISTER = (1 << 31);
+
+    /**
+     * Mask used in node type registration operations.
+     */
+    private static final int NTREG_MASK = (NTREG_REREGISTER | NTREG_UNREGISTER);
+
+    /**
+     * Operation type.
+     */
+    private int operation;
+
+    /**
+     * Collection of node type defintions or node type names.
+     */
+    private Collection collection;
+
+    /**
+     * Create a new instance of this class. Used when serializing a node type
+     * registration or unregistration.
+     *
+     * @param collection collection of node types definitions or node type names
+     * @param isRegister <code>true</code> if this is a registration;
+     *                   <code>false</code> if this is a unregistration
+     * @param record journal record
+     */
+    public NodeTypeRecord(Collection collection, boolean isRegister, Record record) {
+        super(record);
+
+        this.collection = collection;
+        this.operation = isRegister ? REGISTER : UNREGISTER;
+    }
+
+    /**
+     * Create a new instance of this class. Used when serializing a node type
+     * re-registration.
+     *
+     * @param ntDef node type definition
+     * @param record journal record
+     */
+    public NodeTypeRecord(NodeTypeDef ntDef, Record record) {
+        super(record);
+
+        this.collection = new ArrayList();
+        this.collection.add(ntDef);
+        this.operation = REREGISTER;
+    }
+
+    /**
+     * Create a new instance of this class. Used when deseralizing a node type
+     * registration, re-registration or unregistration.
+     *
+     * @param record journal record
+     */
+    NodeTypeRecord(Record record) {
+        super(record);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    protected void doRead() throws JournalException {
+        int size = record.readInt();
+        int opcode = size & NTREG_MASK;
+        size &= ~NTREG_MASK;
+
+        switch (opcode) {
+        case NTREG_REGISTER:
+            operation = REGISTER;
+            collection = new HashSet();
+            for (int i = 0; i < size; i++) {
+                collection.add(record.readNodeTypeDef());
+            }
+            break;
+        case NTREG_REREGISTER:
+            operation = REREGISTER;
+            collection = new HashSet();
+            collection.add(record.readNodeTypeDef());
+            break;
+        case NTREG_UNREGISTER:
+            operation = UNREGISTER;
+            collection = new HashSet();
+            for (int i = 0; i < size; i++) {
+                collection.add(record.readQName());
+            }
+            break;
+        default:
+            String msg = "Unknown opcode: " + opcode;
+            throw new JournalException(msg);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    protected void doWrite() throws JournalException {
+        record.writeChar(IDENTIFIER);
+
+        int size = collection.size();
+        size |= getBitMask();
+        record.writeInt(size);
+
+        Iterator iter = collection.iterator();
+        while (iter.hasNext()) {
+            if (operation == UNREGISTER) {
+                record.writeQName((Name) iter.next());
+            } else {
+                record.writeNodeTypeDef((NodeTypeDef) iter.next());
+            }
+        }
+    }
+
+    /**
+     * Return the bit mask associated with an operation type.
+     *
+     * @return bit mask
+     */
+    private int getBitMask() {
+        switch (operation) {
+        case REGISTER:
+            return NTREG_REGISTER;
+        case UNREGISTER:
+            return NTREG_UNREGISTER;
+        case REREGISTER:
+            return NTREG_REREGISTER;
+        }
+        return 0;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void process(ClusterRecordProcessor processor) {
+        processor.process(this);
+    }
+
+    /**
+     * Return the operation type.
+     * @return <code>REGISTER</code>, <code>REREGISTER</code> or
+     *         <code>UNREGISTER</code>
+     */
+    public int getOperation() {
+        return operation;
+    }
+
+    /**
+     * Return the collection of node type definitions or node type names.
+     *
+     * @return unmodifiable collection
+     */
+    public Collection getCollection() {
+        return Collections.unmodifiableCollection(collection);
+    }
+}

Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/NodeTypeRecord.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/NodeTypeRecord.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision Rev Url

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/Update.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/Update.java?rev=702459&r1=702458&r2=702459&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/Update.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/Update.java Tue Oct  7 05:43:55 2008
@@ -16,8 +16,9 @@
  */
 package org.apache.jackrabbit.core.cluster;
 
+import java.util.List;
+
 import org.apache.jackrabbit.core.state.ChangeLog;
-import org.apache.jackrabbit.core.observation.EventStateCollection;
 
 /**
  * Update operation passed in <code>UpdateEventChannel</code>.
@@ -51,7 +52,9 @@
     /**
      * Return the collection of events this update operation will
      * generate.
+     *
+     * @return collection of <code>EventState</code>s
      */
-    EventStateCollection getEvents();
+    List getEvents();
 
 }

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/journal/ReadRecord.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/journal/ReadRecord.java?rev=702459&r1=702458&r2=702459&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/journal/ReadRecord.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/journal/ReadRecord.java Tue Oct  7 05:43:55 2008
@@ -26,7 +26,7 @@
 /**
  * Record used for reading.
  */
-class ReadRecord extends AbstractRecord {
+public class ReadRecord extends AbstractRecord {
 
     /**
      * This record's journal id.

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/SharedItemStateManager.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/SharedItemStateManager.java?rev=702459&r1=702458&r2=702459&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/SharedItemStateManager.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/SharedItemStateManager.java Tue Oct  7 05:43:55 2008
@@ -834,8 +834,8 @@
         /**
          * {@inheritDoc}
          */
-        public EventStateCollection getEvents() {
-            return events;
+        public List getEvents() {
+            return events.getEvents();
         }
     }
 



Re: svn commit: r702459 [1/2] - in /jackrabbit/trunk/jackrabbit-core/src: main/java/org/apache/jackrabbit/core/cluster/ main/java/org/apache/jackrabbit/core/journal/ main/java/org/apache/jackrabbit/core/state/ test/java/org/apache/jackrabbit/core/cluster/ ...

Posted by Julian Reschke <ju...@gmx.de>.
dpfister@apache.org wrote:
> Author: dpfister
> Date: Tue Oct  7 05:43:55 2008
> New Revision: 702459
> 
> URL: http://svn.apache.org/viewvc?rev=702459&view=rev
> Log:
> JCR-1789 - Provide access to cluster records
> - introduce classes that provide structured access to record contents
> - add tests that verify correct serialization/deserialization
> - change base class of ClusterException to Exception
> - remove obsolete (Item|Node|Property)Operation classes
> ...

I think something is fishy with the change; I'm getting on 2 out of 2 
machines (XPSP3) test failures (full log attached):


java.io.IOException: Unable to delete file: 
target\repository_for_test\revision.log at 
org.apache.commons.io.FileUtils.forceDelete(FileUtils.java:1390) at 
org.apache.commons.io.FileUtils.cleanDirectory(FileUtils.java:1044) at 
org.apache.commons.io.FileUtils.deleteDirectory(FileUtils.java:977) at 
org.apache.jackrabbit.core.journal.FileJournalTest.tearDown(FileJournalTest.java:67) 
at junit.framework.TestCase.runBare(TestCase.java:130) at 
junit.framework.TestResult$1.protect(TestResult.java:106) at 
junit.framework.TestResult.runProtected(TestResult.java:124) at 
junit.framework.TestResult.run(TestResult.java:109) at 
junit.framework.TestCase.run(TestCase.java:118) at 
junit.framework.TestSuite.runTest(TestSuite.java:208) at 
junit.framework.TestSuite.run(TestSuite.java:203) at 
junit.framework.TestSuite.runTest(TestSuite.java:208) at 
junit.framework.TestSuite.run(TestSuite.java:203) at 
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at 
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) 
at 
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) 
at java.lang.reflect.Method.invoke(Method.java:585) at 
org.apache.maven.surefire.junit.JUnitTestSet.execute(JUnitTestSet.java:213) 
at 
org.apache.maven.surefire.suite.AbstractDirectoryTestSuite.executeTestSet(AbstractDirectoryTestSuite.java:140) 
at 
org.apache.maven.surefire.suite.AbstractDirectoryTestSuite.execute(AbstractDirectoryTestSuite.java:127) 
at org.apache.maven.surefire.Surefire.run(Surefire.java:177) at 
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at 
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) 
at 
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) 
at java.lang.reflect.Method.invoke(Method.java:585) at 
org.apache.maven.surefire.booter.SurefireBooter.runSuitesInProcess(SurefireBooter.java:338) 
at 
org.apache.maven.surefire.booter.SurefireBooter.main(SurefireBooter.java:997)

BR, Julian