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

com.yahoo.elide.datastores.multiplex.MultiplexWriteTransaction Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2015, Yahoo Inc.
 * Licensed under the Apache License, Version 2.0
 * See LICENSE file in project root for terms.
 */
package com.yahoo.elide.datastores.multiplex;

import com.yahoo.elide.core.RequestScope;
import com.yahoo.elide.core.datastore.DataStore;
import com.yahoo.elide.core.datastore.DataStoreIterable;
import com.yahoo.elide.core.datastore.DataStoreIterableBuilder;
import com.yahoo.elide.core.datastore.DataStoreTransaction;
import com.yahoo.elide.core.dictionary.EntityDictionary;
import com.yahoo.elide.core.exceptions.HttpStatusException;
import com.yahoo.elide.core.exceptions.TransactionException;
import com.yahoo.elide.core.request.EntityProjection;
import com.yahoo.elide.core.request.Relationship;
import com.yahoo.elide.core.type.Type;

import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Map.Entry;

/**
 * Multiplex transaction handler.
 */
public class MultiplexWriteTransaction extends MultiplexTransaction {
    private static final Object NEWLY_CREATED_OBJECT = new Object();
    private final IdentityHashMap clonedObjects = new IdentityHashMap<>();
    private final Map> dirtyObjects = new HashMap<>();

    public MultiplexWriteTransaction(MultiplexManager multiplexManager) {
        super(multiplexManager);
    }

    @Override
    protected DataStoreTransaction beginTransaction(DataStore dataStore) {
        // begin updating transaction
        return dataStore.beginTransaction();
    }

    @Override
    public  void save(T entity, RequestScope requestScope) {
        Type entityType = EntityDictionary.getType(entity);
        getTransaction(entityType).save(entity, requestScope);
        add(this.multiplexManager.getSubManager(entityType), entity);
    }

    @Override
    public  void delete(T entity, RequestScope requestScope) {
        Type entityType = EntityDictionary.getType(entity);
        getTransaction(entityType).delete(entity, requestScope);
        add(this.multiplexManager.getSubManager(entityType), entity);
    }

    protected void add(DataStore dataStore, Object entity) {
        dirtyObjects.computeIfAbsent(dataStore, key -> new ArrayList<>()).add(entity);
    }

    @Override
    public void commit(RequestScope scope) {
        // flush all before commits
        flush(scope);

        List commitList = new ArrayList<>();

        // Transactions must be committed in reverse order
        ListIterator> iterator = new ArrayList<>(transactions.entrySet())
                .listIterator(transactions.size());
        while (iterator.hasPrevious()) {
            Entry entry = iterator.previous();
            try {
                entry.getValue().commit(scope);
                if (this.multiplexManager.isApplyCompensatingTransactions(entry.getKey())) {
                    commitList.add(entry.getKey());
                }
            } catch (HttpStatusException e) {
                reverseTransactions(commitList, e, scope);
                throw e;
            } catch (Error | RuntimeException e) {
                if ("jakarta.ws.rs.WebApplicationException".equals(e.getClass().getCanonicalName())) {
                    reverseTransactions(commitList, e, scope);
                    throw e;
                }
                TransactionException transactionException = new TransactionException(e);
                reverseTransactions(commitList, transactionException, scope);
                throw transactionException;
            }
        }
    }

    /**
     * Attempt to reverse changes of last commit since not all transactions successfully committed.
     * @param restoreList List of database managers to reverse the last commit
     * @param cause cause to add any suppressed exceptions
     */
    private void reverseTransactions(List restoreList, Throwable cause, RequestScope requestScope) {
        for (DataStore dataStore : restoreList) {
            try (DataStoreTransaction transaction = dataStore.beginTransaction()) {
                List list = dirtyObjects.get(dataStore);
                for (Object dirtyObject : list == null ? Collections.emptyList() : list) {
                    Object cloned = clonedObjects.get(dirtyObject);
                    if (cloned == NEWLY_CREATED_OBJECT) {
                        transaction.delete(dirtyObject, requestScope);
                    } else {
                        // If cloned is null this is an update to an object that wasn't created yet
                        if (cloned != null) {
                            transaction.save(cloned, requestScope);
                        }
                    }
                }
                transaction.commit(requestScope);
            } catch (RuntimeException | IOException e) {
                cause.addSuppressed(e);
            }
        }
    }

    @SuppressWarnings("resource")
    @Override
    public  void createObject(T entity, RequestScope scope) {
        DataStoreTransaction transaction = getTransaction(EntityDictionary.getType(entity));
        transaction.createObject(entity, scope);
        // mark this object as newly created to be deleted on reverse transaction
        clonedObjects.put(entity, NEWLY_CREATED_OBJECT);
    }

    private  DataStoreIterable hold(DataStoreTransaction transaction, DataStoreIterable list) {
        ArrayList newList = new ArrayList<>();
        list.forEach(newList::add);
        for (T object : newList) {
            hold(transaction, object);
        }
        return new DataStoreIterableBuilder(newList)
                .paginateInMemory(list.needsInMemoryPagination())
                .filterInMemory(list.needsInMemoryFilter())
                .sortInMemory(list.needsInMemorySort())
                .build();
    }

    /**
     * Save cloned copy of object for possible reverse transaction.
     * @param subTransaction database sub-transaction
     * @param object entity to clone
     * @return original object
     */
    private  T hold(DataStoreTransaction subTransaction, T object) {
        clonedObjects.put(object, cloneObject(object));
        return object;
    }

    /**
     *  Clone contents of object for possible reverse transaction.
     */
    private Object cloneObject(Object object) {
        if (object == null) {
            return null;
        }

        Type cls = multiplexManager.getDictionary().lookupBoundClass(EntityDictionary.getType(object));
        return this.multiplexManager.objectCloner.clone(object, cls);
    }

    @Override
    public  T loadObject(EntityProjection projection,
                             Serializable id,
                             RequestScope scope) {
        DataStoreTransaction transaction = getTransaction(projection.getType());
        return hold(transaction, (T) transaction.loadObject(projection, id, scope));
    }

    @Override
    public  DataStoreIterable loadObjects(EntityProjection projection, RequestScope scope) {
        DataStoreTransaction transaction = getTransaction(projection.getType());
        return hold(transaction, transaction.loadObjects(projection, scope));
    }

    @Override
    public  DataStoreIterable getToManyRelation(DataStoreTransaction relationTx,
                                                         T entity,
                                                         Relationship relationship,
                                                         RequestScope scope) {
        DataStoreTransaction transaction = getTransaction(EntityDictionary.getType(entity));
        DataStoreIterable relation = super.getToManyRelation(relationTx, entity, relationship, scope);

        return hold(transaction, relation);
    }

    @Override
    public  R getToOneRelation(DataStoreTransaction relationTx,
                                     T entity,
                                     Relationship relationship,
                                     RequestScope scope) {
        DataStoreTransaction transaction = getTransaction(EntityDictionary.getType(entity));
        R relation = super.getToOneRelation(relationTx, entity, relationship, scope);

        return hold(transaction, relation);
    }
}