org.rimudb.Database.java Source code

Java tutorial

Introduction

Here is the source code for org.rimudb.Database.java

Source

/*
 * Copyright (c) 2008-2011 Simon Ritchie.
 * All rights reserved. 
 * 
 * This program is free software: you can redistribute it and/or modify 
 * it under the terms of the GNU Lesser General Public License as published 
 * by the Free Software Foundation, either version 3 of the License, or 
 * (at your option) any later version.
 * 
 * This program 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 Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License 
 * along with this program.  If not, see http://www.gnu.org/licenses/>.
 */
package org.rimudb;

import java.sql.*;
import java.util.*;

import org.apache.commons.logging.*;
import org.rimudb.configuration.*;
import org.rimudb.exception.*;
import org.rimudb.pool.*;
import org.rimudb.sql.*;
import org.rimudb.statistics.*;
import org.w3c.dom.Document;

/**
 * This class manages connectivity to a specific Database.
 * 
 * @author Simon Ritchie
 *
 */
public class Database {
    private static Log log = LogFactory.getLog(Database.class);

    private boolean connected = false;

    private DatabaseConfiguration databaseConfiguration;

    // The table descriptor for each data object
    private Map<String, TableDescriptor> tableDescriptorMap = null;

    private IConnectionManager connectionManager = null;

    private RimuDBDatabaseMetaData dbMetaData;

    private ISQLAdapter sqlAdapter;

    private StatisticFormatter statisticFormatter = new DefaultStatisticFormatter();

    private TableDescriptorLoader tableDescriptorLoader;

    private final boolean validateXML;

    /**
     * Create a Database.
     * @throws RimuDBException 
     */
    public Database(DatabaseConfiguration databaseConfiguration, boolean validateXML) throws RimuDBException {
        super();
        this.databaseConfiguration = databaseConfiguration;
        this.validateXML = validateXML;

        connectionManager = PoolConnectionFactory.createInstance(databaseConfiguration);

        tableDescriptorMap = Collections.synchronizedMap(new TreeMap<String, TableDescriptor>());

        tableDescriptorLoader = new TableDescriptorLoader();

    }

    /**
     * Return the connection pool manager
     */
    public IConnectionManager getConnectionManager() throws RimuDBException {
        return connectionManager;
    }

    /**
     * Get a connection.
     */
    public Connection getDatabaseConnection() throws RimuDBException, SQLException {
        return getConnectionManager().getDatabaseConnection();
    }

    /**
     * Get a Session
     * @return Session
     * @throws RimuDBException
     */
    public Session createSession() throws RimuDBException {
        try {
            return new Session(getDatabaseConnection());
        } catch (SQLException e) {
            throw new RimuDBException(e);
        }
    }

    /**
     * Connect to the database. 
     * 
     * @param user java.lang.String
     * @param password java.lang.String
     */
    public void connect(String user, String password) throws RimuDBException {
        // Set up the alias
        try {
            getConnectionManager().connect(user, password);
        } catch (Exception e) {
            log.error("connect(): " + e);
            throw new RimuDBException(e);
        }

        setConnected(true);

        // Get the database meta data
        dbMetaData = lookupDatabaseInfo();

        // Create the statement builder
        sqlAdapter = SQLAdapterFactory.createInstance(dbMetaData);
        sqlAdapter.setUseQuotes(getDatabaseConfiguration().getPoolConfiguration().isUseQuotes());
        sqlAdapter.setDatabaseConfiguration(getDatabaseConfiguration());
        sqlAdapter.setDatabaseMetaData(dbMetaData);

    }

    /**
     * Get this database's meta data
     */
    private RimuDBDatabaseMetaData lookupDatabaseInfo() throws RimuDBException {
        RimuDBDatabaseMetaData metaData = new RimuDBDatabaseMetaData();
        Connection con = null;
        DatabaseMetaData dbmd = null;
        ResultSet rs = null;

        try {
            con = getDatabaseConnection();
            dbmd = con.getMetaData();

            metaData.setDatabaseProductName(dbmd.getDatabaseProductName());
            metaData.setDatabaseMajorVersion(dbmd.getDatabaseMajorVersion());
            metaData.setSupportsGetGeneratedKeys(dbmd.supportsGetGeneratedKeys());

        } catch (SQLException e) {
            throw new RimuDBException(e);

        } finally {
            if (rs != null) {
                try {
                    rs.close();
                } catch (SQLException e) {
                }
            }
            if (con != null) {
                try {
                    con.close();
                } catch (SQLException e) {
                }
            }
        }

        return metaData;
    }

    /**
     * Return true if we're connected.
     * @return boolean
     */
    public boolean isConnected() {
        return connected;
    }

