Index: src/main/java/org/apache/jackrabbit/core/cluster/ChangeLogRecord.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/cluster/ChangeLogRecord.java (revision 707626) +++ src/main/java/org/apache/jackrabbit/core/cluster/ChangeLogRecord.java (working copy) @@ -43,6 +43,11 @@ public class ChangeLogRecord extends ClusterRecord { /** + * Identifier: DATE + */ + static final char DATE_IDENTIFIER = 'D'; + + /** * Identifier: NODE. */ static final char NODE_IDENTIFIER = 'N'; @@ -78,6 +83,11 @@ private ChangeLog changes; /** + * The time when the changes happened. Milliseconds since January 1 1970 UTC. + */ + private long timestamp = System.currentTimeMillis(); + + /** * List of EventStates. */ private List events; @@ -96,7 +106,7 @@ * Create a new instance of this class. Used when serializing. * * @param changes changes - * @param list of EventStates + * @param events list of EventStates * @param record record * @param workspace workspace */ @@ -131,6 +141,9 @@ while (identifier != END_MARKER) { switch (identifier) { + case DATE_IDENTIFIER: + readTimestampRecord(); + break; case NODE_IDENTIFIER: readNodeRecord(); break; @@ -158,6 +171,15 @@ } /** + * Reads the timestamp record. + * + * @throws JournalException if an error occurs. + */ + private void readTimestampRecord() throws JournalException { + timestamp = record.readLong(); + } + + /** * Read a node record. * * @throws JournalException if an error occurs @@ -288,6 +310,7 @@ * {@inheritDoc} */ protected void doWrite() throws JournalException { + writeTimestampRecord(); Iterator deletedStates = changes.deletedStates(); while (deletedStates.hasNext()) { ItemState state = (ItemState) deletedStates.next(); @@ -324,6 +347,16 @@ } /** + * Writes the timestamp record. + * + * @throws JournalException if an error occurs. + */ + private void writeTimestampRecord() throws JournalException { + record.writeChar(DATE_IDENTIFIER); + record.writeLong(timestamp); + } + + /** * Write a node record * * @param operation operation @@ -397,9 +430,17 @@ * Return the events. * * @return events - * @return */ public List getEvents() { return Collections.unmodifiableList(events); } + + /** + * Returns the timestamp. + * + * @return the timestamp. + */ + public long getTimestamp() { + return timestamp; + } } Index: src/main/java/org/apache/jackrabbit/core/cluster/ClusterNode.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/cluster/ClusterNode.java (revision 707626) +++ src/main/java/org/apache/jackrabbit/core/cluster/ClusterNode.java (working copy) @@ -795,7 +795,8 @@ } } try { - listener.externalUpdate(record.getChanges(), record.getEvents()); + listener.externalUpdate(record.getChanges(), + record.getEvents(), record.getTimestamp()); } catch (RepositoryException e) { String msg = "Unable to deliver update events: " + e.getMessage(); log.error(msg); Index: src/main/java/org/apache/jackrabbit/core/cluster/ClusterRecordDeserializer.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/cluster/ClusterRecordDeserializer.java (revision 707626) +++ src/main/java/org/apache/jackrabbit/core/cluster/ClusterRecordDeserializer.java (working copy) @@ -40,6 +40,7 @@ case ChangeLogRecord.NODE_IDENTIFIER: case ChangeLogRecord.PROPERTY_IDENTIFIER: case ChangeLogRecord.EVENT_IDENTIFIER: + case ChangeLogRecord.DATE_IDENTIFIER: clusterRecord = new ChangeLogRecord(c, record, workspace); clusterRecord.read(); break; Index: src/main/java/org/apache/jackrabbit/core/cluster/Update.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/cluster/Update.java (revision 707626) +++ src/main/java/org/apache/jackrabbit/core/cluster/Update.java (working copy) @@ -57,4 +57,11 @@ */ List getEvents(); + /** + * Returns the timestamp whe this update occured. + * + * @return the timestamp whe this update occured. + */ + long getTimestamp(); + } Index: src/main/java/org/apache/jackrabbit/core/cluster/UpdateEventListener.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/cluster/UpdateEventListener.java (revision 707626) +++ src/main/java/org/apache/jackrabbit/core/cluster/UpdateEventListener.java (working copy) @@ -30,8 +30,10 @@ * * @param changes external changes containing only node and property ids. * @param events events to deliver + * @param timestamp when the change occured. * @throws RepositoryException if the update cannot be processed */ - void externalUpdate(ChangeLog changes, List events) throws RepositoryException; + void externalUpdate(ChangeLog changes, List events, long timestamp) + throws RepositoryException; } Index: src/main/java/org/apache/jackrabbit/core/journal/AppendRecord.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/journal/AppendRecord.java (revision 707626) +++ src/main/java/org/apache/jackrabbit/core/journal/AppendRecord.java (working copy) @@ -205,6 +205,20 @@ /** * {@inheritDoc} */ + public void writeLong(long n) throws JournalException { + checkOutput(); + + try { + dataOut.writeLong(n); + } catch (IOException e) { + String msg = "I/O error while writing long."; + throw new JournalException(msg, e); + } + } + + /** + * {@inheritDoc} + */ public void writeString(String s) throws JournalException { checkOutput(); @@ -386,6 +400,10 @@ throw unsupported(); } + public long readLong() throws JournalException { + throw unsupported(); + } + public String readString() throws JournalException { throw unsupported(); } Index: src/main/java/org/apache/jackrabbit/core/journal/ReadRecord.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/journal/ReadRecord.java (revision 707626) +++ src/main/java/org/apache/jackrabbit/core/journal/ReadRecord.java (working copy) @@ -154,6 +154,20 @@ /** * {@inheritDoc} */ + public long readLong() throws JournalException { + consumed = true; + + try { + return dataIn.readLong(); + } catch (IOException e) { + String msg = "I/O error while reading long."; + throw new JournalException(msg, e); + } + } + + /** + * {@inheritDoc} + */ public String readString() throws JournalException { consumed = true; @@ -241,6 +255,10 @@ throw unsupported(); } + public void writeLong(long n) throws JournalException { + throw unsupported(); + } + public void writeString(String s) throws JournalException { throw unsupported(); } Index: src/main/java/org/apache/jackrabbit/core/journal/Record.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/journal/Record.java (revision 707626) +++ src/main/java/org/apache/jackrabbit/core/journal/Record.java (working copy) @@ -81,6 +81,14 @@ int readInt() throws JournalException; /** + * Read a long from the underlying stream. + * + * @return long value. + * @throws JournalException if an error occurs + */ + long readLong() throws JournalException; + + /** * Read a string from the underlying stream. * * @return string or null @@ -177,6 +185,14 @@ void writeInt(int n) throws JournalException; /** + * Write a long to the underlying stream. + * + * @param n long + * @throws JournalException if an error occurs + */ + void writeLong(long n) throws JournalException; + + /** * Write a string to the underlying stream. * * @param s string, may be null Index: src/main/java/org/apache/jackrabbit/core/observation/EventImpl.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/observation/EventImpl.java (revision 707626) +++ src/main/java/org/apache/jackrabbit/core/observation/EventImpl.java (working copy) @@ -16,6 +16,8 @@ */ package org.apache.jackrabbit.core.observation; +import java.util.Calendar; + import org.apache.jackrabbit.api.observation.JackrabbitEvent; import org.apache.jackrabbit.core.NodeId; import org.apache.jackrabbit.core.SessionImpl; @@ -26,7 +28,6 @@ import org.slf4j.LoggerFactory; import javax.jcr.RepositoryException; -import javax.jcr.observation.Event; /** * Implementation of the {@link javax.jcr.observation.Event} and @@ -46,14 +47,14 @@ private final SessionImpl session; /** - * The ItemManager of the session. + * The shared {@link EventState} object. */ - //private final ItemManager itemMgr; + private final EventState eventState; /** - * The shared {@link EventState} object. + * The timestamp of this event. */ - private final EventState eventState; + private final long timestamp; /** * Cached String value of this Event instance. @@ -67,10 +68,12 @@ * @param session the session of the registerd EventListener * where this Event will be delivered to. * @param eventState the underlying EventState. + * @param timestamp the time when the change occured that caused this event. */ - EventImpl(SessionImpl session, EventState eventState) { + EventImpl(SessionImpl session, EventState eventState, long timestamp) { this.session = session; this.eventState = eventState; + this.timestamp = timestamp; } //---------------------------------------------------------------< Event > @@ -96,6 +99,15 @@ return eventState.getUserId(); } + /** + * {@inheritDoc} + */ + public Calendar getDate() { + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(timestamp); + return cal; + } + //-----------------------------------------------------------< EventImpl > /** Index: src/main/java/org/apache/jackrabbit/core/observation/EventStateCollection.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/observation/EventStateCollection.java (revision 707626) +++ src/main/java/org/apache/jackrabbit/core/observation/EventStateCollection.java (working copy) @@ -88,6 +88,11 @@ private final Path pathPrefix; /** + * Timestamp when this collection was created. + */ + private long timestamp = System.currentTimeMillis(); + + /** * Creates a new empty EventStateCollection. *

