com.mmnaseri.dragonfly.data.impl.DefaultDataAccessSession.java Source code

Java tutorial

Introduction

Here is the source code for com.mmnaseri.dragonfly.data.impl.DefaultDataAccessSession.java

Source

/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2013 Milad Naseri.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

package com.mmnaseri.dragonfly.data.impl;

import com.mmnaseri.couteau.basics.api.Processor;
import com.mmnaseri.dragonfly.data.DataAccessSession;
import com.mmnaseri.dragonfly.data.DataStructureHandler;
import com.mmnaseri.dragonfly.dialect.DatabaseDialect;
import com.mmnaseri.dragonfly.error.DataAccessSessionAlreadyInitializedError;
import com.mmnaseri.dragonfly.error.DataAccessSessionInitializationError;
import com.mmnaseri.dragonfly.error.DatabaseConnectionError;
import com.mmnaseri.dragonfly.error.DatabaseDriverNotFoundError;
import com.mmnaseri.dragonfly.metadata.TableMetadataRegistry;
import com.mmnaseri.dragonfly.statement.StatementRegistry;
import com.mmnaseri.dragonfly.statement.impl.LocalStatementRegistry;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Collection;
import java.util.concurrent.atomic.AtomicLong;

/**
 * This class encapsulates a session of interaction with the database. It is usually sufficient
 * to initialize a single session per application, unless it is necessary to have more than one
 * secured, closed domain of data access object interaction.
 *
 * @author Milad Naseri (mmnaseri@programmer.net)
 * @since 1.0 (2013/9/7, 14:26)
 */
public class DefaultDataAccessSession implements DataAccessSession {

    private static final Log log = LogFactory.getLog(DefaultDataAccessSession.class);
    private static final String JDBC_PREFIX = "jdbc:";
    private static final String PROTOCOL_SPECIFIER = "://";
    private static final String PORT_SEPARATOR = ":";
    private static final String DB_SEPARATOR = "/";
    private static final String DEFAULT_HOST = "localhost";
    public static final long DEFAULT_CONNECTION_THRESHOLD = 100L;
    public static final long DEFAULT_WAIT_LENIENCY = 1000L;
    private DatabaseDialect databaseDialect;
    private final String username;
    private final String password;
    private final DataSource dataSource;
    private final StatementRegistry statementRegistry;
    private final TableMetadataRegistry tableMetadataRegistry;
    private final DataStructureHandler dataStructureHandler;
    private boolean initialized = false;
    private final AtomicLong connections = new AtomicLong(0L);
    private long maxConnections = DEFAULT_CONNECTION_THRESHOLD;
    private long waitLeniency = DEFAULT_WAIT_LENIENCY;
    private final ThreadLocal<DelegatingConnection> localConnection;

    private static String getConnectionString(DatabaseDialect databaseDialect, String hostName, Integer port,
            String databaseName) {
        return JDBC_PREFIX + databaseDialect.getName() + PROTOCOL_SPECIFIER
                + (hostName == null ? DEFAULT_HOST : hostName) + PORT_SEPARATOR
                + (port == null ? databaseDialect.getDefaultPort() : port) + DB_SEPARATOR
                + (databaseName == null ? "" : databaseName);
    }

    public DefaultDataAccessSession(DatabaseDialect databaseDialect, StatementRegistry statementRegistry,
            TableMetadataRegistry tableMetadataRegistry) {
        this(databaseDialect, statementRegistry, tableMetadataRegistry, null, null);
    }

    public DefaultDataAccessSession(DatabaseDialect databaseDialect, StatementRegistry statementRegistry,
            TableMetadataRegistry tableMetadataRegistry, String hostName, String databaseName) {
        this(databaseDialect, statementRegistry, tableMetadataRegistry, hostName, databaseName, null, null);
    }

    public DefaultDataAccessSession(DatabaseDialect databaseDialect, StatementRegistry statementRegistry,
            TableMetadataRegistry tableMetadataRegistry, String hostName, String databaseName, String username,
            String password) {
        this(databaseDialect, statementRegistry, tableMetadataRegistry, hostName, null, databaseName, username,
                password);
    }

    public DefaultDataAccessSession(DatabaseDialect databaseDialect, StatementRegistry statementRegistry,
            TableMetadataRegistry tableMetadataRegistry, String hostName, Integer port, String databaseName,
            String username, String password) {
        this(databaseDialect, statementRegistry, tableMetadataRegistry,
                new JdbcDataSource(getConnectionString(databaseDialect, hostName, port, databaseName)), username,
                password);
    }

    public DefaultDataAccessSession(DatabaseDialect databaseDialect, StatementRegistry statementRegistry,
            TableMetadataRegistry tableMetadataRegistry, String connectionString) {
        this(databaseDialect, statementRegistry, tableMetadataRegistry, connectionString, null, null);
    }

