//$Id: SchemaExport.java,v 1.22 2005/10/18 15:07:46 maxcsaucdk Exp $
package org.hibernate.tool.hbm2ddl;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.Writer;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.HibernateException;
import org.hibernate.JDBCException;
import org.hibernate.cfg.Configuration;
import org.hibernate.cfg.Environment;
import org.hibernate.cfg.NamingStrategy;
import org.hibernate.cfg.Settings;
import org.hibernate.connection.ConnectionProvider;
import org.hibernate.connection.ConnectionProviderFactory;
import org.hibernate.dialect.Dialect;
import org.hibernate.pretty.DDLFormatter;
import org.hibernate.util.ConfigHelper;
import org.hibernate.util.JDBCExceptionReporter;
import org.hibernate.util.PropertiesHelper;
import org.hibernate.util.ReflectHelper;

/**
 * Commandline tool to export table schema to the database. This
 * class may also be called from inside an application.
 *
 * @author Daniel Bradby, Gavin King
 */
public class SchemaExport {

	private static final Log log = LogFactory.getLog(SchemaExport.class);

	private ConnectionHelper connectionHelper;
	private String[] dropSQL;
	private String[] createSQL;
	private String outputFile = null;
	private String importFile = "/import.sql";
	private Dialect dialect;
	private String delimiter;
    private final List exceptions = new ArrayList();
    private boolean haltOnError = false;
    private boolean format = true;

	/**
	 * Create a schema exporter for the given Configuration
	 */
	public SchemaExport(Configuration cfg) throws HibernateException {
		this( cfg, cfg.getProperties() );
	}

	/**
	 * Create a schema exporter for the given Configuration
	 * and given settings
	 */
	public SchemaExport(Configuration cfg, Settings settings) throws HibernateException {
		dialect = settings.getDialect();
		connectionHelper = new SuppliedConnectionProviderConnectionHelper( 
				settings.getConnectionProvider() 
			);
		dropSQL = cfg.generateDropSchemaScript( dialect );
		createSQL = cfg.generateSchemaCreationScript( dialect );
		format = settings.isFormatSqlEnabled();
	}

	/**
	 * Create a schema exporter for the given Configuration, with the given
	 * database connection properties.
	 * 
	 * @deprecated properties may be specified via the Configuration object
	 */
	public SchemaExport(Configuration cfg, Properties properties) 
	throws HibernateException {
		dialect = Dialect.getDialect( properties );

		Properties props = new Properties();
		props.putAll( dialect.getDefaultProperties() );
		props.putAll( properties );

		connectionHelper = new ProviderConnectionHelper( props );
		dropSQL = cfg.generateDropSchemaScript( dialect );
		createSQL = cfg.generateSchemaCreationScript( dialect );
		format = PropertiesHelper.getBoolean(Environment.FORMAT_SQL, props);
	}

	public SchemaExport(Configuration cfg, Connection connection) {
		this.connectionHelper = new SuppliedConnectionHelper( connection );
		dialect = Dialect.getDialect( cfg.getProperties() );
		dropSQL = cfg.generateDropSchemaScript( dialect );
		createSQL = cfg.generateSchemaCreationScript( dialect );
	}

	/**
	 * Set an output filename. The generated script will be written to this file.
	 */
	public SchemaExport setOutputFile(String filename) {
		outputFile = filename;
		return this;
	}

	public SchemaExport setImportFile(String filename) {
		importFile = filename;
		return this;
	}

	/**
	 * Set the end of statement delimiter
	 */
	public SchemaExport setDelimiter(String delimiter) {
		this.delimiter = delimiter;
		return this;
	}

	/**
	 * Run the schema creation script.
	 * @param script print the DDL to the console
	 * @param export export the script to the database
	 */
	public void create(boolean script, boolean export) {
		execute(script, export, false, false);
	}

	/**
	 * Run the drop schema script.
	 * @param script print the DDL to the console
	 * @param export export the script to the database
	 */
	public void drop(boolean script, boolean export) {
		execute(script, export, true, false);
	}
	
	private String format(String sql) {
		return format ? 
				new DDLFormatter(sql).format() :
				sql;
	}

