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    
006    package fc.jdbc;
007    
008    import java.io.*;
009    import java.util.*;
010    import java.sql.*;
011    
012    import fc.io.*;
013    import fc.util.*;
014    
015    /** 
016    This class implements the gateway to a SQL Database. It should be
017    used to request connections.
018    
019    @author hursh jain
020    */
021    public abstract class ConnectionMgr
022    {
023    final protected   Log     log;  //for all logging messages
024              String    myname = getClass().getName();  
025    
026    private   volatile  boolean   mgrClosed = false;
027    //== jdbc ====================================
028    protected volatile  String    catalogName;
029    protected       String    url;
030    protected       String    driver;
031    protected       String    user;
032    protected       String    password;
033    protected       Driver    mydriver;
034    protected     DBName    dbname;
035    
036    private 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    /**
058    Creates a new ConnectionMgr with logging to
059    logging 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    */
068    public 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    /**
082    Delegates to {@link ConnectionMgr(Log, PropertyMgr)} with logging to {@link fc.io.Log#getDefault()}.
083    */
084    public ConnectionMgr(PropertyMgr props) 
085    throws Exception
086      {
087      this(Log.getDefault(), props, null);  
088      }
089    
090    /**
091    Delegates to {@link ConnectionMgr(Log, PropertyMgr)} with logging to {@link fc.io.Log#getDefault()}
092    and using the specified <tt>prefix</tt> for property names.
093    */
094    public ConnectionMgr(PropertyMgr props, String prefix) 
095    throws Exception
096      {
097      this(Log.getDefault(), props, prefix);  
098      }
099    
100    /**
101    Constructs a new ConnectionMgr. The default implementation does not use a
102    connection pool but creates new Connections, whenever needed. Expects the
103    following properties, which should accessible via the specified {@link
104    fc.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>
113    Throws an Exception if this object cannot be constructed for some reason.
114    **/
115    public ConnectionMgr(Log log, PropertyMgr props) 
116    throws Exception
117      {
118      this(log, props, null);
119      }
120    
121    
122    /**
123    Constructs a new ConnectionMgr. The default implementation does not use a
124    connection pool but creates new Connections, whenever needed. Expects the
125    following properties, which should accessible via the specified {@link
126    fc.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>
135    The optional prefix parameter (if not null) is prepended to each property name
136    before it is accessed from the property manager.
137    For example:
138      <blockquote><tt>jdbc.url</tt></blockquote>
139    becomes:
140      <blockquote><i>prefix</i><tt>jdbc.url</tt></blockquote>
141    
142    Throws an Exception if this object cannot be constructed for some reason.
143    **/
144    public ConnectionMgr(Log log, PropertyMgr props, String prefix) 
145    throws 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    /** 
178    Returns the {@link java.sql.Driver} that this connection manager
179    is using to connect to the database.
180    **/
181    public Driver getDriver() {
182      return mydriver;
183      }
184    
185    /**
186    Returns the dbname corresponding that the database connected to by this
187    connection manager. Useful for writing database specific code as/when
188    applicable.
189    */
190    public DBName getDBName() throws SQLException
191      {
192      return dbname;
193      }
194    
195    /**
196    If set, the ConnectionMgr will always return connections
197    set to the specified catalog. Set this to <tt>null</tt> to
198    return connections not set to any catalog.
199    **/ 
200    public void setCatalog(String name) {
201      this.catalogName = name;
202      }
203    
204    /**
205    Returns a connection if successful, otherwise throws a
206    <tt>SQLException</tt>. The returned has the default JDBC
207    properties, including being in auto-commit mode (which means
208    that it automatically commits changes after executing each
209    statement).
210    **/
211    public 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    /**
227    Closes the ConnectionMgr. After this call, no more connections can be checked
228    out and any existing checked out connections are closed. Does <b>not</b> throw
229    an Exception but returns false (instead) if the ConnectionMgr could not 
230    be closed. Calls the {@link #handleMgrShutdown()} method inside a 
231    synchronized block.
232    
233    @return   true if connection manager closed successfully, false otherwise
234    **/
235    synchronized 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, **/
245    public String getURL() {
246      return url;
247      }   
248        
249    public String toString() {
250      return myname;
251      } 
252    
253    //== Abstract methods ======================================
254    
255    /** 
256    Is called by the {@link #close()} method. By default does nothing but
257    subclasses can override this method to handle connection manager 
258    shutdown as deemed necessary.
259    @return true if handler succeeded, false otherwise. 
260    **/
261    protected abstract boolean handleMgrShutdown();
262      
263    /**
264    Returns a connection to the DB everytime it's called.
265    **/ 
266    protected abstract Connection getConImpl() throws SQLException;
267    
268    }  //~class ConnectionMgr
269    
270