package org.infinispan.functional.impl;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.Set;
import org.infinispan.commons.api.functional.EntryView.ReadEntryView;
import org.infinispan.commons.api.functional.EntryView.ReadWriteEntryView;
import org.infinispan.commons.api.functional.EntryView.WriteEntryView;
import org.infinispan.commons.api.functional.MetaParam;
import org.infinispan.commons.marshall.AbstractExternalizer;
import org.infinispan.commons.marshall.AdvancedExternalizer;
import org.infinispan.commons.util.Experimental;
import org.infinispan.commons.util.Util;
import org.infinispan.container.entries.CacheEntry;
import org.infinispan.container.versioning.EntryVersion;
import org.infinispan.container.versioning.FunctionalEntryVersionAdapter;
import org.infinispan.marshall.core.Ids;
import org.infinispan.metadata.Metadata;
/**
* Entry views implementation class holder.
*
* @since 8.0
*/
@Experimental
public final class EntryViews {
private EntryViews() {
// Cannot be instantiated, it's just a holder class
}
public static <K, V> ReadEntryView<K, V> readOnly(CacheEntry<K, V> entry) {
return new EntryBackedReadOnlyView<>(entry);
}
public static <K, V> ReadEntryView<K, V> readOnly(K key, V value, Metadata metadata) {
return new ReadOnlySnapshotView<>(key, value, metadata);
}
public static <K, V> WriteEntryView<V> writeOnly(CacheEntry<K, V> entry) {
return new EntryBackedWriteOnlyView<>(entry);
}
public static <K, V> ReadWriteEntryView<K, V> readWrite(CacheEntry<K, V> entry) {
return new EntryBackedReadWriteView<>(entry);
}
public static <K, V> ReadWriteEntryView<K, V> readWrite(CacheEntry<K, V> entry, V prevValue, Metadata prevMetadata) {
return new EntryAndPreviousReadWriteView<>(entry, prevValue, prevMetadata);
}
public static <K, V> ReadEntryView<K, V> noValue(K key) {
return new NoValueReadOnlyView<>(key);
}
/**
* For convenience, a lambda might decide to return the entry view it
* received as parameter, because that makes easy to return both value and
* meta parameters back to the client.
*
* If the lambda function decides to return an view, launder it into an
* immutable view to avoid the user trying apply any modifications to the
* entry view from outside the lambda function.
*
* If the view is read-only, capture its data into a snapshot from the
* cached entry and avoid changing underneath.
*/
@SuppressWarnings("unchecked")
public static <R> R snapshot(R ret) {
if (ret instanceof EntryBackedReadWriteView) {
EntryBackedReadWriteView view = (EntryBackedReadWriteView) ret;
return (R) new ReadWriteSnapshotView(view.key(), view.entry.getValue(), view.entry.getMetadata());
} else if (ret instanceof EntryAndPreviousReadWriteView) {
EntryAndPreviousReadWriteView view = (EntryAndPreviousReadWriteView) ret;
return (R) new ReadWriteSnapshotView(view.key(), view.entry.getValue(), view.entry.getMetadata());
} else if (ret instanceof EntryBackedReadOnlyView) {
EntryBackedReadOnlyView view = (EntryBackedReadOnlyView) ret;
return (R) new ReadOnlySnapshotView(view.key(), view.entry.getValue(), view.entry.getMetadata());
}
return ret;
}
private static final class EntryBackedReadOnlyView<K, V> implements ReadEntryView<K, V> {
final CacheEntry<K, V> entry;
private EntryBackedReadOnlyView(CacheEntry<K, V> entry) {
this.entry = entry;
}
@Override
public K key() {
return entry.getKey();
}
@Override
public Optional<V> find() {
return entry == null ? Optional.empty() : Optional.ofNullable(entry.getValue());
}
@Override
public V get() throws NoSuchElementException {
if (entry == null || entry.getValue() == null)
throw new NoSuchElementException("No value present");
return entry.getValue();
}
@Override
public <T> Optional<T> findMetaParam(Class<T> type) {
Metadata metadata = entry.getMetadata();
if (metadata instanceof MetaParamsInternalMetadata) {
MetaParamsInternalMetadata metaParamsMetadata = (MetaParamsInternalMetadata) metadata;
return metaParamsMetadata.findMetaParam(type);
}
// TODO: Add interoperability support, e.g. able to retrieve lifespan for data stored in Cache via lifespan API
return Optional.empty();
}
@Override
public String toString() {
return "EntryBackedReadOnlyView{" + "entry=" + entry + '}';
}
}
private static final class ReadOnlySnapshotView<K, V> implements ReadEntryView<K, V> {
final K key;
final V value;
final Metadata metadata;
private ReadOnlySnapshotView(K key, V value, Metadata metadata) {
this.key = key;
this.value = value;
this.metadata = metadata;
}
@Override
public K key() {
return key;
}
@Override
public V get() throws NoSuchElementException {
if (value == null) throw new NoSuchElementException("No value");
return value;
}
@Override
public Optional<V> find() {
return Optional.ofNullable(value);
}
// TODO: Duplication
@Override
public <T> Optional<T> findMetaParam(Class<T> type) {
if (metadata instanceof MetaParamsInternalMetadata) {
MetaParamsInternalMetadata metaParamsMetadata = (MetaParamsInternalMetadata) metadata;
return metaParamsMetadata.findMetaParam(type);
}
// TODO: Add interoperability support, e.g. able to retrieve lifespan for data stored in Cache via lifespan API
return Optional.empty();
}
@Override
public String toString() {
return "ReadOnlySnapshotView{" +
"key=" + key +
", value=" + value +
", metadata=" + metadata +
'}';
}
}
private static final class EntryBackedWriteOnlyView<K, V> implements WriteEntryView<V> {
final CacheEntry<K, V> entry;
private EntryBackedWriteOnlyView(CacheEntry<K, V> entry) {
this.entry = entry;
}
@Override
public Void set(V value, MetaParam.Writable... metas) {
entry.setValue(value);
entry.setChanged(true);
updateMetaParams(entry, metas);
return null;
}
@Override
public Void remove() {
entry.setRemoved(true);
entry.setChanged(true);
entry.setValue(null);
return null;
}
@Override
public String toString() {
return "EntryBackedWriteOnlyView{" + "entry=" + entry + '}';
}
}
private static final class EntryBackedReadWriteView<K, V> implements ReadWriteEntryView<K, V> {
final CacheEntry<K, V> entry;
private EntryBackedReadWriteView(CacheEntry<K, V> entry) {
this.entry = entry;
}
@Override
public K key() {
return entry.getKey();
}
@Override
public Optional<V> find() {
return entry == null ? Optional.empty() : Optional.ofNullable(entry.getValue());
}
@Override
public Void set(V value, MetaParam.Writable... metas) {
setOnly(value, metas);
return null;
}
private void setOnly(V value, MetaParam.Writable[] metas) {
entry.setValue(value);
entry.setChanged(true);
updateMetaParams(entry, metas);
}
@Override
public Void remove() {
if (!entry.isNull()) {
entry.setRemoved(true);
entry.setChanged(true);
entry.setValue(null);
}
return null;
}
@Override
public <T> Optional<T> findMetaParam(Class<T> type) {
Metadata metadata = entry.getMetadata();
if (metadata instanceof MetaParamsInternalMetadata) {
MetaParamsInternalMetadata metaParamsMetadata = (MetaParamsInternalMetadata) metadata;
return metaParamsMetadata.findMetaParam(type);
}
// TODO: Add interoperability support, e.g. able to retrieve lifespan for data stored in Cache via lifespan API
return Optional.empty();
}
@Override
public V get() throws NoSuchElementException {
if (entry == null || entry.getValue() == null)
throw new NoSuchElementException("No value present");
return entry.getValue();
}
@Override
public String toString() {
return "EntryBackedReadWriteView{" + "entry=" + entry + '}';
}
}
private static final class EntryAndPreviousReadWriteView<K, V> implements ReadWriteEntryView<K, V> {
final CacheEntry<K, V> entry;
final V prevValue;
final Metadata prevMetadata;
private EntryAndPreviousReadWriteView(CacheEntry<K, V> entry, V prevValue, Metadata prevMetadata) {
this.entry = entry;
this.prevValue = prevValue;
this.prevMetadata = prevMetadata;
}
@Override
public K key() {
return entry.getKey();
}
@Override
public Optional<V> find() {
return Optional.ofNullable(prevValue);
}
@Override
public Void set(V value, MetaParam.Writable... metas) {
setOnly(value, metas);
return null;
}
private void setOnly(V value, MetaParam.Writable[] metas) {
entry.setValue(value);
entry.setChanged(true);
updateMetaParams(entry, metas);
}
@Override
public Void remove() {
if (!entry.isNull()) {
entry.setRemoved(true);
entry.setChanged(true);
entry.setValue(null);
}
return null;
}
@Override
public <T> Optional<T> findMetaParam(Class<T> type) {
Metadata metadata = prevMetadata; // Use previous metadata
if (metadata instanceof MetaParamsInternalMetadata) {
MetaParamsInternalMetadata metaParamsMetadata = (MetaParamsInternalMetadata) metadata;
return metaParamsMetadata.findMetaParam(type);
}
// TODO: Add interoperability support, e.g. able to retrieve lifespan for data stored in Cache via lifespan API
return Optional.empty();
}
@Override
public V get() throws NoSuchElementException {
if (prevValue == null) throw new NoSuchElementException();
return prevValue;
}
@Override
public String toString() {
return "EntryAndPreviousReadWriteView{" +
"entry=" + entry +
", prevValue=" + prevValue +
", prevMetadata=" + prevMetadata +
'}';
}
}
private static final class NoValueReadOnlyView<K, V> implements ReadEntryView<K, V> {
final K key;
public NoValueReadOnlyView(K key) {
this.key = key;
}
@Override
public K key() {
return key;
}
@Override
public V get() throws NoSuchElementException {
throw new NoSuchElementException("No value for key " + key);
}
@Override
public Optional<V> find() {
return Optional.empty();
}
@Override
public <T> Optional<T> findMetaParam(Class<T> type) {
return Optional.empty();
}
@Override
public String toString() {
return "NoValueReadOnlyView{" + "key=" + key + '}';
}
}
private static final class ReadWriteSnapshotView<K, V> implements ReadWriteEntryView<K, V> {
final K key;
final V value;
final Metadata metadata;
public ReadWriteSnapshotView(K key, V value, Metadata metadata) {
this.key = key;
this.value = value;
this.metadata = metadata;
}
@Override
public K key() {
return key;
}
@Override
public V get() throws NoSuchElementException {
if (value == null)
throw new NoSuchElementException("No value present");
return value;
}
@Override
public Optional<V> find() {
return Optional.ofNullable(value);
}
// TODO: Duplication
@Override
public <T> Optional<T> findMetaParam(Class<T> type) {
if (metadata instanceof MetaParamsInternalMetadata) {
MetaParamsInternalMetadata metaParamsMetadata = (MetaParamsInternalMetadata) metadata;
return metaParamsMetadata.findMetaParam(type);
}
// TODO: Add interoperability support, e.g. able to retrieve lifespan for data stored in Cache via lifespan API
return Optional.empty();
}
@Override
public Void set(V value, MetaParam.Writable... metas) {
throw new IllegalStateException(
"A read-write entry view cannot be modified outside the scope of a lambda");
}
@Override
public Void remove() {
throw new IllegalStateException(
"A read-write entry view cannot be modified outside the scope of a lambda");
}
@Override
public String toString() {
return "ReadWriteSnapshotView{" +
"key=" + key +
", value=" + value +
", metadata=" + metadata +
'}';
}
}
private static <K, V> void updateMetaParams(CacheEntry<K, V> entry, MetaParam.Writable[] metas) {
// TODO: Deal with entry instances that are MetaParamsCacheEntry and merge meta params
// e.g. check if meta params exist and if so, merge, but also check for old metadata
// information and merge it individually
Optional<EntryVersion> version = Optional.ofNullable(entry.getMetadata()).map(m -> m.version());
MetaParams metaParams = MetaParams.empty();
if (version.isPresent()) {
metaParams.add(new MetaParam.MetaEntryVersion(new FunctionalEntryVersionAdapter(version.get())));
}
if (metas.length != 0) {
metaParams.addMany(metas);
}
entry.setMetadata(MetaParamsInternalMetadata.from(metaParams));
}
private static <K, V> MetaParams extractMetaParams(CacheEntry<K, V> entry) {
// TODO: Deal with entry instances that are MetaParamsCacheEntry and merge meta params
// e.g. check if meta params exist and if so, merge, but also check for old metadata
// information and merge it individually
Metadata metadata = entry.getMetadata();
if (metadata instanceof MetaParamsInternalMetadata) {
MetaParamsInternalMetadata metaParamsMetadata = (MetaParamsInternalMetadata) metadata;
return metaParamsMetadata.params;
}
return MetaParams.empty();
}
public static final class ReadOnlySnapshotViewExternalizer implements AdvancedExternalizer<ReadOnlySnapshotView> {
@Override
public Set<Class<? extends ReadOnlySnapshotView>> getTypeClasses() {
return Util.asSet(ReadOnlySnapshotView.class);
}
@Override
public Integer getId() {
return Ids.READ_ONLY_SNAPSHOT_VIEW;
}
@Override
public void writeObject(ObjectOutput output, ReadOnlySnapshotView object) throws IOException {
output.writeObject(object.key);
output.writeObject(object.value);
output.writeObject(object.metadata);
}
@Override
public ReadOnlySnapshotView readObject(ObjectInput input) throws IOException, ClassNotFoundException {
Object key = input.readObject();
Object value = input.readObject();
Metadata metadata = (Metadata) input.readObject();
return new ReadOnlySnapshotView<>(key, value, metadata);
}
}
public static final class NoValueReadOnlyViewExternalizer implements AdvancedExternalizer<NoValueReadOnlyView> {
@Override
public Set<Class<? extends NoValueReadOnlyView>> getTypeClasses() {
return Util.asSet(NoValueReadOnlyView.class);
}
@Override
public Integer getId() {
return Ids.NO_VALUE_READ_ONLY_VIEW;
}
@Override
public void writeObject(ObjectOutput output, NoValueReadOnlyView object) throws IOException {
output.writeObject(object.key);
}
@Override
public NoValueReadOnlyView readObject(ObjectInput input) throws IOException, ClassNotFoundException {
return new NoValueReadOnlyView(input.readObject());
}
}
// Externalizer class defined outside of externalized class to avoid having
// to making externalized class public, since that would leak internal impl.
public static final class ReadWriteSnapshotViewExternalizer
extends AbstractExternalizer<ReadWriteSnapshotView> {
@Override
public Integer getId() {
return Ids.READ_WRITE_SNAPSHOT_VIEW;
}
@Override @SuppressWarnings("unchecked")
public Set<Class<? extends ReadWriteSnapshotView>> getTypeClasses() {
return Util.asSet(ReadWriteSnapshotView.class);
}
@Override
public void writeObject(ObjectOutput output, ReadWriteSnapshotView obj) throws IOException {
output.writeObject(obj.key);
output.writeObject(obj.value);
output.writeObject(obj.metadata);
}
@Override @SuppressWarnings("unchecked")
public ReadWriteSnapshotView readObject(ObjectInput input) throws IOException, ClassNotFoundException {
Object key = input.readObject();
Object value = input.readObject();
Metadata metadata = (Metadata) input.readObject();
return new ReadWriteSnapshotView(key, value, metadata);
}
}
}