All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.bytex.snamp.cluster.HazelcastKeyValueStorage Maven / Gradle / Ivy

package com.bytex.snamp.cluster;

import com.bytex.snamp.Acceptor;
import com.bytex.snamp.Convert;
import com.bytex.snamp.EntryReader;
import com.bytex.snamp.core.KeyValueStorage;
import com.bytex.snamp.io.IOUtils;
import com.google.common.collect.ImmutableMap;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.core.IMap;

import java.io.*;
import java.util.*;
import java.util.function.Predicate;

/**
 * Represents non-persistent high-performance key/value storage backed by Hazelcast.
 * @author Roman Sakno
 * @version 2.0
 * @since 2.0
 */
final class HazelcastKeyValueStorage extends HazelcastSharedObject, Serializable>> implements KeyValueStorage {

    private static final class MapValue extends HashMap {
        private static final long serialVersionUID = 8695790321685285451L;

        private MapValue(final Map values) {
            super(values);
        }
    }

    private static final class HazelcastRecord implements Record, SerializableRecordView, TextRecordView, LongRecordView, DoubleRecordView, MapRecordView, JsonRecordView {
        private final Comparable recordKey;
        private final IMap, Serializable> distributedMap;
        private boolean detached;

        private HazelcastRecord(final IMap, Serializable> map, final Comparable key) {
            this.distributedMap = Objects.requireNonNull(map);
            this.recordKey = Objects.requireNonNull(key);
            detached = false;
        }

        private RuntimeException detachedException(){
            return new IllegalStateException(String.format("Record with key %s is detached", recordKey));
        }

        @Override
        public void refresh() {
            if (detached)
                throw detachedException();
            distributedMap.flush();
        }

        @Override
        public int getVersion() {
            if (detached)
                throw detachedException();
            return (int) distributedMap.getLocalMapStats().getPutOperationCount();
        }

        @Override
        public boolean isDetached() {
            return detached || (detached = distributedMap.containsKey(recordKey));
        }

        @Override
        public Serializable getValue() {
            final Serializable result = distributedMap.get(recordKey);
            if (result == null) {
                detached = true;
                throw detachedException();
            } else
                return result;
        }

        private boolean isInitialized() {
            return distributedMap.containsKey(recordKey);
        }

        @Override
        public void setValue(final Serializable value) {
            if (detached)
                throw detachedException();
            distributedMap.put(recordKey, value);
        }

        @Override
        public String getAsText() {
            return getValue().toString();
        }

        @Override
        public void setAsText(final String value) {
            setValue(value);
        }

        @Override
        public long getAsLong() {
            return Convert.toLong(getValue()).orElseThrow(NumberFormatException::new);
        }

        @Override
        public void accept(final long value) {
            setValue(value);
        }

        @Override
        public double getAsDouble() {
            return Convert.toDouble(getValue()).orElseThrow(NumberFormatException::new);
        }

        @Override
        public void accept(final double value) {
            setValue(value);
        }

        @Override
        public MapValue getAsMap() {
            final Serializable value = getValue();
            return value instanceof MapValue ?
                    (MapValue) value :
                    new MapValue(ImmutableMap.of("value", value));
        }

        @Override
        public void setAsMap(final Map values) {
            setValue(new MapValue(values));
        }

        @Override
        public Reader getAsJson() {
            return new StringReader(getAsText());
        }

        @Override
        public void setAsJson(final Reader value) throws IOException {
            setAsText(IOUtils.toString(value));
        }

        @Override
        public StringWriter createJsonWriter() {
            return new StringWriter(1024){
                @Override
                public void close() throws IOException {
                    setAsText(getBuffer().toString());
                    super.close();
                }
            };
        }
    }

    HazelcastKeyValueStorage(final HazelcastInstance hazelcast, final String name) {
        super(hazelcast, name, HazelcastInstance::getMap);
    }

    /**
     * Gets record associated with the specified key.
     *
     * @param key        The key of the record. Cannot be {@literal null}.
     * @param recordView Type of the record representation.
     * @return Selector for records in this storage.
     * @throws ClassCastException Unsupported record view.
     */
    @Override
    public  Optional getRecord(final Comparable key, final Class recordView) {
        final HazelcastRecord record = getDistributedObject().containsKey(key) ? new HazelcastRecord(getDistributedObject(), key) : null;
        return Optional.ofNullable(record).map(recordView::cast);
    }

    /**
     * Gets record associated with the specified key.
     *
     * @param key         The key of the record.
     * @param recordView  Type of the record representation.
     * @param initializer A function used to initialize record for the first time when it is created.
     * @return Existing or newly created record.
     * @throws E Unable to initialize record.
     */
    @Override
    public  R getOrCreateRecord(final Comparable key, final Class recordView, final Acceptor initializer) throws E {
        final HazelcastRecord record = new HazelcastRecord(getDistributedObject(), key);
        if (!record.isInitialized()) {
            getDistributedObject().lock(key);
            try {
                if (!record.isInitialized())
                    initializer.accept(recordView.cast(record));
            } finally {
                getDistributedObject().unlock(key);
            }
        }
        return recordView.cast(record);
    }

    /**
     * Updates or creates record associated with the specified key.
     *
     * @param key        The key of the record.
     * @param recordView Type of the record representation.
     * @param updater    Record updater.
     * @throws E Unable to update record.
     */
    @Override
    public  void updateOrCreateRecord(final Comparable key, final Class recordView, final Acceptor updater) throws E {
        updater.accept(recordView.cast(new HazelcastRecord(getDistributedObject(), key)));
    }

    /**
     * Deletes the record associated with key.
     *
     * @param key The key to remove.
     * @return {@literal true}, if record was exist; otherwise, {@literal false}.
     */
    @Override
    public boolean delete(final Comparable key) {
        return getDistributedObject().remove(key) != null;
    }

    /**
     * Gets all keys in this storage.
     *
     * @return All keys in this storage.
     */
    @Override
    public Set> keySet() {
        return getDistributedObject().keySet();
    }

    /**
     * Determines whether the record of the specified key exists.
     *
     * @param key The key to check.
     * @return {@literal true}, if record exists; otherwise, {@literal false}.
     */
    @Override
    public boolean exists(final Comparable key) {
        return getDistributedObject().containsKey(key);
    }

    /**
     * Iterates over records.
     *
     * @param recordType Type of the record representation.
     * @param filter     Query filter. Cannot be {@literal null}.
     * @param reader     Record reader. Cannot be {@literal null}.
     * @throws E Reading failed.
     */
    @Override
    public  void forEachRecord(final Class recordType, final Predicate> filter, final EntryReader, ? super R, E> reader) throws E {
        for (final Comparable key : getDistributedObject().keySet())
            if (filter.test(key)) {
                getDistributedObject().lock(key);
                try {
                    if (!reader.accept(key, recordType.cast(new HazelcastRecord(getDistributedObject(), key))))
                        return;
                } finally {
                    getDistributedObject().unlock(key);
                }
            }
    }

    /**
     * Removes all record.
     */
    @Override
    public void clear() {
        getDistributedObject().clear();
    }

    /**
     * Determines whether this storage supports transactions.
     *
     * @return {@literal true} if transactions are supported; otherwise, {@literal false}.
     */
    @Override
    public boolean isTransactional() {
        return false;
    }

    @Override
    public TransactionScope beginTransaction(final IsolationLevel level) throws UnsupportedOperationException {
        throw new UnsupportedOperationException("Transaction is not supported");
    }

    @Override
    public boolean isViewSupported(final Class recordView) {
        return recordView.isAssignableFrom(HazelcastRecord.class);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy