Java tutorial
/* * 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; } }