Return-Path: X-Original-To: apmail-jackrabbit-oak-commits-archive@minotaur.apache.org Delivered-To: apmail-jackrabbit-oak-commits-archive@minotaur.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id C3793109C9 for ; Mon, 27 Jan 2014 15:27:15 +0000 (UTC) Received: (qmail 80903 invoked by uid 500); 27 Jan 2014 15:25:21 -0000 Delivered-To: apmail-jackrabbit-oak-commits-archive@jackrabbit.apache.org Received: (qmail 80749 invoked by uid 500); 27 Jan 2014 15:25:20 -0000 Mailing-List: contact oak-commits-help@jackrabbit.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: oak-dev@jackrabbit.apache.org Delivered-To: mailing list oak-commits@jackrabbit.apache.org Received: (qmail 80646 invoked by uid 99); 27 Jan 2014 15:25:14 -0000 Received: from athena.apache.org (HELO athena.apache.org) (140.211.11.136) by apache.org (qpsmtpd/0.29) with ESMTP; Mon, 27 Jan 2014 15:25:14 +0000 X-ASF-Spam-Status: No, hits=-2000.0 required=5.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.4] (HELO eris.apache.org) (140.211.11.4) by apache.org (qpsmtpd/0.29) with ESMTP; Mon, 27 Jan 2014 15:25:12 +0000 Received: from eris.apache.org (localhost [127.0.0.1]) by eris.apache.org (Postfix) with ESMTP id 1C22A238899C; Mon, 27 Jan 2014 15:24:52 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r1561710 - in /jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation: ./ filter/ Date: Mon, 27 Jan 2014 15:24:51 -0000 To: oak-commits@jackrabbit.apache.org From: jukka@apache.org X-Mailer: svnmailer-1.0.9 Message-Id: <20140127152452.1C22A238899C@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org Author: jukka Date: Mon Jan 27 15:24:51 2014 New Revision: 1561710 URL: http://svn.apache.org/r1561710 Log: OAK-1332: Large number of changes to the same node can fill observation queue Merge EventIterable and JcrListener to EventGenerator to simplify the code and to use just a single list of events as the event queue. Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/filter/VisibleFilter.java (with props) Removed: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/EventIterable.java jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/JcrListener.java Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/ChangeProcessor.java jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/EventContext.java jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/EventGenerator.java jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/filter/EventFilter.java Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/ChangeProcessor.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/ChangeProcessor.java?rev=1561710&r1=1561709&r2=1561710&view=diff ============================================================================== --- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/ChangeProcessor.java (original) +++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/ChangeProcessor.java Mon Jan 27 15:24:51 2014 @@ -20,13 +20,11 @@ package org.apache.jackrabbit.oak.plugin import static com.google.common.base.Preconditions.checkState; -import java.util.Iterator; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import javax.jcr.observation.Event; import javax.jcr.observation.EventListener; import com.google.common.util.concurrent.Monitor; @@ -163,15 +161,12 @@ public class ChangeProcessor implements EventFilter acFilter = new ACFilter(previousRoot, root, permissionProvider, basePath); ImmutableTree beforeTree = getTree(previousRoot, basePath); ImmutableTree afterTree = getTree(root, basePath); - EventContext context = new EventContext(namePathMapper, info); - EventIterable events = new EventIterable( - beforeTree.getNodeState(), afterTree.getNodeState(), - Filters.all(userFilter, acFilter), - new JcrListener(context, beforeTree, afterTree)); - Iterator iterator = events.iterator(); - if (iterator.hasNext() && runningMonitor.enterIf(running)) { + EventGenerator events = new EventGenerator( + namePathMapper, info, beforeTree, afterTree, + Filters.all(userFilter, acFilter)); + if (events.hasNext() && runningMonitor.enterIf(running)) { try { - eventListener.onEvent(new EventIteratorAdapter(iterator)); + eventListener.onEvent(new EventIteratorAdapter(events)); } finally { runningMonitor.leave(); } Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/EventContext.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/EventContext.java?rev=1561710&r1=1561709&r2=1561710&view=diff ============================================================================== --- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/EventContext.java (original) +++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/EventContext.java Mon Jan 27 15:24:51 2014 @@ -92,4 +92,4 @@ final class EventContext { return info.toString(); } -} \ No newline at end of file +} Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/EventGenerator.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/EventGenerator.java?rev=1561710&r1=1561709&r2=1561710&view=diff ============================================================================== --- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/EventGenerator.java (original) +++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/EventGenerator.java Mon Jan 27 15:24:51 2014 @@ -19,174 +19,279 @@ package org.apache.jackrabbit.oak.plugins.observation; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.Lists.newArrayList; +import static com.google.common.collect.Lists.newLinkedList; +import static javax.jcr.observation.Event.NODE_ADDED; +import static javax.jcr.observation.Event.NODE_MOVED; +import static javax.jcr.observation.Event.NODE_REMOVED; +import static javax.jcr.observation.Event.PROPERTY_ADDED; +import static javax.jcr.observation.Event.PROPERTY_CHANGED; +import static javax.jcr.observation.Event.PROPERTY_REMOVED; +import static org.apache.jackrabbit.oak.api.Type.STRING; +import static org.apache.jackrabbit.oak.core.AbstractTree.OAK_CHILD_ORDER; import static org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.MISSING_NODE; +import static org.apache.jackrabbit.oak.spi.state.MoveDetector.SOURCE_PATH; + +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; import javax.annotation.Nonnull; +import javax.jcr.observation.Event; +import javax.jcr.observation.EventIterator; -import org.apache.jackrabbit.oak.api.CommitFailedException; import org.apache.jackrabbit.oak.api.PropertyState; +import org.apache.jackrabbit.oak.commons.PathUtils; +import org.apache.jackrabbit.oak.core.ImmutableTree; +import org.apache.jackrabbit.oak.namepath.NamePathMapper; import org.apache.jackrabbit.oak.plugins.observation.filter.EventFilter; -import org.apache.jackrabbit.oak.spi.state.MoveValidator; +import org.apache.jackrabbit.oak.plugins.observation.filter.Filters; +import org.apache.jackrabbit.oak.plugins.observation.filter.VisibleFilter; +import org.apache.jackrabbit.oak.spi.commit.CommitInfo; import org.apache.jackrabbit.oak.spi.state.NodeState; +import org.apache.jackrabbit.oak.spi.state.NodeStateDiff; + +import com.google.common.collect.ImmutableMap; /** - * {@link EventFilter filter} and report changes between node states to the {@link Listener}. + * Generator of a traversable view of events. */ -public class EventGenerator implements MoveValidator { - private final EventFilter filter; - private final Listener listener; +public class EventGenerator implements EventIterator { + + private final EventContext context; + + private final LinkedList events = newLinkedList(); + private final LinkedList generators = newLinkedList(); + + private long position = 0; /** - * Listener for listening to changes. + * Create a new instance of a {@code EventGenerator} reporting events to the + * passed {@code listener} after filtering with the passed {@code filter}. + * + * @param filter filter for filtering changes */ - public interface Listener { + public EventGenerator( + @Nonnull NamePathMapper namePathMapper, CommitInfo info, + @Nonnull ImmutableTree before, @Nonnull ImmutableTree after, + @Nonnull EventFilter filter) { + this.context = new EventContext(namePathMapper, info); + + filter = Filters.all(new VisibleFilter(), checkNotNull(filter)); + new EventDiff(before, after, filter).run(); + } + + private class EventDiff implements NodeStateDiff, Runnable { + + private final EventFilter filter; + private final NodeState before; + private final NodeState after; + private final ImmutableTree beforeTree; + private final ImmutableTree afterTree; + + EventDiff(ImmutableTree before, ImmutableTree after, + EventFilter filter) { + this.before = before.getNodeState(); + this.after = after.getNodeState(); + this.filter = filter; + this.beforeTree = before; + this.afterTree = after; + } - /** - * Notification for an added property - * @param after added property - */ - void propertyAdded(PropertyState after); + public EventDiff( + EventDiff parent, String name, + NodeState before, NodeState after, EventFilter filter) { + this.before = before; + this.after = after; + this.filter = filter; + this.beforeTree = new ImmutableTree(parent.beforeTree, name, before); + this.afterTree = new ImmutableTree(parent.afterTree, name, after); + } - /** - * Notification for a changed property - * @param before property before the change - * @param after property after the change - */ - void propertyChanged(PropertyState before, PropertyState after); + //------------------------------------------------------< Runnable >-- - /** - * Notification for a deleted property - * @param before deleted property - */ - void propertyDeleted(PropertyState before); + @Override + public void run() { + after.compareAgainstBaseState(before, this); + } - /** - * Notification for an added node - * @param name name of the node - * @param after added node - */ - void childNodeAdded(String name, NodeState after); + @Override + public boolean propertyAdded(PropertyState after) { + if (filter.includeAdd(after)) { + events.add(new EventImpl( + context, PROPERTY_ADDED, afterTree, after.getName())); + } + return true; + } - /** - * Notification for a changed node - * @param name name of the node - * @param before node before the change - * @param after node after the change - */ - void childNodeChanged(String name, NodeState before, NodeState after); + @Override + public boolean propertyChanged( + PropertyState before, PropertyState after) { + if (filter.includeChange(before, after)) { + events.add(new EventImpl( + context, PROPERTY_CHANGED, afterTree, after.getName())); + } + return true; + } - /** - * Notification for a deleted node - * @param name name of the deleted node - * @param before deleted node - */ - void childNodeDeleted(String name, NodeState before); + @Override + public boolean propertyDeleted(PropertyState before) { + if (filter.includeDelete(before)) { + events.add(new EventImpl( + context, PROPERTY_REMOVED, beforeTree, before.getName())); + } + return true; + } - /** - * Notification for a moved node - * @param sourcePath source of the moved node - * @param name name of the moved node - * @param moved moved node - */ - void nodeMoved(String sourcePath, String name, NodeState moved); + @Override + public boolean childNodeAdded(String name, NodeState after) { + PropertyState sourceProperty = after.getProperty(SOURCE_PATH); + if (sourceProperty != null) { + String sourcePath = sourceProperty.getValue(STRING); + if (filter.includeMove(sourcePath, name, after)) { + String destPath = PathUtils.concat(afterTree.getPath(), name); + Map info = ImmutableMap.of( + "srcAbsPath", context.getJcrPath(sourcePath), + "destAbsPath", context.getJcrPath(destPath)); + ImmutableTree tree = new ImmutableTree(afterTree, name, after); + events.add(new EventImpl(context, NODE_MOVED, tree, info)); + } + } + if (filter.includeAdd(name, after)) { + ImmutableTree tree = new ImmutableTree(afterTree, name, after); + events.add(new EventImpl(context, NODE_ADDED, tree)); + } + addChildGenerator(name, MISSING_NODE, after); + return true; + } + + @Override + public boolean childNodeChanged( + String name, NodeState before, NodeState after) { + if (filter.includeChange(name, before, after)) { + detectReorder(name, before, after); + } + addChildGenerator(name, before, after); + return true; + } + + @Override + public boolean childNodeDeleted(String name, NodeState before) { + if (filter.includeDelete(name, before)) { + ImmutableTree tree = new ImmutableTree(beforeTree, name, before); + events.add(new EventImpl(context, NODE_REMOVED, tree)); + } + addChildGenerator(name, before, MISSING_NODE); + return true; + } + + //------------------------------------------------------------< private >--- + + private void detectReorder(String name, NodeState before, NodeState after) { + List afterNames = newArrayList(after.getNames(OAK_CHILD_ORDER)); + List beforeNames = newArrayList(before.getNames(OAK_CHILD_ORDER)); + + afterNames.retainAll(beforeNames); + beforeNames.retainAll(afterNames); + + // Selection sort beforeNames into afterNames recording the swaps as we go + ImmutableTree parent = new ImmutableTree(afterTree, name, after); + for (int a = 0; a < afterNames.size(); a++) { + String afterName = afterNames.get(a); + for (int b = a; b < beforeNames.size(); b++) { + String beforeName = beforeNames.get(b); + if (a != b && beforeName.equals(afterName)) { + beforeNames.set(b, beforeNames.get(a)); + beforeNames.set(a, beforeName); + Map info = ImmutableMap.of( + "srcChildRelPath", beforeNames.get(a), + "destChildRelPath", beforeNames.get(a + 1)); + ImmutableTree tree = parent.getChild(afterName); + events.add(new EventImpl(context, NODE_MOVED, tree, info)); + } + } + } + } /** - * Factory for creating a filter instance for the given child node - * @param name name of the child node + * Factory method for creating {@code EventGenerator} instances of child nodes. + * @param name name of the child node * @param before before state of the child node * @param after after state of the child node - * @return listener for the child node */ - @Nonnull - Listener create(String name, NodeState before, NodeState after); - } - - /** - * Create a new instance of a {@code EventGenerator} reporting events to the - * passed {@code listener} after filtering with the passed {@code filter}. - * @param filter filter for filtering changes - * @param listener listener for listening to the filtered changes - */ - public EventGenerator(@Nonnull EventFilter filter, @Nonnull Listener listener) { - this.filter = checkNotNull(filter); - this.listener = checkNotNull(listener); - } - - @Override - public void move(String name, String sourcePath, NodeState moved) throws CommitFailedException { - if (filter.includeMove(sourcePath, name, moved)) { - listener.nodeMoved(sourcePath, name, moved); + private void addChildGenerator( + String name, NodeState before, NodeState after) { + EventFilter childFilter = filter.create(name, before, after); + if (childFilter != null) { + generators.add( + new EventDiff(this, name, before, after, childFilter)); + } } - } - @Override - public void enter(NodeState before, NodeState after) throws CommitFailedException { } - @Override - public void leave(NodeState before, NodeState after) throws CommitFailedException { - } + //-----------------------------------------------------< EventIterator >-- @Override - public void propertyAdded(PropertyState after) throws CommitFailedException { - if (filter.includeAdd(after)) { - listener.propertyAdded(after); + public long getSize() { + if (generators.isEmpty()) { + return position + events.size(); + } else { + return -1; } } @Override - public void propertyChanged(PropertyState before, PropertyState after) throws CommitFailedException { - if (filter.includeChange(before, after)) { - listener.propertyChanged(before, after); - } + public long getPosition() { + return position; } @Override - public void propertyDeleted(PropertyState before) throws CommitFailedException { - if (filter.includeDelete(before)) { - listener.propertyDeleted(before); + public boolean hasNext() { + while (events.isEmpty()) { + if (generators.isEmpty()) { + return false; + } else { + generators.removeFirst().run(); + } } + return true; } @Override - public MoveValidator childNodeAdded(String name, NodeState after) throws CommitFailedException { - if (filter.includeAdd(name, after)) { - listener.childNodeAdded(name, after); + public void skip(long skipNum) { + while (skipNum > events.size()) { + position += events.size(); + skipNum -= events.size(); + events.clear(); + // the remove below throws NoSuchElementException if there + // are no more generators, which is correct as then we can't + // skip over enough events + generators.removeFirst().run(); } - return createChildGenerator(name, MISSING_NODE, after); + position += skipNum; + events.subList(0, (int) skipNum).clear(); } @Override - public MoveValidator childNodeChanged(String name, NodeState before, NodeState after) throws CommitFailedException { - if (filter.includeChange(name, before, after)) { - listener.childNodeChanged(name, before, after); + public Event nextEvent() { + if (hasNext()) { + position++; + return events.removeFirst(); + } else { + throw new NoSuchElementException(); } - return createChildGenerator(name, before, after); } @Override - public MoveValidator childNodeDeleted(String name, NodeState before) throws CommitFailedException { - if (filter.includeDelete(name, before)) { - listener.childNodeDeleted(name, before); - } - return createChildGenerator(name, before, MISSING_NODE); + public Event next() { + return nextEvent(); } - /** - * Factory method for creating {@code EventGenerator} instances of child nodes. - * @param name name of the child node - * @param before before state of the child node - * @param after after state of the child node - * @return {@code EventGenerator} for a child node - */ - protected EventGenerator createChildGenerator(String name, NodeState before, NodeState after) { - EventFilter childFilter = filter.create(name, before, after); - if (childFilter != null) { - return new EventGenerator( - childFilter, - listener.create(name, before, after)); - } else { - return null; - } + @Override + public void remove() { + throw new UnsupportedOperationException(); } + } Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/filter/EventFilter.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/filter/EventFilter.java?rev=1561710&r1=1561709&r2=1561710&view=diff ============================================================================== --- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/filter/EventFilter.java (original) +++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/filter/EventFilter.java Mon Jan 27 15:24:51 2014 @@ -21,11 +21,10 @@ package org.apache.jackrabbit.oak.plugin import javax.annotation.CheckForNull; import org.apache.jackrabbit.oak.api.PropertyState; -import org.apache.jackrabbit.oak.plugins.observation.EventGenerator.Listener; import org.apache.jackrabbit.oak.spi.state.NodeState; /** - * Filter for determining what changes to report the the {@link Listener}. + * Filter for determining what changes to report the the event listener. */ public interface EventFilter { Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/filter/VisibleFilter.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/filter/VisibleFilter.java?rev=1561710&view=auto ============================================================================== --- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/filter/VisibleFilter.java (added) +++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/filter/VisibleFilter.java Mon Jan 27 15:24:51 2014 @@ -0,0 +1,78 @@ +/* + * 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.oak.plugins.observation.filter; + +import org.apache.jackrabbit.oak.api.PropertyState; +import org.apache.jackrabbit.oak.spi.state.NodeState; + +/** + * Event filter that hides all non-visible content. + */ +public class VisibleFilter implements EventFilter { + + private boolean isVisible(String name) { + return !name.startsWith(":"); + } + + @Override + public boolean includeAdd(PropertyState after) { + return isVisible(after.getName()); + } + + @Override + public boolean includeChange(PropertyState before, PropertyState after) { + return isVisible(after.getName()); + } + + @Override + public boolean includeDelete(PropertyState before) { + return isVisible(before.getName()); + } + + @Override + public boolean includeAdd(String name, NodeState after) { + return isVisible(name); + } + + @Override + public boolean includeChange(String name, NodeState before, NodeState after) { + return isVisible(name); + } + + @Override + public boolean includeDelete(String name, NodeState before) { + return isVisible(name); + } + + @Override + public boolean includeMove(String sourcePath, String name, NodeState moved) { + return isVisible(name); + } + + @Override + public EventFilter create(String name, NodeState before, NodeState after) { + if (isVisible(name)) { + return this; + } else { + return null; + } + } + +} Propchange: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/filter/VisibleFilter.java ------------------------------------------------------------------------------ svn:eol-style = native