com.toro.torod.connection.DefaultToroTransaction.java Source code

Java tutorial

Introduction

Here is the source code for com.toro.torod.connection.DefaultToroTransaction.java

Source

/*
 *     This file is part of ToroDB.
 *
 *     ToroDB is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU Affero General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 *
 *     ToroDB is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU Affero General Public License for more details.
 *
 *     You should have received a copy of the GNU Affero General Public License
 *     along with ToroDB. If not, see <http://www.gnu.org/licenses/>.
 *
 *     Copyright (c) 2014, 8Kdata Technology
 *     
 */

package com.toro.torod.connection;

import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

import javax.annotation.Nonnull;

import com.google.common.base.Function;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.Iterators;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import com.torodb.kvdocument.values.KVBoolean;
import com.torodb.kvdocument.values.KVDouble;
import com.torodb.kvdocument.values.KVInteger;
import com.torodb.kvdocument.values.KVLong;
import com.torodb.kvdocument.values.KVNull;
import com.torodb.kvdocument.values.KVValue;
import com.torodb.kvdocument.values.heap.ByteArrayKVMongoObjectId;
import com.torodb.kvdocument.values.heap.ByteSourceKVBinary;
import com.torodb.kvdocument.values.heap.DefaultKVMongoTimestamp;
import com.torodb.kvdocument.values.heap.ListKVArray;
import com.torodb.kvdocument.values.heap.LocalDateKVDate;
import com.torodb.kvdocument.values.heap.LocalTimeKVTime;
import com.torodb.kvdocument.values.heap.LongKVInstant;
import com.torodb.kvdocument.values.heap.StringKVString;
import com.torodb.torod.core.ValueRow;
import com.torodb.torod.core.ValueRow.TranslatorValueRow;
import com.torodb.torod.core.WriteFailMode;
import com.torodb.torod.core.connection.DeleteResponse;
import com.torodb.torod.core.connection.InsertResponse;
import com.torodb.torod.core.connection.ToroTransaction;
import com.torodb.torod.core.connection.UpdateResponse;
import com.torodb.torod.core.connection.WriteError;
import com.torodb.torod.core.connection.exceptions.RetryTransactionException;
import com.torodb.torod.core.d2r.D2RTranslator;
import com.torodb.torod.core.dbMetaInf.DbMetaInformationCache;
import com.torodb.torod.core.exceptions.ToroImplementationException;
import com.torodb.torod.core.exceptions.ToroRuntimeException;
import com.torodb.torod.core.exceptions.UserToroException;
import com.torodb.torod.core.executor.SessionExecutor;
import com.torodb.torod.core.executor.SessionTransaction;
import com.torodb.torod.core.executor.ToroTaskExecutionException;
import com.torodb.torod.core.language.operations.DeleteOperation;
import com.torodb.torod.core.language.operations.UpdateOperation;
import com.torodb.torod.core.language.querycriteria.QueryCriteria;
import com.torodb.torod.core.language.querycriteria.utils.EqualFactory;
import com.torodb.torod.core.language.update.UpdateAction;
import com.torodb.torod.core.language.update.utils.UpdateActionVisitor;
import com.torodb.torod.core.pojos.Database;
import com.torodb.torod.core.pojos.IndexedAttributes;
import com.torodb.torod.core.pojos.NamedToroIndex;
import com.torodb.torod.core.subdocument.SplitDocument;
import com.torodb.torod.core.subdocument.ToroDocument;
import com.torodb.torod.core.subdocument.values.ScalarArray;
import com.torodb.torod.core.subdocument.values.ScalarBinary;
import com.torodb.torod.core.subdocument.values.ScalarBoolean;
import com.torodb.torod.core.subdocument.values.ScalarDate;
import com.torodb.torod.core.subdocument.values.ScalarDouble;
import com.torodb.torod.core.subdocument.values.ScalarInstant;
import com.torodb.torod.core.subdocument.values.ScalarInteger;
import com.torodb.torod.core.subdocument.values.ScalarLong;
import com.torodb.torod.core.subdocument.values.ScalarMongoObjectId;
import com.torodb.torod.core.subdocument.values.ScalarMongoTimestamp;
import com.torodb.torod.core.subdocument.values.ScalarNull;
import com.torodb.torod.core.subdocument.values.ScalarString;
import com.torodb.torod.core.subdocument.values.ScalarTime;
import com.torodb.torod.core.subdocument.values.ScalarValue;
import com.torodb.torod.core.subdocument.values.ScalarValueVisitor;

