org.modeshape.jcr.bus.RepositoryChangeBus Maven / Gradle / Ivy
/*
* ModeShape (http://www.modeshape.org)
*
* Licensed 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.modeshape.jcr.bus;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.modeshape.common.collection.ring.RingBuffer;
import org.modeshape.common.collection.ring.RingBufferBuilder;
import org.modeshape.common.logging.Logger;
import org.modeshape.jcr.cache.change.ChangeSet;
import org.modeshape.jcr.cache.change.ChangeSetListener;
/**
* Change bus implementation around a {@link org.modeshape.common.collection.ring.RingBuffer}
*
* @author Randall Hauch ([email protected])
* @author Horia Chiorean ([email protected])
*/
public final class RepositoryChangeBus implements ChangeBus {
protected static final Logger LOGGER = Logger.getLogger(RepositoryChangeBus.class);
private static final int DEFAULT_SIZE = 1 << 10; // 1024
private final AtomicBoolean shutdown = new AtomicBoolean(true);
/**
* We use a lock for {@link #register(ChangeSetListener)}, {@link #registerInThread(ChangeSetListener)},
* {@link #unregister(ChangeSetListener)}, {@link #start()} and {@link #shutdown()} to ensure that a single listener is
* properly and atomcially added to either one of the maps. However, the {@link #notify(ChangeSet)} method only needs a
* consistent snapshot of each of the maps (not a consistent snapshot of both), which is why we're using
* concurrent maps (even though we're using a lock for registration).
*/
private final Lock registrationLock = new ReentrantLock();
private final Set inThreadListeners = new CopyOnWriteArraySet<>();
private final RingBuffer ringBuffer;
/**
* Creates new change bus
*
* @param repositoryName the repository name; may not be null
* @param executor the {@link java.util.concurrent.ExecutorService} which will be used internally to submit workers to
* dispatching events to listeners.
*/
public RepositoryChangeBus( String repositoryName,
ExecutorService executor ) {
this.ringBuffer = RingBufferBuilder.withMultipleProducers(executor, new ChangeSetListenerConsumerAdapter())
.ofSize(DEFAULT_SIZE).named(repositoryName).garbageCollect(true).build();
}
@Override
public boolean hasObservers() {
if (shutdown.get()) return false;
return !inThreadListeners.isEmpty() || ringBuffer.hasConsumers();
}
@Override
public boolean register( ChangeSetListener observer ) {
if (observer == null || shutdown.get()) return false;
try {
registrationLock.lock();
return ringBuffer.addConsumer(observer);
} finally {
registrationLock.unlock();
}
}
@Override
public boolean registerInThread( ChangeSetListener observer ) {
if (observer == null || shutdown.get()) return false;
try {
registrationLock.lock();
return inThreadListeners.add(observer);
} finally {
registrationLock.unlock();
}
}
@Override
public boolean unregister( ChangeSetListener observer ) {
if (observer == null || shutdown.get()) return false;
try {
registrationLock.lock();
return ringBuffer.remove(observer) || inThreadListeners.remove(observer);
} finally {
registrationLock.unlock();
}
}
@Override
public synchronized void start() throws Exception {
shutdown.set(false);
}
@Override
public synchronized void shutdown() {
// This method is synchronized to make sure that 'start' and 'stop' are not called simultaneously ...
if (shutdown.getAndSet(true)) {
// It was already shutdown ...
return;
}
try {
registrationLock.lock();
// Clear all of the in-thread listeners ...
inThreadListeners.clear();
// Shutdown the ring buffer waiting for running threads to complete
ringBuffer.shutdown();
} finally {
registrationLock.unlock();
}
}
@Override
public void notify( ChangeSet changeSet ) {
if (changeSet == null || !hasObservers()) return;
if (shutdown.get()) {
throw new IllegalStateException("Change bus has been already shut down, should not have any more observers");
}
// Add the change set into the buffer so it can be processed by the asynchronous listeners ...
ringBuffer.add(changeSet);
// And process all of the in-thread listeners ...
for (ChangeSetListener listener : inThreadListeners) {
listener.notify(changeSet);
}
}
protected class ChangeSetListenerConsumerAdapter implements RingBuffer.ConsumerAdapter {
@Override
public boolean consume( ChangeSetListener consumer,
ChangeSet event,
long position,
long maxPosition ) {
consumer.notify(event);
return true;
}
@Override
public void close( ChangeSetListener consumer ) {
// nothing to do here
}
@Override
public void handleException( ChangeSetListener consumer,
Throwable t,
ChangeSet entry,
long position,
long maxPosition ) {
LOGGER.error(t, BusI18n.errorProcessingEvent, entry.toString(), position);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy