/*

   Derby - Class org.apache.derby.jdbc.AutoloadedDriver

   Licensed to the Apache Software Foundation (ASF) under one or more
   contributor license agreements.  See the NOTICE file distributed with
   this work for additional information regarding copyright ownership.
   The ASF licenses this file to You under the Apache License, Version 2.0
   (the "License"); you may not use this file except in compliance with
   the License.  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

 */

package org.apache.derby.jdbc;

import java.sql.DriverManager;
import java.sql.Driver;
import java.sql.Connection;
import java.sql.DriverPropertyInfo;
import java.sql.SQLException;

import java.security.AccessControlException;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.Properties;

import org.apache.derby.iapi.reference.MessageId;
import org.apache.derby.iapi.reference.SQLState;
import org.apache.derby.iapi.services.i18n.MessageService;
import org.apache.derby.iapi.services.sanity.SanityManager;
import org.apache.derby.iapi.services.monitor.Monitor;
import org.apache.derby.impl.jdbc.Util;


/**
   This is the dummy driver which is registered with the DriverManager and
   which is autoloaded by JDBC4. Loading this class will NOT automatically
   boot the Derby engine, but it will register this class as a valid
   Driver with the DriverManager.
   Instead, the engine boots lazily when you ask for a
   Connection. Alternatively, you can force the engine to boot as follows:

   	 <PRE>
	 Class.forName("org.apache.derby.jdbc.EmbeddedDriver").newInstance();

	 // or

     new org.apache.derby.jdbc.EmbeddedDriver();

    
	</PRE>
*/
public class AutoloadedDriver implements Driver
{
	// This flag is set if the engine is forcibly brought down.
	private	static	boolean	_engineForcedDown = false;
	

    // This is the driver that memorizes the autoloadeddriver (DERBY-2905)
    private static AutoloadedDriver _autoloadedDriver;

	//
	// This is the driver that's specific to the JDBC level we're running at.
	// It's the module which boots the whole Derby engine.
	//
	private	static	Driver	_driverModule;
	
	static
	{
        try {
            //
            // We'd rather load this slightly more capable driver.
            // But if the vm level doesn't support it, then we fall
            // back on the JDBC3 level driver.
            //
            Class.forName( "org.apache.derby.jdbc.AutoloadedDriver40" );
        }
        catch (Throwable e)
        {
            registerMe( new AutoloadedDriver() );
        }
	}

	protected static void   registerMe( AutoloadedDriver me )
	{
		try {
            _autoloadedDriver = me;
            DriverManager.registerDriver( _autoloadedDriver );
		}
		catch (SQLException se)
		{
			String	message = MessageService.getTextMessage
				(MessageId.JDBC_DRIVER_REGISTER_ERROR, se.getMessage() );

			throw new IllegalStateException( message );
		}
	}

	/*
	** Methods from java.sql.Driver.
	*/
	/**
		Accept anything that starts with <CODE>jdbc:derby:</CODE>.
		@exception SQLException if a database-access error occurs.
    @see java.sql.Driver
	*/
	public boolean acceptsURL(String url) throws SQLException {

		//
		// We don't want to accidentally boot the engine just because
		// the application is looking for a connection from some other
		// driver.
		//
		return !_engineForcedDown && InternalDriver.embeddedDriverAcceptsURL(url);
	}

   
	/**
		Connect to the URL if possible
		@exception SQLException illegal url or problem with connectiong
    @see java.sql.Driver
  */
	public Connection connect(String url, Properties info)
		throws SQLException
	{
		//
		// This pretty piece of logic compensates for the following behavior
		// of the DriverManager: When asked to get a Connection, the
		// DriverManager cycles through all of its autoloaded drivers, looking
		// for one which will return a Connection. Without this pretty logic,
		// the embedded driver module will be booted by any request for
		// a connection which cannot be satisfied by drivers ahead of us
		// in the list.
		if (!InternalDriver.embeddedDriverAcceptsURL(url)) { return null; }

		return getDriverModule().connect(url, info);
	}

  /**
   * Returns an array of DriverPropertyInfo objects describing possible properties.
    @exception SQLException if a database-access error occurs.
    @see java.sql.Driver
   */
	public  DriverPropertyInfo[] getPropertyInfo(String url, Properties info)
		throws SQLException
	{
		return getDriverModule().getPropertyInfo(url, info);
	}

