001: /*
002: * ConnectionMgr.java
003: *
004: * This file is part of SQL Workbench/J, http://www.sql-workbench.net
005: *
006: * Copyright 2002-2008, Thomas Kellerer
007: * No part of this code maybe reused without the permission of the author
008: *
009: * To contact the author please send an email to: support@sql-workbench.net
010: *
011: */
012: package workbench.db;
013:
014: import java.beans.PropertyChangeEvent;
015: import java.beans.PropertyChangeListener;
016: import java.io.FileNotFoundException;
017: import java.io.IOException;
018: import java.io.InputStream;
019: import java.sql.Connection;
020: import java.sql.Driver;
021: import java.sql.SQLException;
022: import java.util.ArrayList;
023: import java.util.Collection;
024: import java.util.Collections;
025: import java.util.HashMap;
026: import java.util.Iterator;
027: import java.util.List;
028: import java.util.Map;
029: import java.util.Set;
030: import java.util.TreeSet;
031: import workbench.WbManager;
032: import workbench.db.shutdown.DbShutdownFactory;
033: import workbench.db.shutdown.DbShutdownHook;
034: import workbench.gui.profiles.ProfileKey;
035:
036: import workbench.util.ExceptionUtil;
037: import workbench.log.LogMgr;
038: import workbench.resource.ResourceMgr;
039: import workbench.resource.Settings;
040: import workbench.util.FileUtil;
041: import workbench.util.PropertiesCopier;
042: import workbench.util.StringUtil;
043: import workbench.util.WbPersistence;
044:
045: /**
046: * A connection factory for the application.
047: *
048: * @author support@sql-workbench.net
049: */
050: public class ConnectionMgr implements PropertyChangeListener {
051: private Map<String, WbConnection> activeConnections = new HashMap<String, WbConnection>();
052:
053: private ArrayList<ConnectionProfile> profiles;
054: private List<DbDriver> drivers;
055: private boolean profilesChanged;
056: private boolean readTemplates = true;
057: private boolean templatesImported;
058: private List<PropertyChangeListener> groupsChangeListener;
059: private static ConnectionMgr instance = new ConnectionMgr();
060:
061: private ConnectionMgr() {
062: Settings.getInstance().addPropertyChangeListener(this ,
063: Settings.PROPERTY_PROFILE_STORAGE);
064: }
065:
066: public static ConnectionMgr getInstance() {
067: return instance;
068: }
069:
070: /**
071: * Create a new connection. The profile to be used
072: * is searched by the given profile key
073: * @param def the profile to be used
074: * @param anId the id to be assigned to the connection
075: * @return a new Connection
076: *
077: * @throws java.lang.ClassNotFoundException
078: * @throws java.sql.SQLException
079: * @throws java.lang.Exception
080: */
081: public WbConnection getConnection(ProfileKey def, String anId)
082: throws ClassNotFoundException, SQLException, Exception {
083: ConnectionProfile prof = this .getProfile(def);
084: if (prof == null)
085: return null;
086:
087: return this .getConnection(prof, anId);
088: }
089:
090: public WbConnection findConnection(String id) {
091: return this .activeConnections.get(id);
092: }
093:
094: public WbConnection getConnection(ConnectionProfile aProfile,
095: String anId) throws ClassNotFoundException, SQLException {
096: this .disconnect(anId);
097: LogMgr.logInfo("ConnectionMgr.getConnection()",
098: "Creating new connection for [" + aProfile.getKey()
099: + "] for driver=" + aProfile.getDriverclass());
100: WbConnection conn = this .connect(aProfile, anId);
101: conn.runPostConnectScript();
102: String driverVersion = conn.getDriverVersion();
103: String dbVersion = conn.getDatabaseVersion();
104:
105: LogMgr.logInfo("ConnectionMgr.getConnection()",
106: "Connected to: [" + conn.getMetadata().getProductName()
107: + "], Database version: [" + dbVersion
108: + "], Driver version: [" + driverVersion
109: + "], ID: [" + anId + "]");
110:
111: this .activeConnections.put(anId, conn);
112:
113: return conn;
114: }
115:
116: public Class loadClassFromDriverLib(ConnectionProfile profile,
117: String className) throws ClassNotFoundException {
118: String drvClass = profile.getDriverclass();
119: String drvName = profile.getDriverName();
120: DbDriver drv = this .findDriverByName(drvClass, drvName);
121: if (drv == null)
122: return null;
123: return drv.loadClassFromDriverLib(className);
124: }
125:
126: WbConnection connect(ConnectionProfile aProfile, String anId)
127: throws ClassNotFoundException, SQLException {
128:
129: String drvClass = aProfile.getDriverclass();
130: String drvName = aProfile.getDriverName();
131: //long start, end;
132: //start = System.currentTimeMillis();
133: DbDriver drv = this .findDriverByName(drvClass, drvName);
134: //end = System.currentTimeMillis();
135: //LogMgr.logDebug("ConnectionMgr.connect()", "FindDriver took " + (end - start) + " ms");
136: if (drv == null) {
137: throw new SQLException("Driver class not registered");
138: }
139:
140: copyPropsToSystem(aProfile);
141:
142: Connection sql = drv.connect(aProfile.getUrl(), aProfile
143: .getUsername(), aProfile.decryptPassword(), anId,
144: aProfile.getConnectionProperties());
145: try {
146: sql.setAutoCommit(aProfile.getAutocommit());
147: } catch (Throwable th) {
148: // some drivers do not support this, so
149: // we just ignore the error :-)
150: LogMgr.logInfo("ConnectionMgr.connect()", "Driver ("
151: + drv.getDriverClass()
152: + ") does not support the autocommit property: "
153: + ExceptionUtil.getDisplay(th));
154: }
155: WbConnection conn = new WbConnection(anId, sql, aProfile);
156: return conn;
157: }
158:
159: private void copyPropsToSystem(ConnectionProfile profile) {
160: if (profile != null && profile.getCopyExtendedPropsToSystem()) {
161: PropertiesCopier copier = new PropertiesCopier();
162: copier.copyToSystem(profile.getConnectionProperties());
163: }
164: }
165:
166: private void removePropsFromSystem(ConnectionProfile profile) {
167: if (profile != null && profile.getCopyExtendedPropsToSystem()) {
168: PropertiesCopier copier = new PropertiesCopier();
169: copier.removeFromSystem(profile.getConnectionProperties());
170: }
171: }
172:
173: public DbDriver findDriverByName(String drvClassName, String aName) {
174: DbDriver firstMatch = null;
175:
176: if (this .drivers == null)
177: this .readDrivers();
178:
179: if (aName == null || aName.length() == 0)
180: return this .findDriver(drvClassName);
181:
182: for (DbDriver db : drivers) {
183: if (db.getDriverClass().equals(drvClassName)) {
184: // if the classname and the driver name are the same return the driver immediately
185: // If we don't find a match for the name, we'll use
186: // the first match for the classname
187: if (db.getName().equals(aName))
188: return db;
189: if (firstMatch == null) {
190: firstMatch = db;
191: }
192: }
193: }
194:
195: // In batch mode the default drivers (DriverTemplates.xml) are not loaded.
196: if (firstMatch == null && WbManager.getInstance().isBatchMode()) {
197: // We simple pretend there is one available, this will e.g. make
198: // the ODBC Bridge work without a WbDrivers.xml
199: return new DbDriver(aName, drvClassName, null);
200: }
201:
202: LogMgr.logDebug("ConnectionMgr.findDriverByName()",
203: "Did not find driver with name="
204: + aName
205: + ", using "
206: + (firstMatch == null ? "(n/a)" : firstMatch
207: .getName()));
208:
209: return firstMatch;
210: }
211:
212: public DbDriver findRegisteredDriver(String drvClassName) {
213: if (this .drivers == null)
214: this .readDrivers();
215:
216: DbDriver db = null;
217:
218: for (int i = 0; i < this .drivers.size(); i++) {
219: db = this .drivers.get(i);
220: if (db.getDriverClass().equals(drvClassName))
221: return db;
222: }
223: return null;
224: }
225:
226: public DbDriver findDriver(String drvClassName) {
227: if (drvClassName == null) {
228: LogMgr.logError("ConnectionMgr.findDriver()",
229: "Called with a null classname!",
230: new NullPointerException());
231: return null;
232: }
233:
234: DbDriver db = this .findRegisteredDriver(drvClassName);
235:
236: if (db == null) {
237: LogMgr.logWarning("ConnectionMgr.findDriver()",
238: "Did not find a registered driver with classname = ["
239: + drvClassName + "]");
240: try {
241: // not found --> maybe it's present in the normal classpath...
242: // eg the ODBC bridge
243: Class drvcls = Class.forName(drvClassName);
244: Driver drv = (Driver) drvcls.newInstance();
245: db = new DbDriver(drv);
246: } catch (Exception cnf) {
247: LogMgr.logError("ConnectionMgr.findDriver()",
248: "Error creating instance for driver class ["
249: + drvClassName + "] ", cnf);
250: db = null;
251: }
252: }
253: return db;
254: }
255:
256: /**
257: * Add a new, dynamically defined driver to the list of available
258: * drivers.
259: * This is used if a driver definition is passed on the commandline
260: *
261: * @see workbench.sql.BatchRunner#createCmdLineProfile(workbench.util.ArgumentParser)
262: */
263: public DbDriver registerDriver(String drvClassName, String jarFile) {
264: if (this .drivers == null)
265: this .readDrivers();
266:
267: DbDriver drv = new DbDriver("JdbcDriver", drvClassName, jarFile);
268:
269: // this method is called from BatchRunner.createCmdLineProfile() when
270: // the user passed all driver information on the command line.
271: // as most likely this is the correct driver it has to be put
272: // at the beginning of the list, to prevent a different driver
273: // with the same driver class in WbDrivers.xml to be used instead
274: this .drivers.add(0, drv);
275:
276: return drv;
277: }
278:
279: /**
280: * Returns a List of registered drivers.
281: * This list is read from WbDrivers.xml
282: */
283: public List<DbDriver> getDrivers() {
284: if (this .drivers == null) {
285: this .readDrivers();
286: }
287: return this .drivers;
288: }
289:
290: public void setDrivers(List<DbDriver> aDriverList) {
291: this .drivers = aDriverList;
292: }
293:
294: /**
295: * Find a connection profile identified by the given key.
296: *
297: * @param key the key of the profile
298: * @return a connection profile with that name or null if none was found.
299: */
300: public ConnectionProfile getProfile(ProfileKey key) {
301: this .getProfiles();
302: if (key == null)
303: return null;
304: String name = key.getName();
305: String group = key.getGroup();
306: if (this .profiles == null)
307: return null;
308: ConnectionProfile firstMatch = null;
309: for (ConnectionProfile prof : this .profiles) {
310: if (name.equalsIgnoreCase(prof.getName())) {
311: if (firstMatch == null)
312: firstMatch = prof;
313: if (group == null) {
314: return prof;
315: } else if (group.equalsIgnoreCase(prof.getGroup())) {
316: return prof;
317: }
318: }
319: }
320: return firstMatch;
321: }
322:
323: public synchronized Collection<String> getProfileGroups() {
324: Set<String> result = new TreeSet<String>();
325: if (this .profiles == null)
326: this .readProfiles();
327: for (ConnectionProfile prof : this .profiles) {
328: String group = prof.getGroup();
329: if (StringUtil.isEmptyString(group))
330: continue;
331: result.add(group);
332: }
333: return result;
334: }
335:
336: public void addProfileGroupChangeListener(PropertyChangeListener l) {
337: if (this .groupsChangeListener == null)
338: this .groupsChangeListener = new ArrayList<PropertyChangeListener>();
339: this .groupsChangeListener.add(l);
340: }
341:
342: public void removeProfileGroupChangeListener(
343: PropertyChangeListener l) {
344: if (groupsChangeListener == null)
345: return;
346: groupsChangeListener.remove(l);
347: }
348:
349: public void profileGroupChanged(ConnectionProfile profile) {
350: if (this .groupsChangeListener == null)
351: return;
352: Iterator itr = this .groupsChangeListener.iterator();
353: PropertyChangeEvent evt = new PropertyChangeEvent(profile,
354: ConnectionProfile.PROPERTY_PROFILE_GROUP, null, profile
355: .getGroup());
356: for (PropertyChangeListener l : this .groupsChangeListener) {
357: if (l != null)
358: l.propertyChange(evt);
359: }
360: }
361:
362: /**
363: * Returns a Map with the current profiles.
364: * The key to the map is the profile name, the value is the actual profile
365: * (i.e. instances of {@link ConnectionProfile}
366: */
367: public synchronized List<ConnectionProfile> getProfiles() {
368: if (this .profiles == null)
369: this .readProfiles();
370: return Collections.unmodifiableList(this .profiles);
371: }
372:
373: /**
374: * Disconnects all connections
375: */
376: public void disconnectAll() {
377: Iterator<WbConnection> itr = this .activeConnections.values()
378: .iterator();
379: for (WbConnection con : this .activeConnections.values()) {
380: this .closeConnection(con);
381: }
382: this .activeConnections.clear();
383: }
384:
385: public synchronized void disconnect(WbConnection con) {
386: if (con == null)
387: return;
388: this .activeConnections.remove(con.getId());
389: this .closeConnection(con);
390: }
391:
392: /**
393: * Disconnect the connection with the given id
394: */
395: public synchronized void disconnect(String anId) {
396: WbConnection con = this .activeConnections.get(anId);
397: disconnect(con);
398: }
399:
400: /**
401: * Disconnect the given connection.
402: * @param conn the connection to disconnect.
403: */
404: private synchronized void closeConnection(WbConnection conn) {
405: if (conn == null)
406: return;
407: if (conn.isClosed())
408: return;
409:
410: try {
411: if (conn.getProfile() != null) {
412: LogMgr.logInfo("ConnectionMgr.disconnect()",
413: "Disconnecting: ["
414: + conn.getProfile().getName()
415: + "], ID=" + conn.getId());
416: }
417:
418: conn.runPreDisconnectScript();
419:
420: removePropsFromSystem(conn.getProfile());
421:
422: DbShutdownHook hook = DbShutdownFactory
423: .getShutdownHook(conn);
424: if (hook != null) {
425: hook.shutdown(conn);
426: } else {
427: conn.close();
428: }
429: } catch (Exception e) {
430: LogMgr.logError(this , ResourceMgr
431: .getString("ErrOnDisconnect"), e);
432: }
433: }
434:
435: /**
436: * Check if there is another connection active with the same URL.
437: * This is used when the connection to an embedded database that
438: * needs a {@link workbench.db.shutdown.DbShutdownHook} is called.
439: *
440: * @return true if there is another active connection.
441: */
442: public boolean isActive(WbConnection aConn) {
443: String url = aConn.getUrl();
444: String id = aConn.getId();
445:
446: Iterator itr = this .activeConnections.values().iterator();
447: while (itr.hasNext()) {
448: WbConnection c = (WbConnection) itr.next();
449: if (c == null)
450: continue;
451:
452: if (c.getId().equals(id))
453: continue;
454:
455: String u = c.getUrl();
456: if (u == null)
457: continue;
458: // we found one connection with the same URL
459: if (u.equals(url))
460: return true;
461: }
462:
463: return false;
464: }
465:
466: /**
467: * Save profile and driver definitions to external files.
468: * This merely calls {@link #saveProfiles()} and {@link #saveDrivers()}
469: * @see #saveProfiles()
470: * @see #saveDrivers()
471: */
472: public void writeSettings() {
473: this .saveProfiles();
474: this .saveDrivers();
475: }
476:
477: /**
478: * Saves the driver definitions to an external file.
479: *
480: * The name of the file defaults to <tt>WbDrivers.xml</tt>. The exact location
481: * can be set in the configuration file.
482: * @see workbench.resource.Settings#getDriverConfigFilename()
483: * @see WbPersistence#writeObject(Object)
484: */
485: public void saveDrivers() {
486: WbPersistence writer = new WbPersistence(Settings.getInstance()
487: .getDriverConfigFilename());
488: try {
489: writer.writeObject(this .drivers);
490: } catch (IOException e) {
491: LogMgr.logError("ConnectionMgr.saveDrivers()",
492: "Could not save drivers", e);
493: }
494: }
495:
496: @SuppressWarnings("unchecked")
497: private void readDrivers() {
498: try {
499: WbPersistence reader = new WbPersistence(Settings
500: .getInstance().getDriverConfigFilename());
501: Object result = reader.readObject();
502: if (result == null) {
503: this .drivers = new ArrayList<DbDriver>();
504: } else if (result instanceof ArrayList) {
505: this .drivers = (List<DbDriver>) result;
506: }
507: } catch (FileNotFoundException fne) {
508: LogMgr.logDebug("ConnectionMgr.readDrivers()",
509: "WbDrivers.xml not found. Using defaults.");
510: this .drivers = null;
511: } catch (Exception e) {
512: LogMgr
513: .logDebug(
514: this ,
515: "Could not load driver definitions. Creating new one...",
516: e);
517: this .drivers = null;
518: }
519:
520: if (this .drivers == null) {
521: this .drivers = new ArrayList<DbDriver>();
522: }
523: if (this .readTemplates) {
524: this .importTemplateDrivers();
525: }
526: }
527:
528: public void setReadTemplates(boolean aFlag) {
529: this .readTemplates = aFlag;
530: }
531:
532: @SuppressWarnings("unchecked")
533: private void importTemplateDrivers() {
534: if (this .templatesImported)
535: return;
536:
537: if (this .drivers == null)
538: this .readDrivers();
539:
540: // now read the templates and append them to the driver list
541: InputStream in = null;
542: try {
543: in = this .getClass().getResourceAsStream(
544: "DriverTemplates.xml");
545:
546: WbPersistence reader = new WbPersistence();
547: ArrayList<DbDriver> templates = (ArrayList<DbDriver>) reader
548: .readObject(in);
549:
550: for (int i = 0; i < templates.size(); i++) {
551: DbDriver drv = templates.get(i);
552: if (!this .drivers.contains(drv)) {
553: this .drivers.add(drv);
554: }
555: }
556: } catch (Throwable io) {
557: LogMgr.logWarning("ConectionMgr.readDrivers()",
558: "Could not read driver templates!");
559: } finally {
560: FileUtil.closeQuitely(in);
561: }
562: this .templatesImported = true;
563: }
564:
565: /**
566: * Remove all defined connection profiles.
567: * This does not make sure that all connections are closed!
568: * This method is used in Unit tests to setup a new set of profiles.
569: */
570: public synchronized void clearProfiles() {
571: if (this .profiles == null)
572: return;
573: this .profiles.clear();
574: }
575:
576: /**
577: * Retrieves the connection profiles from an XML file.
578: *
579: * @see WbPersistence#readObject()
580: * @see workbench.resource.Settings#getProfileStorage()
581: */
582: public synchronized void readProfiles() {
583: Object result = null;
584: try {
585: WbPersistence reader = new WbPersistence(Settings
586: .getInstance().getProfileStorage());
587: result = reader.readObject();
588: } catch (FileNotFoundException fne) {
589: LogMgr.logDebug("ConnectionMgr.readProfiles()",
590: "WbProfiles.xml not found. Creating new one.");
591: result = null;
592: } catch (Exception e) {
593: LogMgr.logError("ConnectionMgr.readProfiles()",
594: "Error when reading connection profiles", e);
595: result = null;
596: }
597:
598: this .profiles = new ArrayList<ConnectionProfile>();
599:
600: if (result instanceof Collection) {
601: Collection c = (Collection) result;
602: this .profiles.ensureCapacity(c.size());
603: Iterator itr = c.iterator();
604: while (itr.hasNext()) {
605: ConnectionProfile prof = (ConnectionProfile) itr.next();
606: this .profiles.add(prof);
607: }
608: } else if (result instanceof Object[]) {
609: // This is to support the very first version of the profile storage
610: // probably obsolete by know, but you never know...
611: Object[] l = (Object[]) result;
612: this .profiles.ensureCapacity(l.length);
613: for (int i = 0; i < l.length; i++) {
614: ConnectionProfile prof = (ConnectionProfile) l[i];
615: this .profiles.add(prof);
616: }
617: }
618: this .resetProfiles();
619: }
620:
621: /**
622: * Reset the changed status on the profiles.
623: *
624: * Called after saving the profiles.
625: */
626: private synchronized void resetProfiles() {
627: if (this .profiles != null) {
628: for (ConnectionProfile profile : this .profiles) {
629: profile.reset();
630: }
631: this .profilesChanged = false;
632: }
633: }
634:
635: /**
636: * Save the connectioin profiles to an external file.
637: *
638: * This will also reset the changed flag for any modified or new
639: * profiles. The name of the file defaults to <tt>WbProfiles.xml</tt>, but
640: * can be defined in the configuration properties.
641: *
642: * @see workbench.resource.Settings#getProfileStorage()
643: * @see WbPersistence#writeObject(Object)
644: */
645: public synchronized void saveProfiles() {
646: if (this .profiles != null) {
647: WbPersistence writer = new WbPersistence(Settings
648: .getInstance().getProfileStorage());
649: try {
650: writer.writeObject(this .profiles);
651: this .resetProfiles();
652: } catch (IOException e) {
653: LogMgr.logError("ConnectionMgr.saveProfiles()",
654: "Error saving profiles", e);
655: }
656: }
657: }
658:
659: /**
660: * Returns true if any of the profile definitions has changed.
661: * (Or if a profile has been deleted or added)
662: */
663: public boolean profilesAreModified() {
664: if (this .profilesChanged)
665: return true;
666: if (this .profiles == null)
667: return false;
668: for (ConnectionProfile profile : this .profiles) {
669: if (profile.isChanged()) {
670: return true;
671: }
672: }
673: return false;
674: }
675:
676: /**
677: * This is called from the ProfileListModel when a new profile is added.
678: * The caller needs to make sure that the status is set to new if that
679: * profile was just created.
680: */
681: public void addProfile(ConnectionProfile aProfile) {
682: if (this .profiles == null) {
683: this .readProfiles();
684: }
685: this .profiles.add(aProfile);
686: this .profilesChanged = true;
687: }
688:
689: /**
690: * This is called from the ProfileListModel when a profile has been deleted
691: */
692: public void removeProfile(ConnectionProfile aProfile) {
693: if (this .profiles == null)
694: return;
695:
696: this .profiles.remove(aProfile);
697: // deleting a new profile should not change the status to changed
698: if (!aProfile.isNew()) {
699: this .profilesChanged = true;
700: }
701: }
702:
703: /**
704: * When the property {@link Settings#PROPERTY_PROFILE_STORAGE} is changed
705: * the current list of profiles is cleared.
706: *
707: * @param evt
708: * @see #clearProfiles()
709: */
710: public void propertyChange(java.beans.PropertyChangeEvent evt) {
711: if (evt.getPropertyName().equals(
712: Settings.PROPERTY_PROFILE_STORAGE)) {
713: this.clearProfiles();
714: }
715: }
716:
717: }
|