/* * 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.map.remote; import net.openhft.chronicle.core.Jvm; import net.openhft.chronicle.core.util.SerializableBiFunction; import net.openhft.chronicle.core.util.SerializableUpdaterWithArg; import net.openhft.chronicle.core.util.ThrowingConsumer; import net.openhft.chronicle.core.util.Time; import net.openhft.chronicle.engine.api.EngineReplication.ReplicationEntry; import net.openhft.chronicle.engine.api.map.KeyValueStore; import net.openhft.chronicle.engine.api.map.MapEvent; import net.openhft.chronicle.engine.api.map.MapView; import net.openhft.chronicle.engine.api.pubsub.InvalidSubscriberException; import net.openhft.chronicle.engine.api.pubsub.SubscriptionConsumer; import net.openhft.chronicle.engine.api.tree.Asset; import net.openhft.chronicle.engine.api.tree.RequestContext; import net.openhft.chronicle.engine.collection.ClientWiredStatelessChronicleCollection; import net.openhft.chronicle.engine.collection.ClientWiredStatelessChronicleSet; import net.openhft.chronicle.engine.map.InsertedEvent; import net.openhft.chronicle.engine.map.ObjectKeyValueStore; import net.openhft.chronicle.engine.map.ObjectSubscription; import net.openhft.chronicle.network.connection.AbstractStatelessClient; import net.openhft.chronicle.network.connection.CoreFields; import net.openhft.chronicle.network.connection.TcpChannelHub; import net.openhft.chronicle.wire.ValueIn; import net.openhft.chronicle.wire.Wires; import net.openhft.chronicle.wire.WriteMarshallable; import net.openhft.chronicle.wire.WriteValue; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.File; import java.util.*; import java.util.function.Function; import static net.openhft.chronicle.engine.server.internal.MapWireHandler.EventId; import static net.openhft.chronicle.engine.server.internal.MapWireHandler.EventId.*; import static net.openhft.chronicle.network.connection.CoreFields.stringEvent; public class RemoteKeyValueStore<K, V> extends AbstractStatelessClient<EventId> implements Cloneable, ObjectKeyValueStore<K, V> { private static final WriteValue VOID_PARAMETERS = WriteMarshallable.EMPTY; private final Class<K> kClass; private final Class<V> vClass; private final Map<Long, String> cidToCsp = new HashMap<>(); @NotNull private final RequestContext context; @NotNull private final Asset asset; // todo @NotNull private final ObjectSubscription<K, V> subscriptions; public RemoteKeyValueStore(@NotNull final RequestContext context, @NotNull Asset asset, @NotNull final TcpChannelHub hub) { super(hub, (long) 0, toUri(context)); this.asset = asset; this.kClass = context.keyType(); this.vClass = context.valueType(); this.context = context; subscriptions = asset.acquireView(ObjectSubscription.class, context); subscriptions.setKvStore(this); } public RemoteKeyValueStore(@NotNull RequestContext requestContext, @NotNull Asset asset) { this(requestContext, asset, asset.findView(TcpChannelHub.class)); } @NotNull private static String toUri(@NotNull final RequestContext context) { return context.viewType(MapView.class).toUri(); } @Override public boolean isKeyType(Object key) { return kClass.isInstance(key); } @NotNull public File file() { throw new UnsupportedOperationException(); } @Nullable @SuppressWarnings("NullableProblems") public V putIfAbsent(K key, V value) { checkKey(key); checkValue(value); return proxyReturnTypedObject(putIfAbsent, null, vClass, key, value); } @Override public boolean containsValue(final V value) { throw new UnsupportedOperationException("todo"); } private void checkValue(@Nullable Object value) { if (value == null) throw new NullPointerException("value must not be null"); } @SuppressWarnings("NullableProblems") public boolean remove(@Nullable Object key, Object value) { if (key == null) return false; checkValue(value); return proxyReturnBooleanWithArgs(removeWithValue, key, value); } @SuppressWarnings("NullableProblems") public boolean replace(K key, V oldValue, V newValue) { checkKey(key); checkValue(oldValue); checkValue(newValue); return proxyReturnBooleanWithArgs(replaceForOld, key, oldValue, newValue); } @Nullable @SuppressWarnings("NullableProblems") public V replace(K key, V value) { checkKey(key); checkValue(value); return proxyReturnTypedObject(replace, null, vClass, key, value); } @Nullable public <A, R> R applyTo(@NotNull SerializableBiFunction<MapView<K, V>, A, R> function, A arg) { return (R) proxyReturnTypedObject(applyTo2, null, Object.class, function, arg); } @Nullable public <R, UA, RA> R syncUpdate(SerializableUpdaterWithArg updateFunction, UA ua, SerializableBiFunction returnFunction, RA ra) { return (R) proxyReturnTypedObject(update4, null, Object.class, updateFunction, ua, returnFunction, ra); } public <A> void asyncUpdate(SerializableUpdaterWithArg updateFunction, A arg) { sendEventAsync(update2, toParameters(update2, updateFunction, arg), true); } @Override public void keysFor(final int segment, @NotNull final SubscriptionConsumer<K> kConsumer) throws InvalidSubscriberException { keySet().forEach(ThrowingConsumer.asConsumer(kConsumer::accept)); } @Override public void entriesFor(final int segment, @NotNull final SubscriptionConsumer<MapEvent<K, V>> kvConsumer) throws InvalidSubscriberException { @NotNull String assetName = asset.fullName(); entrySet().forEach(ThrowingConsumer.asConsumer(entry -> kvConsumer.accept(InsertedEvent.of(assetName, entry.getKey(), entry.getValue(), false)))); } /** * calling this method should be avoided at all cost, as the entire {@code object} is * serialized. This equals can be used to compare map that extends ChronicleMap. So two * Chronicle Maps that contain the same data are considered equal, even if the instances of the * chronicle maps were of different types * * @param object the object that you are comparing against * @return true if the contain the same data */ @Override public boolean equals(@Nullable Object object) { if (this == object) return true; if (object == null || object.getClass().isAssignableFrom(Map.class)) return false; @NotNull final Map<? extends K, ? extends V> that = (Map<? extends K, ? extends V>) object; if (that.size() != longSize()) return false; @NotNull final Set<Map.Entry<K, V>> entries = entrySet(); return that.entrySet().equals(entries); } @Override public int hashCode() { return proxyReturnInt(hashCode); } @NotNull public String toString() { if (Jvm.isDebug()) return "toString() not available while debugging"; @NotNull final Iterator<Map.Entry<K, V>> entries = entrySet().iterator(); if (!entries.hasNext()) return "{}"; @NotNull StringBuilder sb = new StringBuilder(); sb.append('{'); while (entries.hasNext()) { final Map.Entry<K, V> e = entries.next(); final K key = e.getKey(); final V value = e.getValue(); sb.append(key == this ? "(this Map)" : key); sb.append('='); sb.append(value == this ? "(this Map)" : value); if (!entries.hasNext()) return sb.append('}').toString(); sb.append(',').append(' '); } return sb.toString(); } public boolean isEmpty() { return longSize() == 0; } public boolean containsKey(Object key) { checkKey(key); return proxyReturnBoolean(containsKey, out -> out.object(key)); } @Nullable public V get(Object key) { checkKey(key); return this.proxyReturnTypedObject(get, null, vClass, key); } @Nullable public V getUsing(K key, Object usingValue) { checkKey(key); return this.proxyReturnTypedObject(get, (V) usingValue, vClass, key); } public long longSize() { return proxyReturnLong(size); } public boolean remove(Object key) { checkKey(key); sendEventAsync(remove, toParameters(remove, key), true); return false; } @Nullable @Override public V getAndRemove(final Object key) { checkKey(key); return proxyReturnTypedObject(getAndRemove, null, vClass, key); } private void checkKey(@Nullable Object key) { if (key == null) throw new NullPointerException("key can not be null"); } public boolean put(K key, V value) { checkKey(key); checkValue(value); sendEventAsync(put, toParameters(put, key, value), true); return false; } @Nullable @Override public V getAndPut(final Object key, final Object value) { checkKey(key); checkValue(value); return proxyReturnTypedObject(getAndPut, null, vClass, key, value); } public void clear() { proxyReturnVoid(clear); } @Nullable public Collection<V> values() { final StringBuilder csp = Wires.acquireStringBuilder(); long cid = proxyReturnWireConsumer(values, read -> { read.typePrefix(); return read.applyToMarshallable(w -> { stringEvent(CoreFields.csp, csp, w); final long cid0 = CoreFields.cid(w); cidToCsp.put(cid0, csp.toString()); return cid0; }); }); @Nullable final Function<ValueIn, V> consumer = valueIn -> valueIn.object(vClass); return new ClientWiredStatelessChronicleCollection<>(hub, ArrayList::new, consumer, "/" + context.name() + "?view=" + "values", cid ); } @NotNull public Set<Map.Entry<K, V>> entrySet() { final StringBuilder csp = Wires.acquireStringBuilder(); long cid = proxyReturnWireConsumer(entrySet, read -> { read.typePrefix(); return read.applyToMarshallable(w -> { stringEvent(CoreFields.csp, csp, w); final long cid0 = CoreFields.cid(w); cidToCsp.put(cid0, csp.toString()); return cid0; }); }); @NotNull Function<ValueIn, Map.Entry<K, V>> consumer = valueIn -> valueIn.applyToMarshallable(r -> { @Nullable final K k = r.read(() -> "key").object(kClass); @Nullable final V v = r.read(() -> "value").object(vClass); return new Map.Entry<K, V>() { @Nullable @Override public K getKey() { return k; } @Nullable @Override public V getValue() { return v; } @NotNull @Override public V setValue(Object value) { throw new UnsupportedOperationException(); } }; } ); return new ClientWiredStatelessChronicleSet<>(hub, csp.toString(), cid, consumer); } @NotNull @Override public Iterator<Map.Entry<K, V>> entrySetIterator() { return entrySet().iterator(); } @NotNull public Set<K> keySet() { final StringBuilder csp = Wires.acquireStringBuilder(); long cid = proxyReturnWireConsumer(keySet, read -> { read.typePrefix(); return read.applyToMarshallable(w -> { stringEvent(CoreFields.csp, csp, w); final long cid0 = CoreFields.cid(w); cidToCsp.put(cid0, csp.toString()); return cid0; }); }); return new ClientWiredStatelessChronicleSet<>(hub, csp.toString(), cid, valueIn -> valueIn.object(kClass)); } @SuppressWarnings("SameParameterValue") private boolean proxyReturnBoolean(@NotNull final EventId eventId, @Nullable final WriteValue consumer) { final long startTime = Time.currentTimeMillis(); return attempt(() -> readBoolean(sendEvent(startTime, eventId, consumer), startTime)); } @SuppressWarnings("SameParameterValue") private int proxyReturnInt(@NotNull final EventId eventId) { final long startTime = Time.currentTimeMillis(); return attempt(() -> readInt(sendEvent(startTime, eventId, VOID_PARAMETERS), startTime)); } @NotNull @Override public Asset asset() { return asset; } @NotNull @Override public KeyValueStore<K, V> underlying() { throw new UnsupportedOperationException(); } @NotNull @Override public ObjectSubscription<K, V> subscription(boolean createIfAbsent) { return subscriptions; } @Override public Class<K> keyType() { return kClass; } @Override public Class<V> valueType() { return vClass; } @Override public void accept(final ReplicationEntry replicationEntry) { throw new UnsupportedOperationException(""); } class Entry implements Map.Entry<K, V> { final K key; final V value; /** * Creates new entry. */ Entry(K k1, V v) { value = v; key = k1; } public final K getKey() { return key; } public final V getValue() { return value; } public final V setValue(V newValue) { V oldValue = value; RemoteKeyValueStore.this.put(getKey(), newValue); return oldValue; } public final boolean equals(Object o) { if (!(o instanceof Map.Entry)) return false; @NotNull final Map.Entry e = (Map.Entry) o; final Object k1 = getKey(); final Object k2 = e.getKey(); if (k1 == k2 || (k1 != null && k1.equals(k2))) { Object v1 = getValue(); Object v2 = e.getValue(); if (v1 == v2 || (v1 != null && v1.equals(v2))) return true; } return false; } public final int hashCode() { return (key == null ? 0 : key.hashCode()) ^ (value == null ? 0 : value.hashCode()); } @NotNull public final String toString() { return getKey() + "=" + getValue(); } } }