    public DefaultDataAccessSession(DatabaseDialect databaseDialect, StatementRegistry statementRegistry,
            TableMetadataRegistry tableMetadataRegistry, String connectionString, String username,
            String password) {
        this(databaseDialect, statementRegistry, tableMetadataRegistry, new JdbcDataSource(connectionString),
                username, password);
    }

    public DefaultDataAccessSession(DatabaseDialect databaseDialect, StatementRegistry statementRegistry,
            TableMetadataRegistry tableMetadataRegistry, DataSource dataSource) {
        this(databaseDialect, statementRegistry, tableMetadataRegistry, dataSource, null, null);
    }

    public DefaultDataAccessSession(DatabaseDialect databaseDialect, StatementRegistry statementRegistry,
            TableMetadataRegistry tableMetadataRegistry, DataSource dataSource, String username, String password) {
        loadDriver(databaseDialect);
        this.databaseDialect = databaseDialect;
        this.statementRegistry = statementRegistry;
        this.tableMetadataRegistry = tableMetadataRegistry;
        this.dataSource = dataSource;
        this.username = username;
        this.password = password;
        this.dataStructureHandler = new DefaultDataStructureHandler(this, tableMetadataRegistry);
        this.localConnection = new ThreadLocal<DelegatingConnection>();
    }

    private void loadDriver(DatabaseDialect databaseDialect) {
        final String className = databaseDialect.getDriverClassName();
        try {
            Class.forName(className, true, getClass().getClassLoader());
        } catch (ClassNotFoundException e) {
            throw new DatabaseDriverNotFoundError(className);
        }
    }

    /**
     * Returns a connection from the underlying data source, using authentication credentials
     * if necessary
     * @return the connection instance
     */
    @Override
    public synchronized Connection getConnection() {
        if (localConnection.get() != null) {
            final DelegatingConnection connection = localConnection.get();
            connection.open();
            return connection;
        }
        connections.incrementAndGet();
        log.info("[" + connections.get() + "] Connection requested ...");
        long wait = 0;
        while (connections.get() >= maxConnections && wait < waitLeniency) {
            try {
                wait += 100;
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new IllegalStateException("Failed to initialize connection", e);
            }
        }
        if (connections.get() >= maxConnections) {
            log.warn("Threshold exceeded but operations are stacked up, so we will proceed anyway.");
        }
        final Connection connection;
        try {
            if (username != null && password != null && !username.isEmpty()) {
                connection = dataSource.getConnection(username, password);
            } else {
                connection = dataSource.getConnection();
            }
        } catch (SQLException e) {
            throw new DatabaseConnectionError(e);
        }
        final DelegatingConnection delegatingConnection = new DelegatingConnection(connection,
                new Processor<Connection>() {
                    @Override
                    public void process(Connection input) {
                        log.info("[" + connections.get() + "] Closing connection ...");
                        connections.decrementAndGet();
                        localConnection.remove();
                    }
                });
        delegatingConnection.open();
        localConnection.set(delegatingConnection);
        return delegatingConnection;
    }

    /**
     * @return the dialect associated with the session
     */
    @Override
    public DatabaseDialect getDatabaseDialect() {
        return databaseDialect;
    }

    /**
     * @return the statement registry associated with session
     */
    public StatementRegistry getStatementRegistry(String region) {
        return new LocalStatementRegistry(statementRegistry, region);
    }

    /**
     * @return the statement registry associated with session
     */
    @Override
    public StatementRegistry getStatementRegistry(Class<?> entityType) {
        return getStatementRegistry(entityType.getCanonicalName());
    }

    /**
     * @return the metadata registry associated with the session
     */
    @Override
    public TableMetadataRegistry getTableMetadataRegistry() {
        return tableMetadataRegistry;
    }

    /**
     * @return all entity types for which metadata is registered
     */
    @Override
    public Collection<Class<?>> getRegisteredEntities() {
        return tableMetadataRegistry.getEntityTypes();
    }

    /**
     * Initializes the session by initializing all data structures
     */
    @Override
    public synchronized void initialize() {
        if (initialized) {
            throw new DataAccessSessionAlreadyInitializedError();
        }
        log.info("Initializing data structures with database handlers");
        try {
            dataStructureHandler.initialize();
        } catch (Error e) {
            throw new DataAccessSessionInitializationError(
                    "An error prevented successful initialization of the session", e);
        }
        initialized = true;
    }

    /**
     * @return {@code true} if the session has already been initialized
     */
    @Override
    public boolean isInitialized() {
        return initialized;
    }

    @Override
    public void markInitialized() {
        initialized = true;
    }

    public void setMaxConnections(long maxConnections) {
        this.maxConnections = maxConnections;
    }

    public void setWaitLeniency(long waitLeniency) {
        this.waitLeniency = waitLeniency;
    }

    public DataStructureHandler getDataStructureHandler() {
        return dataStructureHandler;
    }

}