    /**
     * Set the connected flag
     */
    public void setConnected(boolean connected) {
        this.connected = connected;
    }

    /**
     * Disconnect from the database.
     */
    public void disconnect() throws RimuDBException {

        getConnectionManager().disconnect();

        setConnected(false);
    }

    /**
     * Return the table using the data object class.
     * @return AbstractTable
     * @throws RimuDBException 
     */
    public Table getTable(Class<? extends DataObject> dataObjectClass) throws RimuDBException {
        // Get the table from the map
        TableDescriptor tableDescriptor = getTableDescriptor(dataObjectClass);
        Table table = tableDescriptor.getTable();
        if (table == null) {
            // Only let one thread access this at a time
            synchronized (this) {
                // Check we weren't waiting for another thread that already figured this out
                table = tableDescriptor.getTable();
                if (table == null) {
                    table = new Table();
                    table.setDatabase(this);
                    table.setDataObjectClass(dataObjectClass);

                    // Set the table meta data
                    TableMetaData tableMetaData = loadTableMetaData(dataObjectClass);

                    // Disable auto increment
                    if (tableDescriptor.isAutoIncrementDisabled()) {
                        tableMetaData.disableAutoIncrement();
                    }

                    table.setTableMetaData(tableMetaData);

                    String unqualifiedTableName = tableMetaData.getTableName();

                    // Set the StatementBuilder
                    table.setSQLAdpater(sqlAdapter);

                    // Set the unqualified table name
                    table.setTableName(unqualifiedTableName);

                    table.setOptimisticLocking(tableDescriptor.getOptimisticLocking());

                    table.setUsingLockingHints(tableDescriptor.useLockingHints());

                    // Set the RecordBinder
                    RecordBinder recordBinder = new RecordBinder(tableMetaData, sqlAdapter);
                    table.setRecordBinder(recordBinder);

                    // Perform setup of the table
                    table.initialize();

                    // Save the table in the Table Descriptor 
                    tableDescriptor.setTable(table);
                }
            }
        }
        return table;
    }

    /**
     * Return the DataObjectDescriptor for a given data object class (a xxxxDO class).
     * @param dataObjectClass
     * @param configDescriptor 
     * @return
     * @throws RimuDBConfigurationException 
     */
    TableDescriptor getTableDescriptor(Class<? extends DataObject> dataObjectClass) {
        // Check if we have the descriptor in the map
        TableDescriptor tableDescriptor = tableDescriptorMap.get(dataObjectClass.getName());

        // If we don't have the descriptor yet, determine it from the data object class 
        if (tableDescriptor == null) {
            tableDescriptor = new TableDescriptor(dataObjectClass);
            TableConfiguration tableConfiguration = databaseConfiguration.getTableConfiguration(dataObjectClass);
            if (tableConfiguration != null) {
                tableDescriptor.setOptimisticLocking(tableConfiguration.getOptimisticLocking());
                tableDescriptor.setUseLockingHints(tableConfiguration.useLockingHints());
                tableDescriptor.setAutoIncrementDisabled(tableConfiguration.isDisableAutoIncrement());
            }
            tableDescriptorMap.put(dataObjectClass.getName(), tableDescriptor);
        }

        return tableDescriptor;
    }

    /**
     * Return an array of all the AbstractTables that have been
     * discovered with this Database.
     * @return Table[]
     */
    public Table[] getAllTables() throws RimuDBException {
        List<Table> list = new ArrayList<Table>();
        synchronized (tableDescriptorMap) {
            Iterator<TableDescriptor> iter = tableDescriptorMap.values().iterator();
            while (iter.hasNext()) {
                TableDescriptor dod = (TableDescriptor) iter.next();
                list.add(dod.getTable());
            }
        }
        return (Table[]) list.toArray(new Table[list.size()]);
    }

    /**
     * Force all the tables configured in the XML config file to be loaded.
     * 
     * @throws ClassNotFoundException 
     * @throws RimuDBException 
     * 
     * @throws Exception
     */
    public void forceTableLoad() throws ClassNotFoundException, RimuDBException {
        TableConfiguration[] tableConfigurations = databaseConfiguration.getTableConfigurations();
        for (int i = 0; i < tableConfigurations.length; i++) {
            Class clazz = Class.forName(tableConfigurations[i].getClassName());
            Table table = getTable(clazz);
        }
    }

    /**
     * Return the StatisticFormatter.
     * 
     * @return StatisticFormatter
     */
    public StatisticFormatter getStatisticFormatter() {
        return statisticFormatter;
    }

    /**
     * Return the DatabaseConfiguration.
     * 
     * @return DatabaseConfiguration
     */
    public DatabaseConfiguration getDatabaseConfiguration() {
        return databaseConfiguration;
    }

