/*
* Copyright 2016 higherfrequencytrading.com
*
* 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 net.openhft.chronicle.engine.tree;
import net.openhft.chronicle.bytes.Bytes;
import net.openhft.chronicle.core.Jvm;
import net.openhft.chronicle.core.io.Closeable;
import net.openhft.chronicle.core.io.IORuntimeException;
import net.openhft.chronicle.core.threads.EventHandler;
import net.openhft.chronicle.core.threads.EventLoop;
import net.openhft.chronicle.core.threads.HandlerPriority;
import net.openhft.chronicle.core.threads.InvalidEventHandlerException;
import net.openhft.chronicle.engine.api.map.MapEvent;
import net.openhft.chronicle.engine.api.map.MapView;
import net.openhft.chronicle.engine.api.pubsub.*;
import net.openhft.chronicle.engine.api.set.EntrySetView;
import net.openhft.chronicle.engine.api.set.KeySetView;
import net.openhft.chronicle.engine.api.tree.Asset;
import net.openhft.chronicle.engine.api.tree.AssetNotFoundException;
import net.openhft.chronicle.engine.api.tree.RequestContext;
import net.openhft.chronicle.engine.fs.Clusters;
import net.openhft.chronicle.engine.fs.EngineCluster;
import net.openhft.chronicle.engine.fs.EngineHostDetails;
import net.openhft.chronicle.engine.map.VanillaKeyValueStore;
import net.openhft.chronicle.engine.map.VanillaMapView;
import net.openhft.chronicle.engine.pubsub.QueueTopicPublisher;
import net.openhft.chronicle.engine.query.Filter;
import net.openhft.chronicle.engine.query.QueueConfig;
import net.openhft.chronicle.network.connection.CoreFields;
import net.openhft.chronicle.queue.ChronicleQueue;
import net.openhft.chronicle.queue.ExcerptAppender;
import net.openhft.chronicle.queue.ExcerptTailer;
import net.openhft.chronicle.queue.impl.RollingChronicleQueue;
import net.openhft.chronicle.queue.impl.single.SingleChronicleQueueBuilder;
import net.openhft.chronicle.wire.*;
import net.openhft.chronicle.wire.WireType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.nio.file.Files;
import java.util.*;
import java.util.function.BiConsumer;
import static net.openhft.chronicle.core.util.ObjectUtils.convertTo;
import static net.openhft.chronicle.queue.impl.single.SingleChronicleQueueBuilder.binary;
import static net.openhft.chronicle.queue.impl.single.SingleChronicleQueueBuilder.defaultZeroBinary;
import static net.openhft.chronicle.wire.WireType.*;
/**
* @author Rob Austin.
*/
public class ChronicleQueueView<T, M> implements QueueView<T, M>, MapView<T, M>, SubAssetFactory,
Closeable {
private static final Logger LOG = LoggerFactory.getLogger(ChronicleQueueView.class);
@NotNull
private final RollingChronicleQueue chronicleQueue;
private final Class<T> messageTypeClass;
@NotNull
private final Class<M> elementTypeClass;
private final ThreadLocal<ThreadLocalData> threadLocal;
@NotNull
private final String defaultPath;
@NotNull
private final RequestContext context;
@NotNull
private final Asset asset;
private boolean isSource;
private boolean isReplicating;
private boolean dontPersist;
@NotNull
private QueueConfig queueConfig;
private volatile MapView<T, M> mapView;
public ChronicleQueueView(@NotNull RequestContext context,
@NotNull Asset asset) throws IOException {
this(null, context, asset);
}
public ChronicleQueueView(@Nullable RollingChronicleQueue queue,
@NotNull RequestContext context,
@NotNull Asset asset) throws IOException {
this.context = context;
this.asset = asset;
@NotNull String s = asset.fullName();
if (s.startsWith("/")) s = s.substring(1);
defaultPath = s;
@Nullable final HostIdentifier hostIdentifier = asset.findOrCreateView(HostIdentifier.class);
@Nullable final Byte hostId = hostIdentifier == null ? null : hostIdentifier.hostId();
queueConfig = asset.findView(QueueConfig.class);
if (queueConfig == null)
throw new AssetNotFoundException("QueueConfig not found at " + asset);
chronicleQueue = queue != null ? queue : newInstance(context.basePath(), queueConfig.wireType());
messageTypeClass = context.messageType();
elementTypeClass = context.elementType();
threadLocal = ThreadLocal.withInitial(() -> new ThreadLocalData(chronicleQueue));
dontPersist = context.dontPersist();
if (hostId != null)
replication(context, asset);
@Nullable EventLoop eventLoop = asset.findOrCreateView(EventLoop.class);
eventLoop.addHandler(new EventHandler() {
@Override
public boolean action() throws InvalidEventHandlerException, InterruptedException {
chronicleQueue.acquireAppender().pretouch();
return false;
}
@NotNull
@Override
public HandlerPriority priority() {
return HandlerPriority.MONITOR;
}
});
}
@NotNull
@SuppressWarnings("WeakerAccess")
public static WriteMarshallable newSource(long nextIndexRequired,
@NotNull Class topicType,
@NotNull Class elementType,
boolean acknowledgement,
@Nullable MessageAdaptor messageAdaptor) {
Objects.requireNonNull(topicType);
Objects.requireNonNull(elementType);
try {
Class<?> aClass = Class.forName("software.chronicle.enterprise.queue.QueueSourceReplicationHandler");
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(long.class, Class.class,
Class.class, boolean.class, MessageAdaptor.class);
return (WriteMarshallable) declaredConstructor.newInstance(nextIndexRequired,
topicType, elementType, acknowledgement, messageAdaptor);
} catch (Exception e) {
@NotNull IllegalStateException licence = new IllegalStateException("A Chronicle Queue Enterprise licence is" +
" required to run chronicle-queue replication. Please contact sales@chronicle" +
".software");
Jvm.warn().on(ChronicleQueueView.class, licence.getMessage());
throw licence;
}
}
/**
* @param topicType the type of the topic
* @param elementType the type of the element
* @param acknowledgement {@code true} if message acknowledgement to the source is required
* @param messageAdaptor used to apply processing in the bytes before they are written to the
* queue
* @return and instance of QueueSyncReplicationHandler
*/
@NotNull
@SuppressWarnings("WeakerAccess")
public static WriteMarshallable newSync(
@NotNull Class topicType,
@NotNull Class elementType,
boolean acknowledgement,
@Nullable MessageAdaptor messageAdaptor,
@NotNull WireType wireType) {
try {
Class<?> aClass = Class.forName("software.chronicle.enterprise.queue.QueueSyncReplicationHandler");
Constructor<?> declaredConstructor = aClass.getConstructor(Class.class, Class.class,
boolean.class, MessageAdaptor.class, WireType.class);
return (WriteMarshallable) declaredConstructor.newInstance(topicType, elementType,
acknowledgement, messageAdaptor, wireType);
} catch (Exception e) {
@NotNull IllegalStateException licence = new IllegalStateException("A Chronicle Queue Enterprise licence is" +
" required to do chronicle-queue replication. " +
"Please contact sales@chronicle.software");
Jvm.warn().on(ChronicleQueueView.class, licence.getMessage());
throw licence;
}
}
public static boolean isQueueReplicationAvailable() {
try {
Class.forName("software.chronicle.enterprise.queue.QueueSyncReplicationHandler");
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
private static void deleteFiles(@NotNull File element) throws IOException {
if (element.isDirectory()) {
@Nullable File[] files = element.listFiles();
if (files == null)
return;
for (@NotNull File sub : files) {
deleteFiles(sub);
}
}
try {
Files.deleteIfExists(element.toPath());
} catch (IOException e) {
Jvm.debug().on(ChronicleQueueView.class, "Unable to delete " + element, e);
}
}
@NotNull
public static QueueView create(@NotNull RequestContext context, @NotNull Asset asset) {
try {
return new ChronicleQueueView<>(context, asset);
} catch (IOException e) {
throw Jvm.rethrow(e);
}
}
public MapView<T, M> mapView() {
final MapView<T, M> mapView = this.mapView;
if (mapView != null) {
return mapView;
}
synchronized (this) {
MapView<T, M> mapView0 = this.mapView;
if (mapView0 != null)
return mapView0;
this.mapView = new QueueViewAsMapView<T, M>(this, context, asset);
return this.mapView;
}
}
@Nullable
public RollingChronicleQueue chronicleQueue() {
return chronicleQueue;
}
public void replication(@NotNull RequestContext context, @NotNull Asset asset) {
@Nullable final HostIdentifier hostIdentifier;
try {
hostIdentifier = asset.findOrCreateView(HostIdentifier.class);
} catch (AssetNotFoundException anfe) {
if (LOG.isDebugEnabled())
Jvm.debug().on(getClass(), "replication not enabled " + anfe.getMessage());
return;
}
final int remoteSourceIdentifier = queueConfig.sourceHostId(context.fullName());
isSource = hostIdentifier.hostId() == remoteSourceIdentifier;
isReplicating = true;
@Nullable final Clusters clusters = asset.findView(Clusters.class);
if (clusters == null) {
LOG.warn("no cluster found name=" + context.cluster());
Jvm.debug().on(getClass(), "no cluster found name=" + context.cluster());
return;
}
final EngineCluster engineCluster = clusters.get(context.cluster());
@NotNull final String csp = context.fullName();
if (engineCluster == null) {
Jvm.debug().on(getClass(), "no cluster found name=" + context.cluster());
LOG.warn("no cluster found name=" + context.cluster());
return;
}
byte localIdentifier = hostIdentifier.hostId();
if (LOG.isDebugEnabled())
Jvm.debug().on(getClass(), "hostDetails : localIdentifier=" + localIdentifier + ",cluster=" + engineCluster.hostDetails());
// if true - each replication event sends back an enableAcknowledgment
final boolean acknowledgement = queueConfig.acknowledgment();
final MessageAdaptor messageAdaptor = queueConfig.bytesFunction();
for (@NotNull EngineHostDetails hostDetails : engineCluster.hostDetails()) {
// its the identifier with the larger values that will establish the connection
byte remoteIdentifier = (byte) hostDetails.hostId();
if (remoteIdentifier == localIdentifier)
continue;
engineCluster.findConnectionManager(remoteIdentifier).addListener((nc, isConnected) -> {
if (!isConnected)
return;
if (nc.isAcceptor())
return;
final boolean isSource0 = (remoteIdentifier == remoteSourceIdentifier);
@NotNull WriteMarshallable h = isSource0 ?
newSource(chronicleQueue.createTailer().toEnd().index(), context.topicType(),
context.elementType(),
acknowledgement,
messageAdaptor) :
newSync(context.topicType(),
context.elementType(),
acknowledgement,
messageAdaptor,
chronicleQueue.wireType());
long cid = nc.newCid();
nc.wireOutPublisher().publish(w -> w.writeDocument(true, d ->
d.writeEventName(CoreFields.csp).text(csp)
.writeEventName(CoreFields.cid).int64(cid)
.writeEventName(CoreFields.handler).typedMarshallable(h)));
});
}
}
@NotNull
@Override
public KeySetView<T> keySet() {
throw new UnsupportedOperationException("todo");
}
@NotNull
@Override
public Collection<M> values() {
throw new UnsupportedOperationException("todo");
}
@NotNull
@Override
public EntrySetView<T, Object, M> entrySet() {
throw new UnsupportedOperationException("todo");
}
@NotNull
@Override
public M getUsing(T key, Object using) {
throw new UnsupportedOperationException("todo");
}
@Override
public void registerTopicSubscriber(@NotNull TopicSubscriber<T, M> topicSubscriber) throws
AssetNotFoundException {
asset.registerTopicSubscriber(asset.fullName(), context.type(), context.type2(), topicSubscriber);
}
@Override
public void unregisterTopicSubscriber(@NotNull TopicSubscriber<T, M> topicSubscriber) {
throw new UnsupportedOperationException("todo");
}
@Override
public void registerKeySubscriber(@NotNull Subscriber<T> subscriber) {
throw new UnsupportedOperationException("todo");
}
@Override
public void registerKeySubscriber(@NotNull Subscriber<T> subscriber, @NotNull Filter filter, @NotNull Set<RequestContext.Operation> contextOperations) {
throw new UnsupportedOperationException("todo");
}
@Override
public void registerSubscriber(@NotNull Subscriber<MapEvent<T, M>> subscriber, @NotNull Filter<MapEvent<T, M>> filter, @NotNull Set<RequestContext.Operation> contextOperations) {
throw new UnsupportedOperationException("todo");
}
@Override
public Reference<M> referenceFor(T key) {
return mapView().referenceFor(key);
}
@Override
public Class<T> keyType() {
return mapView().keyType();
}
@Override
public Class<M> valueType() {
return mapView().valueType();
}
@Override
public long longSize() {
return mapView().longSize();
}
@NotNull
@Override
public M getAndPut(T key, M value) {
return getAndPut(key, value);
}
@Nullable
@Override
public M getAndRemove(T key) {
return mapView().getAndRemove(key);
}
public void unregisterTopicSubscriber(@NotNull T topic, @NotNull TopicSubscriber<T, M> topicSubscriber) {
@NotNull String name = "".equals(topic.toString().trim())
? asset.fullName() : asset
.fullName() + "/" + topic.toString();
asset.unregisterTopicSubscriber(name, context.type(), context.type2(), topicSubscriber);
}
@NotNull
@Override
public Publisher<M> publisher(@NotNull T topic) {
throw new UnsupportedOperationException("todo");
}
@Override
public void registerSubscriber(@NotNull T topic, @NotNull Subscriber<M> subscriber) {
@NotNull String name = "".equals(topic.toString().trim())
? asset.fullName() : asset
.fullName() + "/" + topic.toString();
asset.registerTopicSubscriber(name, context.type(), context.type2(),
(topic1, message) -> subscriber.onMessage((M) message));
}
private RollingChronicleQueue newInstance(@Nullable String basePath,
@NotNull WireType wireType) throws IOException {
if (wireType == DELTA_BINARY)
throw new IllegalArgumentException("Chronicle Queues can not be set to use delta wire");
if (wireType != BINARY && wireType != DEFAULT_ZERO_BINARY)
throw new IllegalArgumentException("Currently the chronicle queue only supports Binary and Default Zero Binary Wire");
RollingChronicleQueue chronicleQueue;
@Nullable File baseFilePath;
if (basePath == null)
baseFilePath = new File(defaultPath, "");
else
baseFilePath = new File(basePath);
if (!baseFilePath.exists())
Files.createDirectories(baseFilePath.toPath());
@NotNull final SingleChronicleQueueBuilder builder = wireType == DEFAULT_ZERO_BINARY
? defaultZeroBinary(baseFilePath)
: binary(baseFilePath);
// TODO make configurable
// builder.blockSize(256 << 20);
return builder.build();
}
@NotNull
private ExcerptTailer threadLocalTailer() {
return threadLocal.get().tailer;
}
@NotNull
private ExcerptAppender threadLocalAppender() {
return threadLocal.get().appender;
}
@Nullable
public Tailer<T, M> tailer() {
@NotNull final ExcerptTailer tailer = ChronicleQueueView.this.chronicleQueue.createTailer();
@NotNull final LocalExcept localExcept = new LocalExcept();
return () -> ChronicleQueueView.this.next(tailer, localExcept);
}
private Excerpt<T, M> next(@NotNull ExcerptTailer excerptTailer, @NotNull final LocalExcept excerpt) {
excerpt.clear();
try (DocumentContext dc = excerptTailer.readingDocument()) {
if (!dc.isPresent())
return null;
final Wire wire = dc.wire();
long pos = wire.bytes().readPosition();
final T topic = wire.readEvent(messageTypeClass);
@NotNull final ValueIn valueIn = wire.getValueIn();
if (Bytes.class.isAssignableFrom(elementTypeClass)) {
valueIn.text(excerpt.text());
} else {
@Nullable final M message = valueIn.object(elementTypeClass);
excerpt.message(message);
}
return excerpt
.topic(topic)
.index(excerptTailer.index());
}
}
/**
* @param index gets the except at the given index, if index==0 then the first index is
* returned
* @return the except
*/
@Nullable
@Override
public Excerpt<T, M> getExcerpt(long index) {
final ThreadLocalData threadLocalData = threadLocal.get();
@NotNull ExcerptTailer excerptTailer = threadLocalData.replayTailer;
if (index == 0)
excerptTailer.toStart();
else if (!excerptTailer.moveToIndex(index))
return null;
try (DocumentContext dc = excerptTailer.readingDocument()) {
if (!dc.isPresent())
return null;
final StringBuilder topic = Wires.acquireStringBuilder();
@Nullable final M message = dc.wire().readEventName(topic).object(elementTypeClass);
return threadLocalData.excerpt
.message(message)
.topic(convertTo(messageTypeClass, topic))
.index(excerptTailer.index());
}
}
@Nullable
@Override
public Excerpt<T, M> getExcerpt(@NotNull T topic) {
final ThreadLocalData threadLocalData = threadLocal.get();
@NotNull ExcerptTailer excerptTailer = threadLocalData.replayTailer;
for (; ; ) {
try (DocumentContext dc = excerptTailer.readingDocument()) {
if (!dc.isPresent())
return null;
final StringBuilder t = Wires.acquireStringBuilder();
@NotNull final ValueIn valueIn = dc.wire().readEventName(t);
@Nullable final T topic1 = convertTo(messageTypeClass, t);
if (!topic.equals(topic1))
continue;
@Nullable final M message = valueIn.object(elementTypeClass);
return threadLocalData.excerpt
.message(message)
.topic(topic1)
.index(excerptTailer.index());
}
}
}
@Override
public void set(T key, M element) {
throw new UnsupportedOperationException("todo");
}
@Override
public void publish(@NotNull T topic, @NotNull M message) {
publishAndIndex(topic, message);
}
/**
* @param consumer a consumer that provides that name of the event and value contained within
* the except
*/
public void getExcerpt(@NotNull BiConsumer<CharSequence, M> consumer) {
@NotNull final ExcerptTailer tailer = threadLocalTailer();
tailer.readDocument(w -> {
final StringBuilder eventName = Wires.acquireStringBuilder();
@NotNull final ValueIn valueIn = w.readEventName(eventName);
consumer.accept(eventName, valueIn.object(elementTypeClass));
});
}
public long publishAndIndex(@NotNull T topic, @NotNull M message) {
if (isReplicating && !isSource)
throw new IllegalStateException("You can not publish to a sink used in replication, " +
"you have to publish to the source");
@NotNull final ExcerptAppender excerptAppender = threadLocalAppender();
try (final DocumentContext dc = excerptAppender.writingDocument()) {
dc.wire().writeEvent(messageTypeClass, topic).object(elementTypeClass, message);
}
return excerptAppender.lastIndexAppended();
}
public long set(@NotNull M event) {
if (isReplicating && !isSource)
throw new IllegalStateException("You can not publish to a sink used in replication, " +
"you have to publish to the source");
@NotNull final ExcerptAppender excerptAppender = threadLocalAppender();
excerptAppender.writeDocument(w -> w.writeEventName(() -> "").object(event));
return excerptAppender.lastIndexAppended();
}
@Override
public boolean isEmpty() {
return mapView().isEmpty();
}
@Override
public boolean containsKey(Object key) {
return mapView().containsKey(key);
}
@Override
public boolean containsValue(Object value) {
return mapView().containsValue(value);
}
@Override
public M get(Object key) {
return mapView().get(key);
}
@Override
public M put(T key, M value) {
return mapView().put(key, value);
}
@Override
public M remove(Object key) {
return mapView().remove(key);
}
@Override
public void putAll(@NotNull Map<? extends T, ? extends M> m) {
mapView().putAll(m);
}
public void clear() {
chronicleQueue.clear();
mapView().clear();
}
@NotNull
public File path() {
throw new UnsupportedOperationException("todo");
}
@NotNull
public WireType wireType() {
throw new UnsupportedOperationException("todo");
}
public void close() {
@NotNull File file = chronicleQueue.file();
chronicleQueue.close();
if (dontPersist) {
try {
deleteFiles(file);
} catch (Exception e) {
Jvm.debug().on(getClass(), "Unable to delete " + file, e);
}
}
}
private void deleteFiles(TopicPublisher p) {
if (p instanceof QueueTopicPublisher)
deleteFiles((ChronicleQueueView) ((QueueTopicPublisher) p).underlying());
}
@Override
protected void finalize() throws Throwable {
super.finalize();
Closeable.closeQuietly(this);
}
@Override
public void registerSubscriber(@NotNull Subscriber<MapEvent<T, M>> subscriber) {
mapView().registerSubscriber(subscriber);
}
public void unregisterSubscriber(Subscriber subscriber) {
}
public int subscriberCount() {
throw new UnsupportedOperationException("todo");
}
public String dump() {
return chronicleQueue.dump();
}
@Nullable
@Override
public <E> Asset createSubAsset(@NotNull VanillaAsset vanillaAsset, String name, Class<E> valueType) {
return new VanillaSubAsset<>(vanillaAsset, name, valueType, null);
}
@Override
public M putIfAbsent(@NotNull T key, M value) {
return mapView().putIfAbsent(key, value);
}
@Override
public boolean remove(@NotNull Object key, Object value) {
return mapView().remove(key, value);
}
@Override
public boolean replace(@NotNull T key, @NotNull M oldValue, @NotNull M newValue) {
return mapView().replace(key, oldValue, newValue);
}
@Override
public M replace(@NotNull T key, @NotNull M value) {
return mapView().replace(key, value);
}
@Override
public Asset asset() {
return mapView().asset();
}
@Nullable
@Override
public Object underlying() {
return chronicleQueue;
}
public static class LocalExcept<T, M> implements Excerpt<T, M>, Marshallable, Map.Entry<T, M> {
@Nullable
private T topic;
@Nullable
private M message;
private Bytes bytes;
private long index;
@Nullable
@Override
public T topic() {
return topic;
}
@Nullable
@Override
public M message() {
return message;
}
@Override
public long index() {
return this.index;
}
@NotNull
public LocalExcept<T, M> index(long index) {
this.index = index;
return this;
}
@NotNull
LocalExcept message(M message) {
this.message = message;
return this;
}
@NotNull
LocalExcept topic(T topic) {
this.topic = topic;
return this;
}
@NotNull
@Override
public String toString() {
return "Except{" + "topic=" + topic + ", message=" + message + '}';
}
@Override
public void writeMarshallable(@NotNull WireOut wireOut) {
wireOut.write(() -> "topic").object(topic);
wireOut.write(() -> "message").object(message);
wireOut.write(() -> "index").int64(index);
}
@Override
public void readMarshallable(@NotNull WireIn wireIn) throws IORuntimeException {
topic((T) wireIn.read(() -> "topic").object(Object.class));
message((M) wireIn.read(() -> "message").object(Object.class));
index(wireIn.read(() -> "index").int64());
}
public void clear() {
message = null;
topic = null;
index = -1;
}
public Bytes text() {
if (bytes == null)
bytes = Bytes.allocateElasticDirect();
else
bytes.clear();
message = (M) bytes;
return bytes;
}
@Nullable
@Override
public T getKey() {
return topic;
}
@Nullable
@Override
public M getValue() {
return message;
}
@NotNull
@Override
public M setValue(M value) {
throw new UnsupportedOperationException("todo");
}
}
/**
* provides mapView view support for a Queue View
*
* @param <K>
* @param <V>
*/
private static class QueueViewAsMapView<K, V> extends VanillaMapView<K, V> {
@NotNull
private final QueueView<K, V> queueView;
QueueViewAsMapView(@NotNull final QueueView<K, V> queueView,
@NotNull RequestContext context,
@NotNull Asset asset) {
super(context, asset, new VanillaKeyValueStore<>(context, asset));
this.queueView = queueView;
queueView.registerTopicSubscriber((topic, message) -> {
if (message == null)
super.remove(topic);
else if (topic != null)
super.put(topic, message);
});
}
@Nullable
@Override
public V put(@NotNull K key, @NotNull V value) {
if (putReturnsNull) {
queueView.publishAndIndex(key, value);
super.put(key, value);
return null;
} else {
@Nullable V v = super.get(key);
queueView.publishAndIndex(key, value);
super.put(key, value);
return v;
}
}
@Override
public void set(K key, @NotNull V value) {
queueView.publishAndIndex((K) key, value);
super.put(key, value);
}
@Override
public V remove(Object key) {
if (removeReturnsNull) {
queueView.publishAndIndex((K) key, null);
super.remove(key);
return null;
} else {
@Nullable V v = super.get(key);
queueView.publishAndIndex((K) key, null);
super.remove(key);
return v;
}
}
@SuppressWarnings("WhileLoopReplaceableByForEach")
@Override
public void clear() {
@NotNull Iterator<Entry<K, V>> iterator = entrySet().iterator();
while (iterator.hasNext()) {
remove(iterator.next().getKey());
}
}
@Nullable
@Override
public V putIfAbsent(@net.openhft.chronicle.core.annotation.NotNull K key, @NotNull V value) {
checkKey(key);
checkValue(value);
@Nullable V v = super.putIfAbsent(key, value);
if (v != null)
queueView.publishAndIndex((K) key, value);
return v;
}
@Override
public boolean remove(@net.openhft.chronicle.core.annotation.NotNull Object key, Object value) {
if (!super.remove(key, value))
return false;
queueView.publishAndIndex((K) key, null);
return true;
}
@Override
public boolean replace(@net.openhft.chronicle.core.annotation.NotNull K key,
@net.openhft.chronicle.core.annotation.NotNull V oldValue,
@net.openhft.chronicle.core.annotation.NotNull V newValue) {
if (!super.replace(key, oldValue, newValue))
return false;
queueView.publishAndIndex((K) key, newValue);
return true;
}
@Nullable
@Override
public V replace(@net.openhft.chronicle.core.annotation.NotNull K key,
@net.openhft.chronicle.core.annotation.NotNull V value) {
@Nullable V replaced = super.replace(key, value);
queueView.publishAndIndex((K) key, value);
return replaced;
}
}
class ThreadLocalData {
@NotNull
final ExcerptAppender appender;
@NotNull
final ExcerptTailer tailer;
@NotNull
final ExcerptTailer replayTailer;
@NotNull
final LocalExcept excerpt;
ThreadLocalData(@NotNull ChronicleQueue chronicleQueue) {
appender = chronicleQueue.acquireAppender();
appender.padToCacheAlign(net.openhft.chronicle.wire.MarshallableOut.Padding.ALWAYS);
tailer = chronicleQueue.createTailer();
replayTailer = chronicleQueue.createTailer();
excerpt = new LocalExcept();
}
}
}