package com.apress.practicalwo.practicalutilities;

import java.net.URL;
import java.net.URLClassLoader;

import com.webobjects.eoaccess.EOAdaptor;
import com.webobjects.eoaccess.EOModel;
import com.webobjects.eoaccess.EOModelGroup;
import com.webobjects.eocontrol.EOObjectStoreCoordinator;
import com.webobjects.foundation.NSDictionary;
import com.webobjects.foundation.NSLog;
import com.webobjects.jdbcadaptor.JDBCAdaptor;
import com.webobjects.jdbcadaptor.JDBCPlugIn;


/**
 * Attempts to verify that a JDBC connection can be made and prints out diagnostic
 * suggestions and information if it cannot.
 * 
 * @author Charles Hill and Sacha Mallais
 */
public class EOFJDBCConnectionAnalyzer
{

    protected NSDictionary connectionDictionary;
    protected JDBCAdaptor targetAdaptor;
    protected JDBCPlugIn targetPlugIn;



    /**
     * Designated constructor.  Uses the information in <code>aConnectionDictionary</code> 
     * to attempt to make a JDBC connection.
     * 
     * @param aConnectionDictionary the connection information for the JDBC connection
     */ 
    public EOFJDBCConnectionAnalyzer(NSDictionary aConnectionDictionary)
    {
        super();
        
        /** require [valid_connection_dictionary] aConnectionDictionary != null;  **/
        
        connectionDictionary = aConnectionDictionary;
        analyzeConnection();
    }



    /**
     * Uses the connection dictionary information in <code>aModel</code> to attempt 
     * to make a JDBC connection.
     * 
     * @param aModel the <code>EOModel</code> from which to take the connection 
     * information for the JDBC connection 
     */
    public EOFJDBCConnectionAnalyzer(EOModel aModel)
    {
        this(aModel.connectionDictionary());

        /** require [valid_model] aModel != null;  **/
    }



    /**
     * Uses the connection dictionary information in the EOModel named 
     * <code>aModelName</code>aModel to attempt to make a JDBC connection.
     * 
     * @param aModelName the name of the <code>EOModel</code> from which to take 
     * the connection information for the JDBC connection 
     */
    public EOFJDBCConnectionAnalyzer(String aModelName)
    {
        this(EOModelGroup.defaultGroup().modelNamed(aModelName));
        
        /** require [valid_model_name] aModelName != null;  
         [model_exists] EOModelGroup.defaultGroup().modelNamed(aModelName) != null; **/
    }


    /**
     * Controls the order of analysis. 
     */
    public void analyzeConnection()
    {
        NSLog.out.appendln("Checking JDBC connection with information " +
            connectionDictionary);
            
        EOObjectStoreCoordinator.defaultCoordinator().lock();
        try
        {
            findAdaptor();
            findPlugin();
            findJDBCDriver();
            testConnection();
        }
        finally
        {
            EOObjectStoreCoordinator.defaultCoordinator().unlock();
        }
    }



    /**
     * Attempts to load the JDBCAdaptor.
     */
    public void findAdaptor()
    {
        NSLog.out.appendln("Trying to create JDBCAdaptor...");
        try
        {
            targetAdaptor = (JDBCAdaptor)EOAdaptor.adaptorWithName("JDBC");
        }
        catch (java.lang.IllegalStateException e)
        {
            NSLog.out.appendln("Error: Failed to load JavaJDBCAdaptor.framework");
             NSLog.out.appendln("This framework needs to be included in your " + 
             "application to make JDBC connections");
             dumpClasspath();
             throw new RuntimeException("JDBC Connection Analysis: JavaJDBCAdaptor.framework not on classpath");
        }
        NSLog.out.appendln("Successfully created adaptor " + targetAdaptor().getClass().getName());

        /** ensure [targetAdaptor_created] targetAdaptor() != null;  **/
    }
    
    
    
