Package org.apache.jackrabbit.oak.plugins.observation

Source Code of org.apache.jackrabbit.oak.plugins.observation.ChangeProcessor$EventGeneratingNodeStateDiff

/*
* 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;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import javax.annotation.Nonnull;
import javax.jcr.observation.Event;
import javax.jcr.observation.EventListener;

import com.google.common.base.Function;
import com.google.common.collect.Iterators;
import com.google.common.collect.Lists;
import org.apache.jackrabbit.api.jmx.EventListenerMBean;
import org.apache.jackrabbit.commons.iterator.EventIteratorAdapter;
import org.apache.jackrabbit.commons.observation.ListenerTracker;
import org.apache.jackrabbit.oak.api.ContentSession;
import org.apache.jackrabbit.oak.api.PropertyState;
import org.apache.jackrabbit.oak.api.Tree;
import org.apache.jackrabbit.oak.commons.PathUtils;
import org.apache.jackrabbit.oak.core.ImmutableRoot;
import org.apache.jackrabbit.oak.core.ImmutableTree;
import org.apache.jackrabbit.oak.namepath.NamePathMapper;
import org.apache.jackrabbit.oak.plugins.observation.ChangeDispatcher.ChangeSet;
import org.apache.jackrabbit.oak.plugins.observation.ChangeDispatcher.Listener;
import org.apache.jackrabbit.oak.spi.state.NodeState;
import org.apache.jackrabbit.oak.spi.state.RecursingNodeStateDiff;
import org.apache.jackrabbit.oak.spi.state.VisibleDiff;
import org.apache.jackrabbit.oak.spi.whiteboard.Registration;
import org.apache.jackrabbit.oak.spi.whiteboard.Whiteboard;
import org.apache.jackrabbit.oak.spi.whiteboard.WhiteboardUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.Iterators.emptyIterator;
import static com.google.common.collect.Iterators.singletonIterator;
import static com.google.common.collect.Iterators.transform;
import static javax.jcr.observation.Event.NODE_ADDED;
import static javax.jcr.observation.Event.NODE_REMOVED;
import static javax.jcr.observation.Event.PROPERTY_ADDED;
import static javax.jcr.observation.Event.PROPERTY_REMOVED;
import static org.apache.jackrabbit.oak.plugins.identifier.IdentifierManager.getIdentifier;

/**
* A {@code ChangeProcessor} generates observation {@link javax.jcr.observation.Event}s
* based on a {@link EventFilter} and delivers them to an {@link javax.jcr.observation.EventListener}.
* <p>
* After instantiation a {@code ChangeProcessor} must be started in order for its
* {@link #run()} methods to be regularly executed and stopped in order to not
* execute its run method anymore.
*/
public class ChangeProcessor implements Runnable {

    private static final Logger log =
            LoggerFactory.getLogger(ChangeProcessor.class);

    private final ContentSession contentSession;
    private final NamePathMapper namePathMapper;
    private final AtomicReference<EventFilter> filterRef;
    private final AtomicReference<String> userDataRef = new AtomicReference<String>(null);

    private final ListenerTracker tracker;
    private final EventListener listener;

    private volatile Thread running = null;
    private volatile boolean stopping = false;
    private Runnable deferredUnregister;

    private Registration runnable;
    private Registration mbean;
    private Listener changeListener;

    public ChangeProcessor(
            ContentSession contentSession, NamePathMapper namePathMapper,
            ListenerTracker tracker, EventFilter filter) {
        checkArgument(contentSession instanceof Observable);
        this.contentSession = contentSession;
        this.namePathMapper = namePathMapper;
        this.tracker = tracker;
        this.listener = tracker.getTrackedListener();
        filterRef = new AtomicReference<EventFilter>(filter);
    }

    /**
     * Set the filter for the events this change processor will generate.
     * @param filter
     */
    public void setFilter(EventFilter filter) {
        filterRef.set(filter);
    }

    /**
     * Set the user data to return with {@link javax.jcr.observation.Event#getUserData()}.
     * @param userData
     */
    public void setUserData(String userData) {
        userDataRef.set(userData);
    }

