summaryrefslogblamecommitdiffstats
path: root/src/main/java/org/openslx/imagemaster/db/Database.java
blob: 765f782a7bb45e4b6233f09ba06c3383e9432513 (plain) (tree)

















































































































































                                                                                                                                                    









                                                                                         



























                                                                                                                 
package org.openslx.imagemaster.db;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collections;
import java.util.Properties;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;

import org.apache.log4j.Logger;
import org.openslx.imagemaster.util.Util;

public class Database
{

	private static final Logger LOGGER = Logger.getLogger( Database.class );
	/**
	 * Pool of available connections.
	 */
	private static final Queue<MysqlConnection> pool = new ConcurrentLinkedQueue<>();

	/**
	 * Set of connections currently handed out.
	 */
	private static final Set<MysqlConnection> busyConnections = Collections.newSetFromMap( new ConcurrentHashMap<MysqlConnection, Boolean>() );

	private static final String host;
	private static final String dbname;
	private static final String user;
	private static final String password;

	/**
	 * Static initializer for setting up the database connection.
	 * This gets called implicitly as soon as the class loader loads
	 * the class. In most cases that happens when the class is being
	 * accessed for the first time during run time.
	 */
	static
	{
		// Load connection info from class (TODO: Make pretty)
		Properties properties = new Properties();
		try {
			final BufferedInputStream stream = new BufferedInputStream(
					new FileInputStream(
							"config/mysql.properties" ) );
			properties.load( stream );
			stream.close();
		} catch ( FileNotFoundException e ) {
			LOGGER.fatal( "config/mysql.properties not found!" );
			System.exit( 1 );
		} catch ( IOException e ) {
			LOGGER.fatal( "Error reading from config/mysql.properties: " + e.getMessage() );
			System.exit( 1 );
		} catch ( Exception e ) {
			LOGGER.fatal( "Generic error loading mysql properties file." );
			e.printStackTrace();
			System.exit( 1 );
		}
		host = properties.getProperty( "host" );
		dbname = properties.getProperty( "db" );
		user = properties.getProperty( "user" );
		password = properties.getProperty( "password" );

		Util.notNullFatal( host, "host not set in mysql properties" );
		Util.notNullFatal( dbname, "db not set in mysql properties" );
		Util.notNullFatal( user, "user not set in mysql properties" );
		Util.notNullFatal( password, "password not set in mysql properties" );

		try {
			Class.forName( "com.mysql.jdbc.Driver" ).newInstance();
		} catch ( InstantiationException | IllegalAccessException | ClassNotFoundException e ) {
			LOGGER.fatal( "Cannot get mysql JDBC driver!", e );
			System.exit( 1 );
		}
	}

	/**
	 * Get a connection to the database. If there is a valid connection in the
	 * pool, it will be returned. Otherwise, a new connection is created. If
	 * there are more than 20 busy connections, <code>null</code> is returned.
	 * 
	 * @return connection to database, or <code>null</code>
	 */
	public static MysqlConnection getConnection()
	{
		MysqlConnection con;
		for ( ;; ) {
			con = pool.poll();
			if ( con == null )
				break;
			if ( !con.isValid() ) {
				con.release();
				continue;
			}
			if ( !busyConnections.add( con ) )
				throw new RuntimeException( "Tried to hand out a busy connection!" );
			return con;
		}
		// No pooled connection
		if ( busyConnections.size() > 20 ) {
			LOGGER.warn( "Too many open MySQL connections. Possible connection leak!" );
			return null;
		}
		try {
			// Create fresh connection
			String uri = "jdbc:mysql://" + host + "/" + dbname
					+ "?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8"
					+ "&characterSetResults=utf8&connectionCollation=utf8mb4_unicode_ci";
			Connection rawConnection = DriverManager.getConnection( uri,
					user, password );
			// By convention in our program we don't want auto commit
			rawConnection.setAutoCommit( false );
			// Wrap into our proxy
			con = new MysqlConnection( rawConnection );
			// Keep track of busy mysql connection
			if ( !busyConnections.add( con ) )
				throw new RuntimeException( "Tried to hand out a busy connection!" );
			return con;
		} catch ( SQLException e ) {
			LOGGER.info( "Failed to connect to local mysql server", e );
		}
		return null;
	}

	/**
	 * Called by a {@link MysqlConnection} when its <code>close()</code>-method
	 * is called, so the connection will be added to the pool of available
	 * connections again.
	 * 
	 * @param connection
	 */
	static void returnConnection( MysqlConnection connection )
	{
		if ( !busyConnections.remove( connection ) )
			throw new RuntimeException( "Tried to return a mysql connection to the pool that was not taken!" );
		pool.add( connection );
	}

	/**
	 * Return true if the given sql exception is "duplicate entry XXXX for key YYYY.
	 */
	public static boolean isDuplicateKeyException( SQLException e )
	{
		return e != null && e.getErrorCode() == 1062;
	}

	//

	public static void printCharsetInformation()
	{
		LOGGER.info( "MySQL charset related variables:" );
		try ( MysqlConnection connection = Database.getConnection() ) {
			MysqlStatement stmt = connection.prepareStatement( "SHOW VARIABLES LIKE :what" );
			stmt.setString( "what", "char%" );
			ResultSet rs = stmt.executeQuery();
			while ( rs.next() ) {
				LOGGER.info( rs.getString( "Variable_name" ) + ": " + rs.getString( "Value" ) );
			}
			stmt.setString( "what", "collat%" );
			rs = stmt.executeQuery();
			while ( rs.next() ) {
				LOGGER.info( rs.getString( "Variable_name" ) + ": " + rs.getString( "Value" ) );
			}
		} catch ( SQLException e ) {
			LOGGER.error( "Query failed in Database.printCharsetInformation()", e );
		}
		LOGGER.info( "End of variables" );
	}

	public static void printDebug()
	{
		LOGGER.info( "Available: " + pool.size() );
		LOGGER.info( "Busy: " + busyConnections.size() );
	}

}// end class