	public void execute(boolean script, boolean export, boolean justDrop, boolean justCreate) {

		log.info("Running hbm2ddl schema export");

		Connection connection = null;
		Writer outputFileWriter = null;
		Reader importFileReader = null;
		Statement statement = null;

        exceptions.clear();

		try {
			
			try 
			{
				InputStream stream = ConfigHelper.getResourceAsStream(importFile);
				importFileReader = new InputStreamReader(stream);
			}
			catch (HibernateException e)
			{
				log.debug("import file not found: " + importFile);
			}

			if (outputFile != null) {
				log.info("writing generated schema to file: " + outputFile);
				outputFileWriter = new FileWriter(outputFile);
			}

			if (export) {
				log.info("exporting generated schema to database");
				connection = connectionHelper.getConnection();
				if ( !connection.getAutoCommit() ) {
					connection.commit();
					connection.setAutoCommit(true);
				}
				statement = connection.createStatement();
			}
			
			if (!justCreate) {
				drop( script, export, outputFileWriter, statement );
			}

			if (!justDrop) {
				create( script, export, outputFileWriter, statement );
				if (export && importFileReader!=null) {
					importScript(importFileReader, statement);
				}
			}

			log.info("schema export complete");

		}

		catch(Exception e) {
            exceptions.add(e);
			log.error("schema export unsuccessful", e);
		}

		finally {

			try {
				if (statement!=null) statement.close();
				if (connection!=null) connectionHelper.release();
			}
			catch(Exception e) {
                exceptions.add(e);
				log.error( "Could not close connection", e );
			}

			try {
				if (outputFileWriter!=null) outputFileWriter.close();
				if (importFileReader!=null) importFileReader.close();
			}
			catch (IOException ioe) {
                exceptions.add(ioe);
				log.error( "Error closing output file: " + outputFile, ioe );
			}

		}
	}
		
	private void importScript(Reader importFileReader, Statement statement) 
			throws IOException {
		log.info("Executing import script: " + importFile);
		BufferedReader reader = new BufferedReader(importFileReader);
		for ( String sql = reader.readLine(); sql!=null; sql = reader.readLine() ) {
			try {
				String trimmedSql = sql.trim();
				if(trimmedSql.length()==0 || 
				   trimmedSql.startsWith("--") || 
				   trimmedSql.startsWith("//") || 
				   trimmedSql.startsWith("/*")) {				
					continue;
				} else {
					if(trimmedSql.endsWith(";")) {
						trimmedSql = trimmedSql.substring(0,trimmedSql.length()-1);
					}
					log.debug(trimmedSql);
					if(true) continue;
					statement.execute(trimmedSql);
				}
			}
			catch (SQLException e) {
				throw new JDBCException("Error during import script execution", e);
			}			
		}
	}

	private void create(boolean script, boolean export, Writer fileOutput, Statement statement) 
			throws IOException {
		for (int j = 0; j < createSQL.length; j++) {
			try {
				execute( script, export, fileOutput, statement, createSQL[j] );
			}
			catch (SQLException e) {
				if (haltOnError) {
					throw new JDBCException("Error during DDL export", e);
				}
		        exceptions.add(e);
				log.error( "Unsuccessful: " + createSQL[j] );
				log.error( e.getMessage() );
			}
		}
	}

	private void drop(boolean script, boolean export, Writer fileOutput, Statement statement) 
			throws IOException {
		for (int i = 0; i < dropSQL.length; i++) {
			try {
				execute( script, export, fileOutput, statement, dropSQL[i] );
			}
			catch (SQLException e) {
		        exceptions.add(e);
				log.debug( "Unsuccessful: " + dropSQL[i] );
				log.debug( e.getMessage() );
			}
		}
	}

	private void execute(boolean script, boolean export, Writer fileOutput, Statement statement, final String sql) 
			throws IOException, SQLException {
		String formatted = format( sql );
		if (delimiter != null) formatted += delimiter;
		if (script) System.out.println(formatted);
		log.debug(formatted);
		if (outputFile != null) fileOutput.write( formatted + "\n" );
		if (export) statement.executeUpdate( sql );
	}