    /**
     * Returns the driver's major version number. 
     @see java.sql.Driver
     */
	public int getMajorVersion() {
		try {
			return (getDriverModule().getMajorVersion());
		}
		catch (SQLException se) {
			return 0;
		}
	}
    /**
     * Returns the driver's minor version number.
     @see java.sql.Driver
     */
	public int getMinorVersion() {
		try {
			return (getDriverModule().getMinorVersion());
		}
		catch (SQLException se) {
			return 0;
		}
	}

  /**
   * Report whether the Driver is a genuine JDBC COMPLIANT (tm) driver.
     @see java.sql.Driver
   */
	public boolean jdbcCompliant() {
		try {
			return (getDriverModule().jdbcCompliant());
		}
		catch (SQLException se) {
			return false;
		}
	}

	///////////////////////////////////////////////////////////////////////
	//
	// Support for booting and shutting down the engine.
	//
	///////////////////////////////////////////////////////////////////////

	/*
	** Retrieve the driver which is specific to our JDBC level.
	** We defer real work to this specific driver.
	*/
	static	Driver getDriverModule() throws SQLException {

		if ( _engineForcedDown && (_autoloadedDriver == null))
		{
			// Driver not registered
            throw Util.generateCsSQLException( SQLState.CORE_JDBC_DRIVER_UNREGISTERED );
		}

		if ( !isBooted() ) { EmbeddedDriver.boot(); }

		return _driverModule;
	}
	
	/**
	** Record which driver module actually booted.
	*  @param driver the driver register to DriverManager is not AutoloadedDriver
	**/
	static	void	registerDriverModule( Driver driver )
	{
		_driverModule = driver;
		_engineForcedDown = false;
		
        try {
            if (_autoloadedDriver == null) {
                //Support JDBC 4 or higher (DERBY-2905)
                _autoloadedDriver = makeAutoloadedDriver();
                DriverManager.registerDriver(_autoloadedDriver);
            }
        } catch (SQLException e) {
            if (SanityManager.DEBUG)
                SanityManager.THROWASSERT(e);
        }
	}
	
	/**
	** Unregister the driver and the AutoloadedDriver if exists. 
	*  This happens when the engine is forcibly shut down.
	*  
	**/
	static	void	unregisterDriverModule()
	{
		_engineForcedDown = true;
        try {
            // deregister is false if user set deregister=false attribute (DERBY-2905)
            if (InternalDriver.getDeregister() && _autoloadedDriver != null) {
                deregisterDriver(_autoloadedDriver);
                _autoloadedDriver = null;
            }

            // DERBY-5085, need to restore the default value
            InternalDriver.setDeregister(true);

            _driverModule = null;
        } catch (SQLException e) {
            if (SanityManager.DEBUG)
                SanityManager.THROWASSERT(e);
        }
	}

    private static void deregisterDriver(final AutoloadedDriver driver)
            throws SQLException {
        // DERBY-6224: DriverManager.deregisterDriver() requires a special
        // permission in JDBC 4.2 and later. Call it in a privileged block
        // so that the permission doesn't have to be granted to code that
        // invokes engine shutdown.
        try {
            AccessController.doPrivileged(
                    new PrivilegedExceptionAction<Void>() {
                public Void run() throws SQLException {
                    // Requires SQLPermission("deregisterDriver")
                    DriverManager.deregisterDriver(driver);
                    return null;
                }
            });
        } catch (PrivilegedActionException pae) {
            throw (SQLException) pae.getCause();
        } catch (AccessControlException ace) {
            // Since no permission was needed for deregisterDriver() before
            // Java 8, applications may be surprised to find that engine
            // shutdown fails because of it. For backward compatibility,
            // don't fail shutdown if the permission is missing. Instead,
            // log a message saying the driver could not be deregistered.
            Monitor.logTextMessage(MessageId.CONN_DEREGISTER_NOT_PERMITTED);
            Monitor.logThrowable(ace);
        }
    }

	/*
	** Return true if the engine has been booted.
	*/
	private	static	boolean	isBooted()
	{
		return ( _driverModule != null );
	}
	
    /**
     * load slightly more capable driver if possible.
     * But if the vm level doesn't support it, then we fall
     * back on the JDBC3 level driver.
     * @return AutoloadedDriver 
     */
    private static AutoloadedDriver makeAutoloadedDriver() 
    { 
        try { 
            return (AutoloadedDriver) Class.forName( "org.apache.derby.jdbc.AutoloadedDriver40" ).newInstance(); 
        } 
        catch (Throwable t) {} 

        return new AutoloadedDriver(); 
    } 
}

