001// Copyright (c) 2001 Hursh Jain (http://www.mollypages.org) 
002// The Molly framework is freely distributable under the terms of an
003// MIT-style license. For details, see the molly pages web site at:
004// http://www.mollypages.org/. Use, modify, have fun !
005
006package fc.jdbc;
007
008import java.io.*;
009import java.util.*;
010import java.sql.*;
011
012import fc.io.*;
013import fc.util.*;
014
015/** 
016This class implements the gateway to a SQL Database. It should be
017used to request connections.
018
019@author hursh jain
020*/
021public abstract class ConnectionMgr
022{
023final protected   Log     log;  //for all logging messages
024          String    myname = getClass().getName();  
025
026private   volatile  boolean   mgrClosed = false;
027//== jdbc ====================================
028protected volatile  String    catalogName;
029protected       String    url;
030protected       String    driver;
031protected       String    user;
032protected       String    password;
033protected       Driver    mydriver;
034protected     DBName    dbname;
035
036private static class PropertyMgrWrapper extends PropertyMgr
037  {
038  Map map = new HashMap();
039  PropertyMgrWrapper(
040    String url, String driver, 
041    String user, String password, String catalog)
042    {
043    map.put("jdbc.url", url);
044    map.put("jdbc.driver", driver);
045    map.put("jdbc.user", user);
046    map.put("jdbc.password", password);
047    map.put("jdbc.catalog", catalog);
048    }
049  
050  public String get(String name) {
051    return (String) map.get(name);
052    }   
053  public String set(String name, String backup) { Log.getDefault().error("not implemented"); return null; }
054  public void save() { Log.getDefault().error("not implemented");}
055  }
056
057/**
058Creates a new ConnectionMgr with logging to
059logging to {@link fc.io.Log#getDefault()}. 
060
061@param  jdbc.url
062@param  jdbc.driver
063@param  jdbc.user
064@param  jdbc.password
065@param  jdbc.catalog    optional, sets the default and can be
066<tt>null</tt>
067*/
068public ConnectionMgr(
069    String jdbc_url, String jdbc_driver, 
070    String jdbc_user, String jdbc_password, 
071    String jdbc_catalog)  
072  throws Exception
073  { 
074     //we need to delegate because other constructrs do
075   //things like driver initialization etc.
076  this(new PropertyMgrWrapper(
077    jdbc_url, jdbc_driver, jdbc_user, jdbc_password, 
078    jdbc_catalog));
079  }
080
081/**
082Delegates to {@link ConnectionMgr(Log, PropertyMgr)} with logging to {@link fc.io.Log#getDefault()}.
083*/
084public ConnectionMgr(PropertyMgr props) 
085throws Exception
086  {
087  this(Log.getDefault(), props, null);  
088  }
089
090/**
091Delegates to {@link ConnectionMgr(Log, PropertyMgr)} with logging to {@link fc.io.Log#getDefault()}
092and using the specified <tt>prefix</tt> for property names.
093*/
094public ConnectionMgr(PropertyMgr props, String prefix) 
095throws Exception
096  {
097  this(Log.getDefault(), props, prefix);  
098  }
099
100/**
101Constructs a new ConnectionMgr. The default implementation does not use a
102connection pool but creates new Connections, whenever needed. Expects the
103following properties, which should accessible via the specified {@link
104fc.app.PropertyMgr}.
105<ol>
106  <li><tt>jdbc.url</tt></li>
107  <li><tt>jdbc.driver</tt></li>
108  <li><tt>jdbc.user</tt></li>
109  <li><tt>jdbc.password</tt></li>
110  <li>An optional <tt>jdbc.catalog</tt> property sets the
111  default jdbc catalog. Can be <tt>null</tt>.</li>
112</ol>
113Throws an Exception if this object cannot be constructed for some reason.
114**/
115public ConnectionMgr(Log log, PropertyMgr props) 
116throws Exception
117  {
118  this(log, props, null);
119  }
120
121
122/**
123Constructs a new ConnectionMgr. The default implementation does not use a
124connection pool but creates new Connections, whenever needed. Expects the
125following properties, which should accessible via the specified {@link
126fc.app.PropertyMgr}.
127<ol>
128  <li><tt>jdbc.url</tt></li>
129  <li><tt>jdbc.driver</tt></li>
130  <li><tt>jdbc.user</tt></li>
131  <li><tt>jdbc.password</tt></li>
132  <li>An optional <tt>jdbc.catalog</tt> property sets the
133  default jdbc catalog. Can be <tt>null</tt>.</li>
134</ol>
135The optional prefix parameter (if not null) is prepended to each property name
136before it is accessed from the property manager.
137For example:
138  <blockquote><tt>jdbc.url</tt></blockquote>
139becomes:
140  <blockquote><i>prefix</i><tt>jdbc.url</tt></blockquote>
141
142Throws an Exception if this object cannot be constructed for some reason.
143**/
144public ConnectionMgr(Log log, PropertyMgr props, String prefix) 
145throws Exception
146  {
147  //1. don't call toString() from this constructor since it's
148  //overriden in subclasses
149  
150  Argcheck.notnull(log); 
151  Argcheck.notnull(props);
152  
153  this.log = log;
154  
155  url     =   props.getRequired(prefix!=null?prefix+"jdbc.url":"jdbc.url");
156  driver    =   props.getRequired(prefix!=null?prefix+"jdbc.driver":"jdbc.driver");
157  user    =   props.getRequired(prefix!=null?prefix+"jdbc.user":"jdbc.user");
158  password  =   props.getRequired(prefix!=null?prefix+"jdbc.password":"jdbc.password");
159  catalogName =   props.get(prefix!=null?prefix+"jdbc.catalog":"jdbc.catalog"); //can be null;
160  
161  // Register driver, Use normal instantiation and manually register the
162    //driver - does not rely on static initializer being run.
163    final Class mydriver_class = Class.forName(driver);
164  mydriver = (Driver) mydriver_class.newInstance();
165  DriverManager.registerDriver(mydriver);
166  
167  dbname = DBName.fromDriver(mydriver_class.getName());
168  if (dbname == null) {
169    log.warn("Cannot guess dbname from the driver. Connections will still work perfectly but getDBName() in this class will return null");
170    }
171    
172  String sep = System.getProperty("line.separator");
173  log.bug("ConnectionMgr constructor; driver="+driver+sep+"url="+url+sep+"user="+user+sep+"password="+password+sep+"catalog="+catalogName); 
174  }
175
176
177/** 
178Returns the {@link java.sql.Driver} that this connection manager
179is using to connect to the database.
180**/
181public Driver getDriver() {
182  return mydriver;
183  }
184
185/**
186Returns the dbname corresponding that the database connected to by this
187connection manager. Useful for writing database specific code as/when
188applicable.
189*/
190public DBName getDBName() throws SQLException
191  {
192  return dbname;
193  }
194
195/**
196If set, the ConnectionMgr will always return connections
197set to the specified catalog. Set this to <tt>null</tt> to
198return connections not set to any catalog.
199**/ 
200public void setCatalog(String name) {
201  this.catalogName = name;
202  }
203
204/**
205Returns a connection if successful, otherwise throws a
206<tt>SQLException</tt>. The returned has the default JDBC
207properties, including being in auto-commit mode (which means
208that it automatically commits changes after executing each
209statement).
210**/
211public Connection getConnection() throws SQLException
212  { 
213  if (mgrClosed) {
214    log.warn(this, "tried to get a connection from closed ConnectionMgr");
215      throw new SQLException("tried to get a connection from closed ConnectionMgr");
216    }
217  Connection con = getConImpl();
218  if (catalogName != null) {
219    log.bug(this, "setting catalog name to: ", catalogName);
220    con.setCatalog(catalogName);
221    }
222  log.bug(this, ", returning connection=", con);
223  return con;
224  }
225
226/**
227Closes the ConnectionMgr. After this call, no more connections can be checked
228out and any existing checked out connections are closed. Does <b>not</b> throw
229an Exception but returns false (instead) if the ConnectionMgr could not 
230be closed. Calls the {@link #handleMgrShutdown()} method inside a 
231synchronized block.
232
233@return   true if connection manager closed successfully, false otherwise
234**/
235synchronized public boolean close() 
236  {
237  synchronized (this) {
238    mgrClosed = true;
239    log.bug(this, ": closing ConnectionMgr");
240    return handleMgrShutdown();
241    }
242  }
243    
244/** Returns the jdbc url that this connection manager is using, **/
245public String getURL() {
246  return url;
247  }   
248    
249public String toString() {
250  return myname;
251  } 
252
253//== Abstract methods ======================================
254
255/** 
256Is called by the {@link #close()} method. By default does nothing but
257subclasses can override this method to handle connection manager 
258shutdown as deemed necessary.
259@return true if handler succeeded, false otherwise. 
260**/
261protected abstract boolean handleMgrShutdown();
262  
263/**
264Returns a connection to the DB everytime it's called.
265**/ 
266protected abstract Connection getConImpl() throws SQLException;
267
268}  //~class ConnectionMgr
269
270