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

com.speedment.runtime.core.internal.component.transaction.TransactionComponentImpl Maven / Gradle / Ivy

Go to download

A Speedment bundle that shades all dependencies into one jar. This is useful when deploying an application on a server.

The newest version!
/*
 *
 * Copyright (c) 2006-2019, Speedment, Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); You may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at:
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.speedment.runtime.core.internal.component.transaction;

import com.speedment.common.injector.State;
import com.speedment.common.injector.annotation.ExecuteBefore;
import com.speedment.common.injector.annotation.WithState;
import com.speedment.runtime.config.Dbms;
import com.speedment.runtime.core.component.ProjectComponent;
import com.speedment.runtime.core.component.connectionpool.ConnectionPoolComponent;
import com.speedment.runtime.core.component.connectionpool.PoolableConnection;
import com.speedment.runtime.core.component.transaction.DataSourceHandler;
import com.speedment.runtime.core.component.transaction.Isolation;
import com.speedment.runtime.core.component.transaction.TransactionComponent;
import com.speedment.runtime.core.component.transaction.TransactionHandler;
import com.speedment.runtime.core.db.SqlConsumer;
import com.speedment.runtime.core.exception.TransactionException;

import java.sql.SQLException;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Stream;

import static com.speedment.common.injector.State.STARTED;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toSet;

/**
 *
 * @author Per Minborg
 */
public class TransactionComponentImpl implements TransactionComponent {

    private final Map, DataSourceHandler> dataSourceHandlers;
    private final Map txObjects;
    private final Map> threadSets;
    private Dbms singleDbms;

    @ExecuteBefore(STARTED)
    void setupSingleDbms(@WithState(State.RESOLVED) ProjectComponent projectComponent) {
        final Set dbmses = projectComponent.getProject().dbmses().collect(toSet());
        if (dbmses.size() == 1) {
            singleDbms = dbmses.iterator().next();
        }
    }

    @ExecuteBefore(State.STARTED)
    void addDbmsDataSourceHandler(ConnectionPoolComponent connectionPoolComponent) {
        final Function extractor = dbms -> connectionPoolComponent.getConnection(dbms);
        final Consumer starter = wrapSqlException(c -> c.setAutoCommit(false), "setup connection");
        final BiFunction isolationConfigurator = (PoolableConnection c, Isolation newLevel) -> {
            final int previousLevel;
            try {
                previousLevel = c.getTransactionIsolation();
                c.setTransactionIsolation(newLevel.getSqlIsolationLevel());
            } catch (SQLException sqle) {
                throw new TransactionException("Unable to get/set isolation level for a connection " + c, sqle);
            }
            return Isolation.fromSqlIsolationLevel(previousLevel);
        };
        final Consumer committer = wrapSqlException(PoolableConnection::commit, "commit connection");
        final Consumer rollbacker = wrapSqlException(PoolableConnection::rollback, "rollback connection");
        final Consumer closer = wrapSqlException(c -> {
            c.setAutoCommit(true);
            c.close();
        }, "close connection");
        putDataSourceHandler(Dbms.class, DataSourceHandler.of(extractor, isolationConfigurator, starter, committer, rollbacker, closer));
    }

    public TransactionComponentImpl() {
        this.dataSourceHandlers = new ConcurrentHashMap<>();
        this.txObjects = new ConcurrentHashMap<>();
        this.threadSets = new ConcurrentHashMap<>();
    }

    @Override
    public TransactionHandler createTransactionHandler() {
        if (singleDbms == null) {
            throw new IllegalStateException("This project does not contain exactly one Dbms.");
        }
        return createTransactionHandler(singleDbms);
    }

    @Override
    public  TransactionHandler createTransactionHandler(T dataSource) {
        return new TransactionHandlerImpl(this, dataSource, findMapping(dataSource));
    }

    @Override
    @SuppressWarnings("unchecked")
    public  void putDataSourceHandler(Class dataSourceClass, DataSourceHandler dataSourceHandler) {
        dataSourceHandlers.put(dataSourceClass, (DataSourceHandler) dataSourceHandler);
    }

    @Override
    public void put(Thread thread, Object txObject) {
        if (txObjects.putIfAbsent(thread, txObject) != null) {
            throw new IllegalStateException(
                String.format("There is already a txObject associated with thread %s ", thread)
            );
        }
        threadSets.computeIfAbsent(
            txObject,
            (Object k) -> new HashSet<>()
        ).add(thread);
    }

    @Override
    public Optional get(Thread thread) {
        return Optional.ofNullable(txObjects.get(requireNonNull(thread)));
    }

    @Override
    public void remove(Thread thread) {
        final Object removedTxObject = txObjects.remove(requireNonNull(thread));
        if (removedTxObject != null) {
            final Set threadSet = threadSets.get(removedTxObject);
            threadSet.remove(thread);
            if (threadSet.isEmpty()) {
                // Clean up 
                threadSets.remove(removedTxObject);
            }
        }
    }

    @Override
    public Stream threads(Object txObject) {
        return Optional.ofNullable(threadSets.get(txObject))
            .map(Set::stream)
            .orElse(Stream.empty());
    }

    private DataSourceHandler findMapping(Object dataSource) {
        final Class originalClass = dataSource.getClass();
        {
            final DataSourceHandler dataSourceHandler = dataSourceHandlers.get(originalClass);
            if (dataSourceHandler != null) {
                return dataSourceHandler;
            }
        }
        for (Class interf : originalClass.getInterfaces()) {
            final DataSourceHandler dataSourceHandler = dataSourceHandlers.get(interf);
            if (dataSourceHandler != null) {
                dataSourceHandlers.put(interf, dataSourceHandler); // Enter this association to speed up next look-up
                return dataSourceHandler;
            }
        }
        Class superClass = originalClass.getSuperclass();
        while (superClass != null) {
            final DataSourceHandler dataSourceHandler = dataSourceHandlers.get(superClass);
            if (dataSourceHandler != null) {
                dataSourceHandlers.put(superClass, dataSourceHandler); // Enter this association to speed up next look-up
                return dataSourceHandler;
            }
            superClass = superClass.getSuperclass();
        }
        throw new IllegalArgumentException(
            String.format(
                "Unable to find a mapping for the data source %s of class %s. Available class mappings: ",
                dataSource,
                originalClass,
                dataSourceHandlers.keySet().stream().map(Object::toString).collect(joining(", "))
            )
        );
    }

    private  Consumer wrapSqlException(SqlConsumer sqlConsumer, String label) {
        return (T t) -> {
            try {
                sqlConsumer.accept(t);
            } catch (SQLException sqle) {
                throw new TransactionException("Unable to " + label + ": " + t, sqle);
            }
        };
    }

}