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

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

package com.bytex.snamp.cluster;

import com.bytex.snamp.Acceptor;
import com.bytex.snamp.EntryReader;
import com.bytex.snamp.core.KeyValueStorage;
import com.orientechnologies.orient.core.db.ODatabase;
import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal;
import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx;
import com.orientechnologies.orient.core.db.record.OIdentifiable;
import com.orientechnologies.orient.core.id.ORID;
import com.orientechnologies.orient.core.index.OCompositeKey;
import com.orientechnologies.orient.core.index.OIndex;
import com.orientechnologies.orient.core.index.OIndexKeyCursor;
import com.orientechnologies.orient.core.iterator.ORecordIteratorClass;
import com.orientechnologies.orient.core.metadata.schema.OClass;
import com.orientechnologies.orient.core.metadata.schema.OSchema;
import com.orientechnologies.orient.core.record.impl.ODocument;
import com.orientechnologies.orient.core.sql.OCommandSQL;
import com.orientechnologies.orient.core.tx.OTransaction;
import com.orientechnologies.orient.core.tx.OTransactionOptimistic;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import java.util.function.Predicate;

/**
 * Represents key/value storage backed by OrientDB.
 * @author Roman Sakno
 * @version 2.0
 * @since 2.0
 */
final class OrientKeyValueStorage extends GridSharedObject implements KeyValueStorage {
    private static final class TransactionScopeImpl extends OTransactionOptimistic implements TransactionScope {
        private TransactionScopeImpl(final ODatabaseDocumentTx iDatabase) {
            super(iDatabase);
        }
    }

    private final AtomicReference documentClass;
    private final ODatabaseDocumentTx database;
    private final String indexName;

    OrientKeyValueStorage(final ODatabaseDocumentTx database,
                          final String collectionName,
                          final boolean forceCreate) {
        if (!database.isActiveOnCurrentThread())
            database.activateOnCurrentThread();
        this.database = database;
        indexName = collectionName + "Index";
        //init class
        final OSchema schema = database.getMetadata().getSchema();
        final OClass documentClass;
        if (schema.existsClass(collectionName))
            documentClass = schema.getClass(collectionName);
        else if (forceCreate) {
            documentClass = schema.createClass(collectionName);
            PersistentFieldDefinition.defineFields(documentClass);
            PersistentFieldDefinition.createIndex(documentClass, indexName);
        } else
            documentClass = null;
        this.documentClass = new AtomicReference<>(documentClass);
        ODatabaseRecordThreadLocal.INSTANCE.remove();
    }

    private OClass getDocumentClass() {
        final OClass documentClass = this.documentClass.get();
        if (documentClass == null)
            throw objectIsDestroyed();
        else
            return documentClass;
    }

    private  V getRecord(final Comparable indexKey, final Function transform) {
        final OClass documentClass = getDocumentClass();
        final OIdentifiable recordId = DBUtils.supplyWithDatabase(database, () -> (OIdentifiable) documentClass.getClassIndex(indexName).get(PersistentFieldDefinition.getCompositeKey(indexKey)));
        return recordId == null ? null : transform.apply(recordId);
    }

    @Override
    public String getName() {
        return getDocumentClass().getName();
    }

    /**
     * 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 PersistentRecord record = getRecord(key, PersistentRecord::new);
        if (record != null) {
            record.setDatabase(database);
            record.setClassName(getDocumentClass().getName());
            return database.load(record) == null ? Optional.empty() : Optional.of(record).map(recordView::cast);
        } else
            return Optional.empty();
    }

    /**
     * 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 {
        PersistentRecord record = getRecord(key, PersistentRecord::new);
        final boolean isNew;
        if (isNew = record == null) {
            record = new PersistentRecord();
            record.setKey(key);
        }
        record.setDatabase(database);
        record.setClassName(getDocumentClass().getName());
        if (isNew) {
            //new record detected
            initializer.accept(recordView.cast(record));
            record.save();
        } else
            database.reload(record);
        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 {
        PersistentRecord record = getRecord(key, PersistentRecord::new);
        final boolean isNew;
        if (isNew = record == null) {
            record = new PersistentRecord();
            record.setKey(key);
        }
        record.setDatabase(database);
        record.setClassName(getDocumentClass().getName());
        if (!isNew)
            database.reload(record);
        updater.accept(recordView.cast(record));
    }

    /**
     * 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) {
        final ORID recordId = getRecord(key, OIdentifiable::getIdentity);
        final boolean success;
        if (success = recordId != null)
            DBUtils.runWithDatabase(database, () -> database.delete(recordId, ODatabase.OPERATION_MODE.SYNCHRONOUS));
        return success;
    }

    /**
     * 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 getRecord(key, OIdentifiable::getIdentity) != null;
    }

    /**
     * Removes all record.
     */
    @Override
    public void clear() {
        final OClass documentClass = this.getDocumentClass();
        DBUtils.runWithDatabase(database, () -> {
            try {
                documentClass.truncate();
            } catch (final IOException e) {
                throw new UncheckedIOException(e);
            }
        });
    }

