package er.extensions.jdbc; import java.net.URL; import java.net.URLClassLoader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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.NSMutableDictionary; import com.webobjects.jdbcadaptor.JDBCAdaptor; import com.webobjects.jdbcadaptor.JDBCPlugIn; import er.extensions.foundation.ERXExceptionUtilities; /** * 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 * {@literal @}madeWonderfulBy mschrag */ public class ERXJDBCConnectionAnalyzer { private static final Logger log = LoggerFactory.getLogger("er.transaction.adaptor.ConnectionAnalyzer"); private NSDictionary _connectionDictionary; private JDBCAdaptor _targetAdaptor; private 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 ERXJDBCConnectionAnalyzer(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 ERXJDBCConnectionAnalyzer(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 ERXJDBCConnectionAnalyzer(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() { NSMutableDictionary mutableConnectionDictionary = _connectionDictionary.mutableClone(); mutableConnectionDictionary.setObjectForKey("<password deleted for log>", "password"); log.info("Checking JDBC connection with information {}", mutableConnectionDictionary); EOObjectStoreCoordinator.defaultCoordinator().lock(); try { findAdaptor(); findPlugin(); findJDBCDriver(); testConnection(); } catch (RuntimeException t) { log.error(ERXExceptionUtilities.toParagraph(t)); } finally { EOObjectStoreCoordinator.defaultCoordinator().unlock(); } } /** * Attempts to load the JDBCAdaptor. */ public void findAdaptor() { log.info("Trying to create JDBCAdaptor..."); try { _targetAdaptor = (JDBCAdaptor) EOAdaptor.adaptorWithName("JDBC"); } catch (java.lang.IllegalStateException e) { log.info("Error: Failed to load JavaJDBCAdaptor.framework"); log.info("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"); } log.info("Successfully created adaptor {}", targetAdaptor().getClass()); /** ensure [targetAdaptor_created] targetAdaptor() != null; * */ } /** * Attempts to load JDBCPlugIn or sub-class and verify related configuration. */ public void findPlugin() { /** require [targetAdaptor_created] targetAdaptor() != null; * */ log.info("Trying to create plugin..."); try { _targetAdaptor.setConnectionDictionary(connectionDictionary()); _targetPlugIn = targetAdaptor().plugIn(); log.info("Created plugin {}", targetPlugIn().getClass()); } catch (java.lang.NoClassDefFoundError e) { log.info("Error: Failed to load class {} when creating JDBC plugin.", e.getMessage()); log.info("This is probably a class which is required by the plugin class and can also indicate that the JDBC driver was not found."); log.info("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"); } catch (Exception e) { // Unwrap the exception to get at the real problem Throwable t = ERXExceptionUtilities.getMeaningfulThrowable(e); log.info("Error: Plugin creationg failed.", t); throw new RuntimeException("JDBC Connection Analysis: unexpected failure creating plugin"); } if (targetPlugIn().getClass().equals(com.webobjects.jdbcadaptor.JDBCPlugIn.class)) { String driverClassName = (String) connectionDictionary().objectForKey(JDBCAdaptor.DriverKey); if ((driverClassName == null) || (driverClassName.length() == 0)) { log.info("Error: Failed to load custom JDBC plugin and connection dictionary does not include the driver class name under the key {}", JDBCAdaptor.DriverKey); log.info("Either \n(a) the plugin is missing from your classpath or \n(b) the connection dictionary has a misspelled '{}' key or \n(c) the plug-in name specified under the '{}' key is incorrect or \n(d) the class name for the JDBC driver under the key '{}' is missing from the connection dictionary or\n(e)the connection dictionary has a misspelled '{}' key", JDBCAdaptor.PlugInKey, JDBCAdaptor.PlugInKey, JDBCAdaptor.DriverKey, JDBCAdaptor.DriverKey); dumpClasspath(); throw new RuntimeException("JDBC Connection Analysis: Missing plugin or driver"); } log.info("WARNING: using generic JDBCPlugIn."); } /** ensure [targetPlugIn_created] targetPlugIn() != null; * */ } /** * Attempts to load JDBC driver class. */ public void findJDBCDriver() { /** require [targetPlugIn_created] targetPlugIn() != null; * */ log.info("Trying to load JDBC driver {}...", targetAdaptor().driverName()); Class targetDriver; try { targetDriver = Class.forName(targetAdaptor().driverName()); } catch (ClassNotFoundException e) { log.info("Error: Failed to load JDBC driver class {}", e.getMessage()); log.info("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()); } log.info("Successfully loaded JDBC driver {}", targetDriver.getName()); } /** * Attempts to make connection to databas via JDBC. */ public void testConnection() { /** require [targetPlugIn_created] targetPlugIn() != null; * */ log.info("JDBC driver and plugin are loaded, trying to connect..."); try { targetAdaptor().assertConnectionDictionaryIsValid(); } catch (RuntimeException t) { log.info("Error: Exception thrown while connecting.\nCheck exception message carefully."); throw t; } catch (Error e) { log.info("Error: Exception thrown while connecting.\nCheck exception message carefully."); throw e; } log.info("JDBC connection successful!"); } /* * Prints out the classpath being used by this JVM. */ public void dumpClasspath() { log.info("The classpath being used is: "); URLClassLoader classLoader = (URLClassLoader) getClass().getClassLoader(); URL[] sourceURLs = classLoader.getURLs(); for (int i = 0; i < sourceURLs.length; i++) { log.info("{}", sourceURLs[i]); } } /* * Prints out the Java extension directories being used by this JVM. */ public void dumpExtensionDirectories() { log.info("The JRE extension directories being used are: "); log.info(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; * */ }