    /**
     * Attempts to load JDBCPlugIn or sub-class and verify related configuration.
     */
    public void findPlugin()
    {
        /** require [targetAdaptor_created] targetAdaptor() != null;  **/
        
        NSLog.out.appendln("Trying to create plugin...");
        
        try
        {        
            targetAdaptor.setConnectionDictionary(connectionDictionary());
            targetPlugIn = targetAdaptor().plugIn();
            NSLog.out.appendln("Created plugin " + targetPlugIn().getClass().getName());
        }
        catch (java.lang.NoClassDefFoundError e)
        {
            NSLog.out.appendln("Error: Failed to load class " + e.getMessage() + 
            "when creating JDBC plugin.");
            NSLog.out.appendln("This is probably a class which is required by the " + 
            "plugin class and can also indicate that the JDBC driver was not found.");
            NSLog.out.appendln("Either (a) your classpath is wrong or (b) something " + 
            "is missing from the JRE extensions directory/ies.");
            dumpClasspath();
            dumpExtensionDirectories();
            throw new RuntimeException("JDBC Connection Analysis: Missing class needed by plugin");
        }
        
        if (targetPlugIn().getClass().equals(com.webobjects.jdbcadaptor.JDBCPlugIn.class))
        {
            String driverClassName = (String)connectionDictionary().objectForKey(JDBCAdaptor.DriverKey); 
            if ((driverClassName == null) || (driverClassName.length() == 0))
            {
                NSLog.out.appendln("Error: Failed to load custom JDBC plugin and " + 
                "connection dictionary does not include the driver class name under " +
                "the key " + JDBCAdaptor.DriverKey);
                NSLog.out.appendln("Either \n(a) the plugin is missing from your classpath " + 
                "or \n(b) the connection dictionary has a misspelled '" + JDBCAdaptor.PlugInKey + 
                "' key or \n(c) the plug-in name specified under the '" + JDBCAdaptor.PlugInKey + 
                "' key is incorrect or \n(d) the class name for the JDBC driver under the " + 
                "key '" + JDBCAdaptor.DriverKey + "' is missing from the connection dictionary or" +
                "\n(e)the connection dictionary has a misspelled '" + JDBCAdaptor.DriverKey + "' key");
                dumpClasspath();
                throw new RuntimeException("JDBC Connection Analysis: Missing plugin or driver");
            }
            else
            {
                NSLog.out.appendln("WARNING: using generic JDBCPlugIn."); 
            } 
        }

        /** ensure [targetPlugIn_created] targetPlugIn() != null;  **/
    }
    
    
    
    /**
     * Attempts to load JDBC driver class.
     */
       public void findJDBCDriver()
    {
        /** require [targetPlugIn_created] targetPlugIn() != null;  **/
        
        NSLog.out.appendln("Trying to load JDBC driver " + targetAdaptor().driverName() + "...");
        Class targetDriver;
        
        try
        {
            targetDriver = Class.forName(targetAdaptor().driverName());
        }
        catch (ClassNotFoundException e)
        {
            NSLog.out.appendln("Error: Failed to load JDBC driver class " + e.getMessage());
            NSLog.out.appendln("The JDBC driver jar is either missing from  (a) " + 
            "your classpath or (b) the JRE extensions directory/ies.");
            dumpClasspath();
            dumpExtensionDirectories();
            throw new RuntimeException("JDBC Connection Analysis: Cannot load JDBC driver. " + e.getMessage());
        }
        NSLog.out.appendln("Successfully loaded JDBC driver " + targetDriver.getName());
    }
    
    
    
    /**
     * Attempts to make connection to databas via JDBC.
     */
    public void testConnection()
    {
        /** require [targetPlugIn_created] targetPlugIn() != null;  **/
        
        NSLog.out.appendln("JDBC driver and plugin are loaded, trying to connect...");
        try
        {
            targetAdaptor().assertConnectionDictionaryIsValid();
        }
        catch (RuntimeException t)
        {
            NSLog.out.appendln("Error: Exception thrown while connecting.\n" + 
            "Check exception message carefully.");
            throw t;
        }
        catch (Error e)
        {
            NSLog.out.appendln("Error: Exception thrown while connecting.\n" + 
            "Check exception message carefully.");
            throw e;
        }
        NSLog.out.appendln("JDBC connection successful!");
    }
    
    
    
    /*
     * Prints out the classpath being used by this JVM.
     */
    public void dumpClasspath()
    {
        NSLog.out.appendln("The classpath being used is: ");

        URLClassLoader classLoader = (URLClassLoader)getClass().getClassLoader();
        URL[] sourceURLs = classLoader.getURLs();
        for (int i = 0; i < sourceURLs.length; i++)
        {
            NSLog.out.appendln(sourceURLs[i]);
        }
    }



    /*
     * Prints out the Java extension directories being used by this JVM.
     */
    public void dumpExtensionDirectories()
    {
        NSLog.out.appendln("The JRE extension directories being used are: ");
        NSLog.out.appendln(System.getProperties().getProperty("java.ext.dirs"));
    }



	/**
	 * Returns the connection dictionary being analyzed.
     * 
     * @return the connection dictionary being analyzed.
	 */
	public NSDictionary connectionDictionary()
	{
		return connectionDictionary;
	}



	/**
	 * Returns an instance of JDBCAdaptor.
     * 
     * @return an instance of JDBCAdaptor
	 */
	public JDBCAdaptor targetAdaptor()
	{
		return targetAdaptor;
	}



    /**
     * Returns an instance of JDBCPlugIn or sub-class created from the connection
     * dictionary information.
     * 
     * @return an instance of JDBCPlugIn or sub-class
     */
	public JDBCPlugIn targetPlugIn()
	{
		return targetPlugIn;
	}


    /** invariant [valid_connectionDictionary] connectionDictionary != null; **/
}
