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    }