/**
 *
 */
public class DefaultToroTransaction implements ToroTransaction {

    private final DbMetaInformationCache cache;
    private final SessionTransaction sessionTransaction;
    private final D2RTranslator d2r;
    private final SessionExecutor executor;

    DefaultToroTransaction(DbMetaInformationCache cache, SessionTransaction sessionTransaction, D2RTranslator d2r,
            SessionExecutor executor) {
        this.cache = cache;
        this.sessionTransaction = sessionTransaction;
        this.d2r = d2r;
        this.executor = executor;
    }

    @Override
    public void close() {
        sessionTransaction.close();
    }

    @Override
    public ListenableFuture<?> rollback() {
        return sessionTransaction.rollback();
    }

    @Override
    public ListenableFuture<?> commit() {
        return sessionTransaction.commit();
    }

    @Override
    public ListenableFuture<InsertResponse> insertDocuments(String collection,
            FluentIterable<ToroDocument> documents, WriteFailMode mode) {

        try {

            List<SplitDocument> documentsList = Lists.newArrayList();

            Function<ToroDocument, SplitDocument> toRelationFun = d2r.getToRelationalFunction(executor, collection);
            for (ToroDocument document : documents) {
                documentsList.add(toRelationFun.apply(document));
            }

            return sessionTransaction.insertSplitDocuments(collection, documentsList, mode);

        } catch (ToroTaskExecutionException ex) {
            //TODO: Change exceptions
            throw new RuntimeException(ex);
        }
    }

    @Override
    public ListenableFuture<DeleteResponse> delete(@Nonnull String collection,
            @Nonnull List<? extends DeleteOperation> deletes, @Nonnull WriteFailMode mode) {
        cache.createCollection(executor, collection, null);
        return sessionTransaction.delete(collection, deletes, mode);
    }

    @Override
    public ListenableFuture<NamedToroIndex> createIndex(String collection, String indexName,
            IndexedAttributes attributes, boolean unique, boolean blocking) {
        cache.createCollection(executor, collection, null);
        return sessionTransaction.createIndex(collection, indexName, attributes, unique, blocking);
    }

    @Override
    public ListenableFuture<Boolean> dropIndex(String collection, String indexName) {
        cache.createCollection(executor, collection, null);
        return sessionTransaction.dropIndex(collection, indexName);
    }

    @Override
    public Collection<? extends NamedToroIndex> getIndexes(String collection) {
        try {
            cache.createCollection(executor, collection, null);
            return sessionTransaction.getIndexes(collection).get();
        } catch (InterruptedException | ExecutionException ex) {
            throw new ToroRuntimeException(ex);
        }
    }

    @Override
    public ListenableFuture<Long> getIndexSize(String collection, String indexName) {
        cache.createCollection(executor, collection, null);
        return sessionTransaction.getIndexSize(collection, indexName);
    }

    @Override
    public ListenableFuture<Long> getCollectionSize(String collection) {
        cache.createCollection(executor, collection, null);
        return sessionTransaction.getCollectionSize(collection);
    }

    @Override
    public ListenableFuture<Long> getDocumentsSize(String collection) {
        cache.createCollection(executor, collection, null);
        return sessionTransaction.getDocumentsSize(collection);
    }

    @Override
    public ListenableFuture<Integer> count(String collection, QueryCriteria query) {
        cache.createCollection(executor, collection, null);
        return sessionTransaction.count(collection, query);
    }

    @Override
    public ListenableFuture<List<? extends Database>> getDatabases() {
        return sessionTransaction.getDatabases();
    }

    @Override
    public ListenableFuture<Integer> createPathViews(String collection) throws UnsupportedOperationException {
        return sessionTransaction.createPathViews(collection);
    }