    /**
     * Start this change processor
     * @param whiteboard  the whiteboard instance to used for scheduling individual
     *                    runs of this change processor.
     * @throws IllegalStateException if started already
     */
    public synchronized void start(Whiteboard whiteboard) {
        checkState(runnable == null, "Change processor started already");

        stopping = false;
        changeListener = ((Observable) contentSession).newListener();
        runnable = WhiteboardUtils.scheduleWithFixedDelay(whiteboard, this, 1);
        mbean = WhiteboardUtils.registerMBean(
                whiteboard, EventListenerMBean.class, tracker.getListenerMBean(),
                "EventListener", tracker.toString());
    }

    /**
     * Stop this change processor if running. After returning from this methods no further
     * events will be delivered.
     * @throws IllegalStateException if not yet started or stopped already
     */
    public void stop() {
        stopping = true; // do this outside synchronization

        if (running == Thread.currentThread()) {
            // Defer stopping from event listener, defer unregistering until
            // event listener is done
            deferredUnregister = new Runnable() {
                @Override
                public void run() {
                    unregister();
                }
            };
        } else {
            // Otherwise wait for the event listener to terminate and unregister immediately
            synchronized (this) {
                try {
                    while (running != null) {
                        wait();
                    }
                    unregister();
                }
                catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        }
    }

    private void unregister() {
        checkState(runnable != null, "Change processor not started");
        mbean.unregister();
        runnable.unregister();
        changeListener.dispose();
    }

    @Override
    public void run() {
        // guarantee that only one thread is processing changes at a time
        synchronized (this) {
            if (running != null) {
                return;
            } else {
                running = Thread.currentThread();
            }
        }

        try {
            ChangeSet changes = changeListener.getChanges();
            while (!stopping && changes != null) {
                EventFilter filter = filterRef.get();
                if (!(filter.excludeLocal() && changes.isLocal(contentSession))) {
                    String path = namePathMapper.getOakPath(filter.getPath());
                    ImmutableTree beforeTree = getTree(changes.getBeforeState(), path);
                    ImmutableTree afterTree = getTree(changes.getAfterState(), path);
                    EventGeneratingNodeStateDiff diff = new EventGeneratingNodeStateDiff(changes, beforeTree, afterTree);
                    SecureNodeStateDiff.compare(VisibleDiff.wrap(diff), beforeTree, afterTree);
                    if (!stopping) {
                        diff.sendEvents();
                    }
                }
                changes = changeListener.getChanges();
            }
        } catch (Exception e) {
            log.debug("Error while dispatching observation events", e);
        } finally {
            running = null;
            synchronized (this) { notifyAll(); }
            if (deferredUnregister != null) {
                deferredUnregister.run();
            }
        }
    }

    private static ImmutableTree getTree(NodeState beforeState, String path) {
        return new ImmutableRoot(beforeState).getTree(path);
    }

    //------------------------------------------------------------< private >---

    private class EventGeneratingNodeStateDiff extends RecursingNodeStateDiff {
        public static final int EVENT_LIMIT = 8192;

        private final ChangeSet changes;
        private final Tree beforeTree;
        private final Tree afterTree;

        private List<Iterator<Event>> events;
        private int eventCount;

        EventGeneratingNodeStateDiff(ChangeSet changes, Tree beforeTree, Tree afterTree, List<Iterator<Event>> events) {
            this.changes = changes;
            this.beforeTree = beforeTree;
            this.afterTree = afterTree;
            this.events = events;
        }

        public EventGeneratingNodeStateDiff(ChangeSet changes, Tree beforeTree, Tree afterTree) {
            this(changes, beforeTree, afterTree, Lists.<Iterator<Event>>newArrayList());
        }

        public void sendEvents() {
            Iterator<Event> eventIt = Iterators.concat(events.iterator());
            if (eventIt.hasNext()) {
                try {
                    listener.onEvent(new EventIteratorAdapter(eventIt) {
                        @Override
                        public boolean hasNext() {
                            return !stopping && super.hasNext();
                        }
                    });
                }
                catch (Exception e) {
                    log.warn("Unhandled exception in observation listener: " + listener, e);
                }
                events = new ArrayList<Iterator<Event>>(EVENT_LIMIT);
            }
        }

        @Override
        public boolean propertyAdded(PropertyState after) {
            if (filterRef.get().include(PROPERTY_ADDED, afterTree)) {
                Event event = generatePropertyEvent(PROPERTY_ADDED, afterTree, after);
                events.add(singletonIterator(event));
            }
            return !stopping;
        }

        @Override
        public boolean propertyChanged(PropertyState before, PropertyState after) {
            if (filterRef.get().include(Event.PROPERTY_CHANGED, afterTree)) {
                Event event = generatePropertyEvent(Event.PROPERTY_CHANGED, afterTree, after);
                events.add(singletonIterator(event));
            }
            return !stopping;
        }

        @Override
        public boolean propertyDeleted(PropertyState before) {
            if (filterRef.get().include(PROPERTY_REMOVED, afterTree)) {
                Event event = generatePropertyEvent(PROPERTY_REMOVED, beforeTree, before);
                events.add(singletonIterator(event));
            }
            return !stopping;
        }

        @Override
        public boolean childNodeAdded(String name, NodeState after) {
            if (filterRef.get().includeChildren(afterTree.getPath())) {
                Iterator<Event> events = generateNodeEvents(
                        NODE_ADDED, afterTree.getChild(name));
                this.events.add(events);
                if (++eventCount > EVENT_LIMIT) {
                    sendEvents();
                }
            }
            return !stopping;
        }

        @Override
        public boolean childNodeDeleted(String name, NodeState before) {
            if (filterRef.get().includeChildren(beforeTree.getPath())) {
                Iterator<Event> events = generateNodeEvents(
                        NODE_REMOVED, beforeTree.getChild(name));
                this.events.add(events);
            }
            return !stopping;
        }

        @Override
        public boolean childNodeChanged(String name, NodeState before, NodeState after) {
            return !stopping;
        }

        @Nonnull
        @Override
        public RecursingNodeStateDiff createChildDiff(String name, NodeState before, NodeState after) {
            if (filterRef.get().includeChildren(afterTree.getPath())) {
                EventGeneratingNodeStateDiff diff = new EventGeneratingNodeStateDiff(
                        changes, beforeTree.getChild(name), afterTree.getChild(name), events);
                return VisibleDiff.wrap(diff);
            } else {
                return RecursingNodeStateDiff.EMPTY;
            }
        }

        private EventImpl createEvent(int eventType, String path, String id) {
            // TODO support info
            return new EventImpl(
                    eventType, namePathMapper.getJcrPath(path), changes.getUserId(),
                    id, null, changes.getDate(), userDataRef.get(),
                    changes.isExternal());
        }

        private Event generatePropertyEvent(int eventType, Tree parent, PropertyState property) {
            String path = PathUtils.concat(parent.getPath(), property.getName());
            return createEvent(eventType, path, getIdentifier(parent));
        }

        private Iterator<Event> generateNodeEvents(int eventType, final Tree tree) {
            EventFilter filter = filterRef.get();
            Iterator<Event> nodeEvent;
            if (filter.include(eventType, tree.isRoot() ? null : tree.getParent())) {
                Event event = createEvent(eventType, tree.getPath(), getIdentifier(tree));
                nodeEvent = singletonIterator(event);
            } else {
                nodeEvent = emptyIterator();
            }

            final int propertyEventType = eventType == NODE_ADDED
                    ? PROPERTY_ADDED
                    : PROPERTY_REMOVED;

            Iterator<Event> propertyEvents;
            if (filter.include(propertyEventType, tree)) {
                propertyEvents = transform(
                        tree.getProperties().iterator(),
                        new Function<PropertyState, Event>() {
                            @Override
                            public Event apply(PropertyState property) {
                                return generatePropertyEvent(propertyEventType, tree, property);
                            }
                        });
            } else {
                propertyEvents = emptyIterator();
            }

            Iterator<Event> childNodeEvents = filter.includeChildren(tree.getPath())
                    ? Iterators.concat(generateChildEvents(eventType, tree))
                    : Iterators.<Event>emptyIterator();

            return Iterators.concat(nodeEvent, propertyEvents, childNodeEvents);
        }

        private Iterator<Iterator<Event>> generateChildEvents(final int eventType, final Tree tree) {
            return transform(
                    tree.getChildren().iterator(),
                    new Function<Tree, Iterator<Event>>() {
                        @Override
                        public Iterator<Event> apply(Tree child) {
                            return generateNodeEvents(eventType, child);
                        }
                    });
        }

    }

}
TOP

Related Classes of org.apache.jackrabbit.oak.plugins.observation.ChangeProcessor$EventGeneratingNodeStateDiff

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.