001package com.hfg.sql.jdbc;
002
003import com.hfg.security.LoginCredentials;
004import com.hfg.util.StringUtil;
005
006import java.sql.Connection;
007import java.sql.DriverManager;
008import java.sql.SQLException;
009import java.util.concurrent.*;
010
011//------------------------------------------------------------------------------
012/**
013 * Represents a JDBC-compatible RDBMS database container.
014 *
015 * @author J. Alex Taylor, hairyfatguy.com
016 */
017//------------------------------------------------------------------------------
018// com.hfg XML/HTML Coding Library
019//
020// This library is free software; you can redistribute it and/or
021// modify it under the terms of the GNU Lesser General Public
022// License as published by the Free Software Foundation; either
023// version 2.1 of the License, or (at your option) any later version.
024//
025// This library is distributed in the hope that it will be useful,
026// but WITHOUT ANY WARRANTY; without even the implied warranty of
027// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
028// Lesser General Public License for more details.
029//
030// You should have received a copy of the GNU Lesser General Public
031// License along with this library; if not, write to the Free Software
032// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
033//
034// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com
035// jataylor@hairyfatguy.com
036//------------------------------------------------------------------------------
037
038public class JDBCServer
039{
040   private String  mName;
041   private String  mHost;
042   private Integer mPort;
043   private RDBMS   mRDBMS;
044
045
046   //###########################################################################
047   // CONSTRUCTORS
048   //###########################################################################
049
050   //---------------------------------------------------------------------------
051   public JDBCServer(RDBMS inRDBMS, String inHostname)
052   {
053      mRDBMS = inRDBMS;
054      mHost  = inHostname;
055      setPort(mRDBMS.getDefaultPort());
056   }
057
058
059   //###########################################################################
060   // PUBLIC METHODS
061   //###########################################################################
062
063   //---------------------------------------------------------------------------
064   public JDBCServer setName(String inValue)
065   {
066      mName = inValue;
067      return this;
068   }
069
070   //---------------------------------------------------------------------------
071   public String name()
072   {
073      return mName;
074   }
075
076   //---------------------------------------------------------------------------
077   /**
078    Returns the relational database management system associated with the database server.
079    */
080   public RDBMS getRDBMS()
081   {
082      return mRDBMS;
083   }
084
085   //---------------------------------------------------------------------------
086   /**
087    Specifies the host machine where the database server is located.
088    */
089   public JDBCServer setHost(String inValue)
090   {
091      mHost = inValue;
092      return this;
093   }
094
095   //---------------------------------------------------------------------------
096   /**
097    Returns the host machine where the database server is located.
098    */
099   public String getHost()
100   {
101      return mHost;
102   }
103
104   //---------------------------------------------------------------------------
105   /**
106    Specifies the port to use to connect to the database via JDBC.
107    */
108   public JDBCServer setPort(int inValue)
109   {
110      mPort = inValue;
111      return this;
112   }
113
114   //---------------------------------------------------------------------------
115   /**
116    Returns the port to use to connect to the database via JDBC.
117    */
118   public Integer getPort()
119   {
120      return mPort;
121   }
122
123   //---------------------------------------------------------------------------
124   public Connection getConnection(String inDatabaseName, LoginCredentials inCredentials, JDBCConnectionSettings inSettings)
125   {
126      Connection conn;
127
128      Integer timeout = (inSettings != null ? inSettings.getConnectTimeoutInSeconds() : null);
129
130      ExecutorService service = Executors.newSingleThreadExecutor();
131      Future<Connection> result = service.submit(new ConnectionBroker(inDatabaseName, inCredentials, inSettings));
132
133      try
134      {
135         if (timeout != null)
136         {
137            conn = result.get(timeout, TimeUnit.SECONDS);
138         }
139         else
140         {
141            conn = result.get();
142         }
143
144      }
145      catch (TimeoutException e)
146      {
147         result.cancel(true);
148         throw new JDBCException("Database connection attempt to " + (StringUtil.isSet(name()) ? name() : inDatabaseName)
149                                 + " timed out after " + timeout + " sec!", e);
150      }
151      catch (Exception e)
152      {
153         result.cancel(true);
154         throw new JDBCException("Problem while establishing a connection to "
155                                 + (StringUtil.isSet(name()) ? name() : inDatabaseName) + "!", e);
156      }
157      finally
158      {
159         service.shutdown();
160      }
161
162      return conn;
163   }
164
165   //---------------------------------------------------------------------------
166   public JDBCConnectionPool establishConnectionPool(String inDatabaseName, LoginCredentials inCredentials, JDBCConnectionPoolSettings inPoolSettings)
167   {
168      return getRDBMS().establishConnectionPool(this, inDatabaseName, inCredentials, inPoolSettings);
169   }
170
171   //---------------------------------------------------------------------------
172   public String getConnectString(String inDatabaseName)
173   {
174      return getRDBMS().getConnectString(this, inDatabaseName);
175   }
176
177   //---------------------------------------------------------------------------
178   public String getConnectString(String inDatabaseName, JDBCConnectionSettings inSettings)
179   {
180      return getRDBMS().getConnectString(this, inDatabaseName, inSettings);
181   }
182
183   //###########################################################################
184   // INNER CLASS
185   //###########################################################################
186
187   private class ConnectionBroker implements Callable<Connection>
188   {
189      String           mDatabaseName;
190      LoginCredentials mCredentials;
191      JDBCConnectionSettings mSettings;
192
193      //------------------------------------------------------------------------
194      public ConnectionBroker(String inDatabaseName, LoginCredentials inCredentials, JDBCConnectionSettings inSettings)
195      {
196         mDatabaseName = inDatabaseName;
197         mCredentials  = inCredentials;
198         mSettings     = inSettings;
199      }
200
201      //------------------------------------------------------------------------
202      public Connection call()
203         throws SQLException
204      {
205         if (mSettings != null
206             && mSettings.getConnectTimeoutInSeconds() != null
207             && mSettings.getConnectTimeoutInSeconds() > 0)
208         {
209            DriverManager.setLoginTimeout(mSettings.getConnectTimeoutInSeconds());
210         }
211
212         Connection conn;
213         try
214         {
215            conn = initConn();
216         }
217         catch (SQLException e)
218         {
219            if (e.getMessage().contains("No suitable driver found"))
220            {
221               // JDBC 4 is *supposed* to not need a Class.forName() driver initialization. However, when a different
222               // classloader is used as is the case for a Tomcat-deployed webapp, it still needs to be done.
223               try
224               {
225                  Class.forName(getRDBMS().getDriverClassName());
226                  conn = initConn();
227               }
228               catch (Exception e2)
229               {
230                  throw e;
231               }
232            }
233            else
234            {
235               throw e;
236            }
237         }
238
239         return conn;
240      }
241
242      //------------------------------------------------------------------------
243      private Connection initConn()
244            throws SQLException
245      {
246         return DriverManager.getConnection(getConnectString(mDatabaseName, mSettings),
247                                            mCredentials.getUser(),
248                                            mCredentials.getPasswordString());
249      }
250   }
251
252}