* Because the item state manager in {@link #createEventStates} may represent @@ -434,6 +439,22 @@ } /** + * @return the timestamp when this collection was created. + */ + public long getTimestamp() { + return timestamp; + } + + /** + * Sets a new timestamp for this collection. + * + * @param timestamp the new timestamp value. + */ + public void setTimestamp(long timestamp) { + this.timestamp = timestamp; + } + + /** * Returns an iterator over {@link EventState} instance. * * @return an iterator over {@link EventState} instance. Index: src/main/java/org/apache/jackrabbit/core/observation/FilteredEventIterator.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/observation/FilteredEventIterator.java (revision 707626) +++ src/main/java/org/apache/jackrabbit/core/observation/FilteredEventIterator.java (working copy) @@ -63,6 +63,11 @@ private long pos = 0; /** + * The timestamp when the events occured. + */ + private long timestamp; + + /** * Creates a new FilteredEventIterator. * * @param c an unmodifiable Collection of {@link javax.jcr.observation.Event}s. @@ -78,6 +83,7 @@ actualEvents = c.iterator(); this.filter = filter; this.denied = denied; + this.timestamp = c.getTimestamp(); fetchNext(); } @@ -158,7 +164,8 @@ // check denied set if (denied == null || !denied.contains(state.getTargetId())) { try { - next = filter.blocks(state) ? null : new EventImpl(filter.getSession(), state); + next = filter.blocks(state) ? null : new EventImpl( + filter.getSession(), state, timestamp); } catch (RepositoryException e) { log.error("Exception while applying filter.", e); } Index: src/main/java/org/apache/jackrabbit/core/RepositoryImpl.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/RepositoryImpl.java (revision 707626) +++ src/main/java/org/apache/jackrabbit/core/RepositoryImpl.java (working copy) @@ -2039,11 +2039,14 @@ /** * {@inheritDoc} */ - public void externalUpdate(ChangeLog external, List events) throws RepositoryException { + public void externalUpdate(ChangeLog external, + List events, + long timestamp) throws RepositoryException { try { EventStateCollection esc = new EventStateCollection( getObservationDispatcher(), null, null); esc.addAll(events); + esc.setTimestamp(timestamp); getItemStateProvider().externalUpdate(external, esc); } catch (IllegalStateException e) { Index: src/main/java/org/apache/jackrabbit/core/state/SharedItemStateManager.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/state/SharedItemStateManager.java (revision 707626) +++ src/main/java/org/apache/jackrabbit/core/state/SharedItemStateManager.java (working copy) @@ -514,6 +514,11 @@ private HashMap attributes; /** + * Timestamp when this update was created. + */ + private long timestamp = System.currentTimeMillis(); + + /** * Create a new instance of this class. */ public Update(ChangeLog local, EventStateCollectionFactory factory, @@ -837,6 +842,13 @@ public List getEvents() { return events.getEvents(); } + + /** + * {@inheritDoc} + */ + public long getTimestamp() { + return timestamp; + } } /** Index: src/main/java/org/apache/jackrabbit/core/version/VersionManagerImpl.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/version/VersionManagerImpl.java (revision 707626) +++ src/main/java/org/apache/jackrabbit/core/version/VersionManagerImpl.java (working copy) @@ -494,9 +494,11 @@ /** * {@inheritDoc} */ - public void externalUpdate(ChangeLog changes, List events) throws RepositoryException { + public void externalUpdate(ChangeLog changes, List events, long timestamp) + throws RepositoryException { EventStateCollection esc = getEscFactory().createEventStateCollection(null); esc.addAll(events); + esc.setTimestamp(timestamp); sharedStateMgr.externalUpdate(changes, esc); } Index: src/test/java/org/apache/jackrabbit/core/cluster/ClusterRecordTest.java =================================================================== --- src/test/java/org/apache/jackrabbit/core/cluster/ClusterRecordTest.java (revision 707626) +++ src/test/java/org/apache/jackrabbit/core/cluster/ClusterRecordTest.java (working copy) @@ -156,7 +156,7 @@ events.add(createEventState(p2, n2, Event.PROPERTY_REMOVED)); events.add(createEventState(n3, Event.NODE_REMOVED, "{}n3")); - UpdateEvent update = new UpdateEvent(changes, events); + UpdateEvent update = new UpdateEvent(changes, events, System.currentTimeMillis()); UpdateEventChannel channel = master.createUpdateChannel(DEFAULT_WORKSPACE); channel.updateCreated(update); Index: src/test/java/org/apache/jackrabbit/core/cluster/SimpleEventListener.java =================================================================== --- src/test/java/org/apache/jackrabbit/core/cluster/SimpleEventListener.java (revision 707626) +++ src/test/java/org/apache/jackrabbit/core/cluster/SimpleEventListener.java (working copy) @@ -28,7 +28,6 @@ import javax.jcr.nodetype.NoSuchNodeTypeException; import org.apache.jackrabbit.core.NodeId; -import org.apache.jackrabbit.core.cluster.LockEventListener; import org.apache.jackrabbit.core.nodetype.InvalidNodeTypeDefException; import org.apache.jackrabbit.core.nodetype.NodeTypeDef; import org.apache.jackrabbit.core.state.ChangeLog; @@ -413,10 +412,10 @@ /** * {@inheritDoc} */ - public void externalUpdate(ChangeLog changes, List events) + public void externalUpdate(ChangeLog changes, List events, long timestamp) throws RepositoryException { - clusterEvents.add(new UpdateEvent(changes, events)); + clusterEvents.add(new UpdateEvent(changes, events, timestamp)); } @@ -441,14 +440,21 @@ private final transient Map attributes = new HashMap(); /** + * Timestamp when the changes in this update event occured. + */ + private final long timestamp; + + /** * Create a new instance of this class. * * @param changes change log * @param events list of EventStates + * @param timestamp time when the changes in this event occured. */ - public UpdateEvent(ChangeLog changes, List events) { + public UpdateEvent(ChangeLog changes, List events, long timestamp) { this.changes = changes; this.events = events; + this.timestamp = timestamp; } /** @@ -472,6 +478,13 @@ /** * {@inheritDoc} */ + public long getTimestamp() { + return timestamp; + } + + /** + * {@inheritDoc} + */ public void setAttribute(String name, Object value) { attributes.put(name, value); } @@ -487,7 +500,7 @@ * {@inheritDoc} */ public int hashCode() { - return changes.hashCode() ^ events.hashCode(); + return changes.hashCode() ^ events.hashCode() ^ (int) (timestamp ^ (timestamp >>> 32)); } /** @@ -497,7 +510,8 @@ if (obj instanceof UpdateEvent) { UpdateEvent other = (UpdateEvent) obj; return SimpleEventListener.equals(changes, other.changes) && - SimpleEventListener.equals(events, other.events); + SimpleEventListener.equals(events, other.events) && + timestamp == other.timestamp; } return false; }