    /**
     * Return meta data about the underlying database.
     * 
     * @return RimuDBDatabaseMetaData
     */
    public RimuDBDatabaseMetaData getDatabaseMetaData() {
        return dbMetaData;
    }

    /**
     * Return the Data Object class for a given Data Object class name.
     * 
     * @param dataObjectClassName String
     * @return Class<? extends DataObject>
     * @throws ClassNotFoundException 
     */
    public Class<? extends DataObject> getDataObjectClassByName(String dataObjectClassName)
            throws ClassNotFoundException {

        // The class name might be qualified with a package
        if (dataObjectClassName.indexOf(".") > -1) {
            Class clazz = Class.forName(dataObjectClassName);
            return clazz;
        }

        TableConfiguration[] tableConfigurations = databaseConfiguration.getTableConfigurations();
        for (int i = 0; i < tableConfigurations.length; i++) {

            if (matchClass(tableConfigurations[i].getClassName(), dataObjectClassName)) {
                Class clazz = Class.forName(tableConfigurations[i].getClassName());
                return clazz;
            }

        }

        return null;
    }

    /**
     * Return true if the searchClassName matches the matchClassName. The classes are assumed
     * to be xxxxDO classes, and the matchClassName may not have a leading package name nor a 
     * trailing 'DO'.
     * 
     * @param searchClassName
     * @param matchClassName
     * @return boolean
     */
    protected boolean matchClass(String searchClassName, String matchClassName) {
        // Assume the searchClassName is fully qualified as it is from the Compound Database XML

        // If the matchClassName is fully qualified then compare it with the searchClassName
        int matchPos = matchClassName.lastIndexOf('.');
        if (matchPos > -1) {
            return (searchClassName.equals(matchClassName) || searchClassName.equals(matchClassName + "DO"));
        }

        // Otherwise the matchClassName contains no package name so just test the class name
        int searchPos = searchClassName.lastIndexOf('.');
        if (searchPos > -1) {
            searchClassName = searchClassName.substring(searchPos + 1);
        }
        return (searchClassName.equals(matchClassName) || searchClassName.equals(matchClassName + "DO"));
    }

    public Table getTable(String dataObjectClassName) throws RimuDBException {
        Class<? extends DataObject> dataObjectClass;
        try {
            dataObjectClass = getDataObjectClassByName(dataObjectClassName);
        } catch (ClassNotFoundException e) {
            throw new RimuDBException("Could not find data object class " + dataObjectClassName
                    + ". Check that the name matches the DO class name, and that the DO is definied in the configuration file '"
                    + getDatabaseConfiguration().getConfigurationFilename() + "'.");
        }
        if (dataObjectClass == null) {
            throw new RimuDBException("Could not find data object class " + dataObjectClassName
                    + ". Check that the name matches the DO class name, and that the DO is definied in the configuration file '"
                    + getDatabaseConfiguration().getConfigurationFilename() + "'.");
        }
        return getTable(dataObjectClass);
    }

    public TableMetaData loadTableMetaData(Class<? extends DataObject> dataObjectClass) throws RimuDBException {
        String rootFilename = extractRootFilename(dataObjectClass);
        String packageFilename = extractPackageFilename(dataObjectClass);

        Document document = tableDescriptorLoader.loadFromResource(rootFilename, packageFilename, validateXML);
        return tableDescriptorLoader.convertToTableMetaData(document);
    }

    /**
     * @param dataObjectClass
     * @return
     */
    private String extractRootFilename(Class<? extends DataObject> dataObjectClass) {
        String fullname = dataObjectClass.getName();
        String className = fullname;
        int pos = fullname.lastIndexOf('.');
        if (pos > -1) {
            if (fullname.endsWith("DO")) {
                int len = fullname.length();
                className = fullname.substring(pos + 1, len - 2);
            } else {
                className = fullname.substring(pos + 1);
            }
        }

        String filename = "/" + className + ".xml";
        return filename;
    }

    private String extractPackageFilename(Class<? extends DataObject> dataObjectClass) {
        String fullClassname = dataObjectClass.getName();
        String filename1 = fullClassname.replaceAll("\\.", "/");
        String filename2 = filename1;
        if (filename1.endsWith("DO")) {
            int len = filename1.length();
            filename2 = filename1.substring(0, len - 2);
        }

        String filename = "/" + filename2 + ".xml";
        return filename;
    }

    /**
     * Return the SQLAdapter to allow callers to perform some database agnostic operations
     * with GenericQueries.
     * 
     * @return ISQLAdapter
     */
    public ISQLAdapter getSQLAdapter() {
        return sqlAdapter;
    }

}