001 package org.apache.ojb.broker.accesslayer;
002
003 /* Copyright 2002-2005 The Apache Software Foundation
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018 import org.apache.ojb.broker.metadata.JdbcConnectionDescriptor;
019 import org.apache.ojb.broker.platforms.PlatformException;
020 import org.apache.ojb.broker.platforms.PlatformFactory;
021 import org.apache.ojb.broker.util.ClassHelper;
022 import org.apache.ojb.broker.util.logging.Logger;
023 import org.apache.ojb.broker.util.logging.LoggerFactory;
024
025 import javax.naming.InitialContext;
026 import javax.naming.NamingException;
027 import javax.sql.DataSource;
028 import java.sql.Connection;
029 import java.sql.DriverManager;
030 import java.sql.SQLException;
031 import java.util.HashMap;
032 import java.util.Map;
033 import java.util.Properties;
034
035 /**
036 * Abstract base class to simplify implementation of {@link ConnectionFactory}'s.
037 *
038 * @author <a href="mailto:armin@codeAuLait.de">Armin Waibel</a>
039 * @version $Id: ConnectionFactoryAbstractImpl.java,v 1.1 2007-08-24 22:17:30 ewestfal Exp $
040 */
041 public abstract class ConnectionFactoryAbstractImpl implements ConnectionFactory
042 {
043 private Logger log = LoggerFactory.getLogger(ConnectionFactoryAbstractImpl.class);
044
045 /**
046 * holds the datasource looked up from JNDI in a map, keyed
047 * by the JNDI name.
048 */
049 private Map dataSourceCache = new HashMap();
050
051 /**
052 * Returns a valid JDBC Connection. Implement this method in concrete subclasses.
053 * Concrete implementations using Connection pooling are responsible for any validation
054 * and pool removal management.
055 * <p>
056 * Note: This method is never called for a jdbc-connection-descriptor that uses datasources,
057 * OJB only manages connections from DriverManager.
058 * <p>
059 * Note: If the concrete implementation does not callback to
060 * {@link #newConnectionFromDriverManager(org.apache.ojb.broker.metadata.JdbcConnectionDescriptor)}
061 * when creating a new Connection, it <em>must</em> call
062 * {@link #initializeJdbcConnection(java.sql.Connection, org.apache.ojb.broker.metadata.JdbcConnectionDescriptor)}
063 * so that the platform implementation can peform any RDBMS-specific init tasks for newly
064 * created Connection objetcs.
065 *
066 * @param jcd the connection descriptor for which to return a validated Connection
067 * @return a valid Connection, never null.
068 * Specific implementations <em>must</em> guarantee that the connection is not null and
069 * that it is valid.
070 * @throws LookupException if a valid Connection could not be obtained
071 */
072 public abstract Connection checkOutJdbcConnection(JdbcConnectionDescriptor jcd)
073 throws LookupException;
074
075 /**
076 * Releases a Connection after use. Implement this method in concrete subclasses.
077 * Concrete implementations using Connection pooling are responsible for any validation
078 * and pool removal management.
079 * <p>
080 * Note: This method is never called for a jdbc-connection-descriptor that uses datasources,
081 * OJB only manages connections from DriverManager.
082 *
083 * @param jcd the connection descriptor for which the connection was created
084 * @param con the connection to release.
085 * Callers <em>must</em> guarantee that the passed connection was obtained by calling
086 * {@link #checkOutJdbcConnection(org.apache.ojb.broker.metadata.JdbcConnectionDescriptor)}.
087 * @throws LookupException if errors occured during release of object. Typically happens
088 * if return of object to pool fails in a pooled implementation.
089 */
090 public abstract void releaseJdbcConnection(JdbcConnectionDescriptor jcd, Connection con)
091 throws LookupException;
092
093 public void releaseConnection(JdbcConnectionDescriptor jcd, Connection con)
094 {
095 if (con == null) return;
096 if (jcd.isDataSource())
097 {
098 try
099 {
100 con.close();
101 }
102 catch (SQLException e)
103 {
104 log.error("Closing connection failed", e);
105 }
106 }
107 else
108 {
109 try
110 {
111 releaseJdbcConnection(jcd, con);
112 }
113 catch (LookupException e)
114 {
115 log.error("Unexpected exception when return connection " + con +
116 " to pool using " + jcd, e);
117 }
118 }
119 }
120
121 public Connection lookupConnection(JdbcConnectionDescriptor jcd) throws LookupException
122 {
123 Connection conn;
124 /*
125 use JNDI datasourcelookup or ordinary jdbc DriverManager
126 to obtain connection ?
127 */
128 if (jcd.isDataSource())
129 {
130 if (log.isDebugEnabled())
131 {
132 log.debug("do datasource lookup, name: " + jcd.getDatasourceName() +
133 ", user: " + jcd.getUserName());
134 }
135 conn = newConnectionFromDataSource(jcd);
136 }
137 else
138 {
139 conn = checkOutJdbcConnection(jcd);
140 // connection is now guaranteed to be valid by API contract (else exception is thrown)
141 }
142 return conn;
143 }
144
145 /**
146 * Initialize the connection with the specified properties in OJB
147 * configuration files and platform depended properties.
148 * Invoke this method after a NEW connection is created, not if re-using from pool.
149 *
150 * @see org.apache.ojb.broker.platforms.PlatformFactory
151 * @see org.apache.ojb.broker.platforms.Platform
152 */
153 protected void initializeJdbcConnection(Connection con, JdbcConnectionDescriptor jcd)
154 throws LookupException
155 {
156 try
157 {
158 PlatformFactory.getPlatformFor(jcd).initializeJdbcConnection(jcd, con);
159 }
160 catch (PlatformException e)
161 {
162 throw new LookupException("Platform dependent initialization of connection failed", e);
163 }
164 }
165
166 /**
167 * Override this method to do cleanup in your implementation.
168 * Do a <tt>super.releaseAllResources()</tt> in your method implementation
169 * to free resources used by this class.
170 */
171 public synchronized void releaseAllResources()
172 {
173 this.dataSourceCache.clear();
174 }
175
176 /**
177 * Creates a new connection from the data source that the connection descriptor
178 * represents. If the connection descriptor does not directly contain the data source
179 * then a JNDI lookup is performed to retrieve the data source.
180 *
181 * @param jcd The connection descriptor
182 * @return A connection instance
183 * @throws LookupException if we can't get a connection from the datasource either due to a
184 * naming exception, a failed sanity check, or a SQLException.
185 */
186 protected Connection newConnectionFromDataSource(JdbcConnectionDescriptor jcd)
187 throws LookupException
188 {
189 Connection retval = null;
190 // use JNDI lookup
191 DataSource ds = jcd.getDataSource();
192
193 if (ds == null)
194 {
195 // [tomdz] Would it suffice to store the datasources only at the JCDs ?
196 // Only possible problem would be serialization of the JCD because
197 // the data source object in the JCD does not 'survive' this
198 ds = (DataSource) dataSourceCache.get(jcd.getDatasourceName());
199 }
200 try
201 {
202 if (ds == null)
203 {
204 /**
205 * this synchronization block won't be a big deal as we only look up
206 * new datasources not found in the map.
207 */
208 synchronized (dataSourceCache)
209 {
210 InitialContext ic = new InitialContext();
211 ds = (DataSource) ic.lookup(jcd.getDatasourceName());
212 /**
213 * cache the datasource lookup.
214 */
215 dataSourceCache.put(jcd.getDatasourceName(), ds);
216 }
217 }
218 if (jcd.getUserName() == null)
219 {
220 retval = ds.getConnection();
221 }
222 else
223 {
224 retval = ds.getConnection(jcd.getUserName(), jcd.getPassWord());
225 }
226 }
227 catch (SQLException sqlEx)
228 {
229 log.error("SQLException thrown while trying to get Connection from Datasource (" +
230 jcd.getDatasourceName() + ")", sqlEx);
231 throw new LookupException("SQLException thrown while trying to get Connection from Datasource (" +
232 jcd.getDatasourceName() + ")", sqlEx);
233 }
234 catch (NamingException namingEx)
235 {
236 log.error("Naming Exception while looking up DataSource (" + jcd.getDatasourceName() + ")", namingEx);
237 throw new LookupException("Naming Exception while looking up DataSource (" + jcd.getDatasourceName() +
238 ")", namingEx);
239 }
240 // initialize connection
241 initializeJdbcConnection(retval, jcd);
242 if(log.isDebugEnabled()) log.debug("Create new connection using DataSource: "+retval);
243 return retval;
244 }
245
246 /**
247 * Returns a new created connection
248 *
249 * @param jcd the connection descriptor
250 * @return an instance of Connection from the drivermanager
251 */
252 protected Connection newConnectionFromDriverManager(JdbcConnectionDescriptor jcd)
253 throws LookupException
254 {
255 Connection retval = null;
256 // use JDBC DriverManager
257 final String driver = jcd.getDriver();
258 final String url = getDbURL(jcd);
259 try
260 {
261 // loads the driver - NB call to newInstance() added to force initialisation
262 ClassHelper.getClass(driver, true);
263 final String user = jcd.getUserName();
264 final String password = jcd.getPassWord();
265 final Properties properties = getJdbcProperties(jcd, user, password);
266 if (properties.isEmpty())
267 {
268 if (user == null)
269 {
270 retval = DriverManager.getConnection(url);
271 }
272 else
273 {
274 retval = DriverManager.getConnection(url, user, password);
275 }
276 }
277 else
278 {
279 retval = DriverManager.getConnection(url, properties);
280 }
281 }
282 catch (SQLException sqlEx)
283 {
284 log.error("Error getting Connection from DriverManager with url (" + url + ") and driver (" + driver + ")", sqlEx);
285 throw new LookupException("Error getting Connection from DriverManager with url (" + url + ") and driver (" + driver + ")", sqlEx);
286 }
287 catch (ClassNotFoundException cnfEx)
288 {
289 log.error(cnfEx);
290 throw new LookupException("A class was not found", cnfEx);
291 }
292 catch (Exception e)
293 {
294 log.error("Instantiation of jdbc driver failed", e);
295 throw new LookupException("Instantiation of jdbc driver failed", e);
296 }
297 // initialize connection
298 initializeJdbcConnection(retval, jcd);
299 if(log.isDebugEnabled()) log.debug("Create new connection using DriverManager: "+retval);
300 return retval;
301 }
302
303 /**
304 * Returns connection properties for passing to DriverManager, after merging
305 * JDBC driver-specific configuration settings with name/password from connection
306 * descriptor.
307 * @param jcd the connection descriptor with driver-specific settings
308 * @param user the jcd username (or null if not using authenticated login)
309 * @param password the jcd password (only used when user != null)
310 * @return merged properties object to pass to DriverManager
311 */
312 protected Properties getJdbcProperties(JdbcConnectionDescriptor jcd,
313 String user, String password)
314 {
315 final Properties jdbcProperties;
316 jdbcProperties = jcd.getConnectionPoolDescriptor().getJdbcProperties();
317 if (user != null)
318 {
319 jdbcProperties.put("user", user);
320 jdbcProperties.put("password", password);
321 }
322 return jdbcProperties;
323 }
324
325 protected Properties getJdbcProperties(JdbcConnectionDescriptor jcd)
326 {
327 final String user = jcd.getUserName();
328 final String password = jcd.getPassWord();
329 return getJdbcProperties(jcd, user, password);
330 }
331
332 protected String getDbURL(JdbcConnectionDescriptor jcd)
333 {
334 return jcd.isDataSource() ? jcd.getDatasourceName() :
335 jcd.getProtocol() + ":" + jcd.getSubProtocol() + ":" + jcd.getDbAlias();
336 }
337
338 protected String getJcdDescription(JdbcConnectionDescriptor jcd)
339 {
340 return "Connection for JdbcConnectionDescriptor (" +
341 (jcd.getDatasourceName() != null ? "datasource: " + jcd.getDatasourceName() :
342 "db-url: " + getDbURL(jcd) + ", user: " + jcd.getUserName()) +
343 ")";
344 }
345
346 }