    @Override
    public ListenableFuture<Void> dropPathViews(String collection) throws UnsupportedOperationException {
        return sessionTransaction.dropPathViews(collection);
    }

    @Override
    public ListenableFuture<Iterator<ValueRow<KVValue<?>>>> sqlSelect(String sqlQuery)
            throws UnsupportedOperationException, UserToroException {
        ListenableFuture<Iterator<ValueRow<ScalarValue<?>>>> valueFuture = sessionTransaction.sqlSelect(sqlQuery);

        IteratorTranslator<ValueRow<ScalarValue<?>>, ValueRow<KVValue<?>>> iteratorTranslator = new IteratorTranslator<>(
                TranslatorValueRow.getBuilderFunction(new ToDocValueFunction()));

        return Futures.transform(valueFuture, iteratorTranslator, MoreExecutors.directExecutor());
    }

    @Override
    public ListenableFuture<UpdateResponse> update(String collection, List<? extends UpdateOperation> updates,
            WriteFailMode mode, UpdateActionVisitor<ToroDocument, Void> updateActionVisitorDocumentToInsert,
            UpdateActionVisitor<UpdatedToroDocument, ToroDocument> updateActionVisitorDocumentToUpdate) {
        cache.createCollection(executor, collection, null);
        UpdateResponse.Builder builder = new UpdateResponse.Builder();
        for (int updateIndex = 0; updateIndex < updates.size(); updateIndex++) {
            UpdateOperation update = updates.get(updateIndex);

            try {
                update(collection, update, updateIndex, updateActionVisitorDocumentToInsert,
                        updateActionVisitorDocumentToUpdate, builder);
            } catch (InterruptedException | ExecutionException | RetryTransactionException ex) {
                return Futures.immediateFailedCheckedFuture(ex);
            } catch (UserToroException ex) {
                builder.addError(new WriteError(updateIndex, -1, ex.getLocalizedMessage()));
            }
        }

        return Futures.immediateCheckedFuture(builder.build());
    }

    private void update(String collection, UpdateOperation update, int updateIndex,
            UpdateActionVisitor<ToroDocument, Void> updateActionVisitorDocumentToInsert,
            UpdateActionVisitor<UpdatedToroDocument, ToroDocument> updateActionVisitorDocumentToUpdate,
            UpdateResponse.Builder responseBuilder)
            throws InterruptedException, ExecutionException, RetryTransactionException {
        List<ToroDocument> candidates = sessionTransaction.readAll(collection, update.getQuery()).get();

        responseBuilder.addCandidates(candidates.size());

        if (candidates.isEmpty() && update.isInsertIfNotFound()) {
            ToroDocument documentToInsert = documentToInsert(update.getAction(),
                    updateActionVisitorDocumentToInsert);
            Future<InsertResponse> insertFuture = insertDocuments(collection,
                    FluentIterable.from(Collections.singleton(documentToInsert)), WriteFailMode.TRANSACTIONAL);
            //as we are using a synchronized update, we need to wait until the insert is executed
            insertFuture.get();
            responseBuilder.addInsertedDocument(documentToInsert, updateIndex);
            responseBuilder.addCandidates(1);
        }
        Set<ToroDocument> objectsToDelete = Sets.newHashSet();
        Set<ToroDocument> objectsToInsert = Sets.newHashSet();
        for (ToroDocument candidate : candidates) {
            UpdatedToroDocument documentUpdated = documentUpdated(update.getAction(), candidate,
                    updateActionVisitorDocumentToUpdate);
            if (documentUpdated.isUpdated()) {
                responseBuilder.incrementModified(1);

                objectsToDelete.add(candidate);
                objectsToInsert.add(documentUpdated);

                if (update.isJustOne()) {
                    break;
                }
            }
        }
        if (!objectsToDelete.isEmpty()) {
            List<DeleteOperation> deletes = Lists.newArrayListWithCapacity(objectsToDelete.size());
            for (ToroDocument objectToDelete : objectsToDelete) {
                deletes.add(new DeleteOperation(EqualFactory.createEquality(objectToDelete), true));
            }
            DeleteResponse deleteResponse = delete(collection, deletes, WriteFailMode.TRANSACTIONAL).get();
            if (deleteResponse.getDeleted() != objectsToDelete.size()) {
                throw new RetryTransactionException("Update: " + objectsToDelete.size() + " should be deleted, but "
                        + deleteResponse.getDeleted() + " objects have been " + "deleted instead");
            }
        }
        if (!objectsToInsert.isEmpty()) {
            InsertResponse insertResponse = insertDocuments(collection, FluentIterable.from(objectsToInsert),
                    WriteFailMode.TRANSACTIONAL).get();
            if (insertResponse.getInsertedDocsCounter() != objectsToInsert.size()) {
                throw new ToroImplementationException("Update: " + objectsToInsert.size()
                        + " should be inserted, but " + insertResponse.getInsertedDocsCounter() + " objects have "
                        + "been inserted instead");
            }
        }

    }