	public static void main(String[] args) {
		try {
			Configuration cfg = new Configuration();

			boolean script = true;
			boolean drop = false;
			boolean create = false;
			boolean halt = false;
			boolean export = true;
			String outFile = null;
			String importFile = "/import.sql";
			String propFile = null;
			boolean format = false;
			String delim = null;

			for ( int i=0; i<args.length; i++ )  {
				if( args[i].startsWith("--") ) {
					if( args[i].equals("--quiet") ) {
						script = false;
					}
					else if( args[i].equals("--drop") ) {
						drop = true;
					}
					else if( args[i].equals("--create") ) {
						create = true;
					}
					else if( args[i].equals("--haltonerror") ) {
						halt = true;
					}
					else if( args[i].equals("--text") ) {
						export = false;
					}
					else if( args[i].startsWith("--output=") ) {
						outFile = args[i].substring(9);
					}
					else if( args[i].startsWith("--import=") ) {
						importFile = args[i].substring(9);
					}
					else if( args[i].startsWith("--properties=") ) {
						propFile = args[i].substring(13);
					}
					else if( args[i].equals("--format") ) {
						format = true;
					}
					else if ( args[i].startsWith("--delimiter=") ) {
						delim = args[i].substring(12);
					}
					else if ( args[i].startsWith("--config=") ) {
						cfg.configure( args[i].substring(9) );
					}
					else if ( args[i].startsWith("--naming=") ) {
						cfg.setNamingStrategy(
								(NamingStrategy) ReflectHelper.classForName( args[i].substring(9) )
										.newInstance()
							);
					}
				}
				else {
					String filename = args[i];
					if ( filename.endsWith( ".jar" ) ) {
						cfg.addJar( new File(filename) );
					}
					else {
						cfg.addFile(filename);
					}
				}

			}
			
			if (propFile!=null) {
				Properties props = new Properties();
				props.putAll( cfg.getProperties() );
				props.load( new FileInputStream(propFile) );
				cfg.setProperties(props);
			}

			SchemaExport se = new SchemaExport(cfg)
					.setHaltOnError(halt)
					.setOutputFile(outFile)
					.setImportFile(importFile)
					.setDelimiter(delim);
			if (format) se.setFormat(true);
			se.execute(script, export, drop, create);
			
		}
		catch(Exception e) {
			log.error( "Error creating schema ", e );
			e.printStackTrace();
		}
	}

    /**
     * Returns a List of all Exceptions which occured during the export.
     * @return A List containig the Exceptions occured during the export
     */
    public List getExceptions() {
        return exceptions;
    }

	private interface ConnectionHelper {
		Connection getConnection() throws SQLException;
		void release() throws SQLException;
	}

	private class SuppliedConnectionHelper implements ConnectionHelper {
		private Connection connection;

		public SuppliedConnectionHelper(Connection connection) {
			this.connection = connection;
		}

		public Connection getConnection() {
			return connection;
		}

		public void release() {
			JDBCExceptionReporter.logAndClearWarnings(connection);
			connection = null;
		}
	}

	private class SuppliedConnectionProviderConnectionHelper implements ConnectionHelper {
		private ConnectionProvider provider;
		private Connection connection;

		public SuppliedConnectionProviderConnectionHelper(ConnectionProvider provider) {
			this.provider = provider;
		}

		public Connection getConnection() throws SQLException {
			if (connection == null) {
				connection = provider.getConnection();
				if ( !connection.getAutoCommit() ) {
					connection.commit();
					connection.setAutoCommit( true );
				}
			}
			return connection;
		}

		public void release() throws SQLException {
			if (connection != null) {
				JDBCExceptionReporter.logAndClearWarnings(connection);
				provider.closeConnection( connection );
				connection = null;
			}
		}
	}
	
	public SchemaExport setFormat(boolean format) {
		this.format = format;
		return this;
	}

	private class ProviderConnectionHelper implements ConnectionHelper {
		private Properties cfgProperties;
		private ConnectionProvider connectionProvider;
		private Connection connection;

		public ProviderConnectionHelper(Properties cfgProperties) {
			this.cfgProperties = cfgProperties;
		}

		public Connection getConnection() throws SQLException {
			if ( connection == null ) {
				connectionProvider = ConnectionProviderFactory.newConnectionProvider( cfgProperties );
				connection = connectionProvider.getConnection();
				if ( !connection.getAutoCommit() ) {
					connection.commit();
					connection.setAutoCommit( true );
				}
			}
			return connection;
		}

		public void release() throws SQLException {
			if ( connection!=null ) {
				JDBCExceptionReporter.logAndClearWarnings(connection);
				connectionProvider.closeConnection( connection );
				connectionProvider.close();
			}
			connection = null;
		}
	}

	public SchemaExport setHaltOnError(boolean haltOnError) {
		this.haltOnError = haltOnError;
		return this;
	}
}