    private void destroyImpl() { 
        final OClass documentClass = this.documentClass.getAndSet(null);
        if (documentClass != null) {
            database.command(new OCommandSQL(String.format("drop class %s", documentClass.getName()))).execute(); //remove class
            database.command(new OCommandSQL(String.format("drop index %s", indexName))).execute();       //remove indexes
        }
    }

    @Override
    void destroy() {
        DBUtils.runWithDatabase(database, this::destroyImpl);
    }

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

    private static  void forEachRecord(final ODatabaseDocumentTx database,
                                                                               final OClass documentClass,
                                                                               final Class recordType,
                                                                               final Predicate> filter,
                                                                               final EntryReader, ? super R, E> reader) throws E{
        final ORecordIteratorClass records = database.browseClass(documentClass.getName());
        while (records.hasNext()){
            final ODocument document = records.next();
            final PersistentRecord record;
            if(document instanceof PersistentRecord)
                record = (PersistentRecord) document;
            else {
                record = new PersistentRecord(document);
                database.reload(record);
            }
            record.lock(false);
            try {
                final Comparable key;
                if (filter.test(key = record.getKey()))
                    if(!reader.accept(key, recordType.cast(record)))
                        return;
            } finally {
                record.unlock();
            }
        }
    }

    /**
     * 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 {
        final OClass documentClass = getDocumentClass();
        DBUtils.acceptWithDatabase(database, database -> forEachRecord(database, documentClass, recordType, filter, reader));
    }

    private static Set> keySet(final OClass documentClass, final String indexName){
        final OIndex index = documentClass.getClassIndex(indexName);
        final OIndexKeyCursor cursor = index.keyCursor();
        Object key;
        final Set> result = new HashSet<>(15);
        while ((key = cursor.next(5)) instanceof OCompositeKey) {
            final OCompositeKey compositeKey = (OCompositeKey) key;
            compositeKey.getKeys().stream()
                    .filter(k -> k instanceof Comparable)
                    .map(k -> (Comparable) k)
                    .findFirst()
                    .ifPresent(result::add);
        }
        return result;
    }

    /**
     * Gets all keys in this storage.
     *
     * @return All keys in this storage.
     */
    @Override
    public Set> keySet() {
        final OClass documentClass = getDocumentClass();
        return DBUtils.supplyWithDatabase(database, () -> keySet(documentClass, indexName));
    }

    /**
     * Starts transaction.
     *
     * @param level The required level of transaction.
     * @return A new transaction scope.
     */
    @Override
    public TransactionScope beginTransaction(final IsolationLevel level) {
        getDocumentClass();
        final TransactionScopeImpl transaction = new TransactionScopeImpl(database);
        switch (level) {
            case READ_COMMITTED:
                transaction.setIsolationLevel(OTransaction.ISOLATION_LEVEL.READ_COMMITTED);
                break;
            case REPEATABLE_READ:
                transaction.setIsolationLevel(OTransaction.ISOLATION_LEVEL.REPEATABLE_READ);
                break;
            default:
                throw new IllegalArgumentException(String.format("Unsupported isolation level %s", level));
        }
        transaction.begin();
        return transaction;
    }

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




© 2015 - 2025 Weber Informatics LLC | Privacy Policy