    private ToroDocument documentToInsert(UpdateAction action,
            UpdateActionVisitor<ToroDocument, Void> updateActionVisitorDocumentToInsert) {
        return action.accept(updateActionVisitorDocumentToInsert, null);
    }

    private UpdatedToroDocument documentUpdated(UpdateAction action, ToroDocument candidate,
            UpdateActionVisitor<UpdatedToroDocument, ToroDocument> updateActionVisitorDocumentToUpdate) {
        return action.accept(updateActionVisitorDocumentToUpdate, candidate);
    }

    private static class IteratorTranslator<I, O> implements Function<Iterator<I>, Iterator<O>> {

        private final Function<I, O> function;

        private IteratorTranslator(Function<I, O> function) {
            this.function = function;
        }

        @Override
        public Iterator<O> apply(@Nonnull Iterator<I> input) {
            return Iterators.transform(input, function);
        }
    }

    private static class ToDocValueFunction
            implements Function<ScalarValue<?>, KVValue<?>>, ScalarValueVisitor<KVValue<?>, Void> {

        @Override
        public KVValue<?> apply(@Nonnull ScalarValue<?> input) {
            return (KVValue<?>) input.accept(this, null);
        }

        @Override
        public KVValue<?> visit(ScalarBoolean value, Void arg) {
            if (value.getValue()) {
                return KVBoolean.TRUE;
            }
            return KVBoolean.FALSE;
        }

        @Override
        public KVValue<?> visit(ScalarNull value, Void arg) {
            return KVNull.getInstance();
        }

        @Override
        public KVValue<?> visit(ScalarArray value, Void arg) {
            return new ListKVArray(Lists.newArrayList(Iterators.transform(value.iterator(), this)));
        }

        @Override
        public KVValue<?> visit(ScalarInteger value, Void arg) {
            return KVInteger.of(value.getValue());
        }

        @Override
        public KVValue<?> visit(ScalarLong value, Void arg) {
            return KVLong.of(value.longValue());
        }

        @Override
        public KVValue<?> visit(ScalarDouble value, Void arg) {
            return KVDouble.of(value.doubleValue());
        }

        @Override
        public KVValue<?> visit(ScalarString value, Void arg) {
            return new StringKVString(value.getValue());
        }

        @Override
        public KVValue<?> visit(ScalarMongoObjectId value, Void arg) {
            return new ByteArrayKVMongoObjectId(value.getArrayValue());
        }

        @Override
        public KVValue<?> visit(ScalarMongoTimestamp value, Void arg) {
            return new DefaultKVMongoTimestamp(value.getSecondsSinceEpoch(), value.getOrdinal());
        }

        @Override
        public KVValue<?> visit(ScalarInstant value, Void arg) {
            return new LongKVInstant(value.getMillisFromUnix());
        }

        @Override
        public KVValue<?> visit(ScalarDate value, Void arg) {
            return new LocalDateKVDate(value.getValue());
        }

        @Override
        public KVValue<?> visit(ScalarTime value, Void arg) {
            return new LocalTimeKVTime(value.getValue());
        }

        @Override
        public KVValue<?> visit(ScalarBinary value, Void arg) {
            return new ByteSourceKVBinary(value.getSubtype(), value.getCategory(), value.getByteSource());
        }

    }
}