View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    * 
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   * 
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.jetspeed.components.rdbms.ojb;
18  
19  import java.io.PrintWriter;
20  import java.sql.Connection;
21  import java.sql.DatabaseMetaData;
22  import java.sql.DriverManager;
23  import java.sql.SQLException;
24  import java.util.Map;
25  
26  import javax.naming.Context;
27  import javax.naming.InitialContext;
28  import javax.sql.DataSource;
29  
30  import org.apache.commons.dbcp.BasicDataSource;
31  import org.apache.commons.logging.Log;
32  import org.apache.commons.logging.LogFactory;
33  import org.apache.ojb.broker.PBKey;
34  import org.apache.ojb.broker.accesslayer.ConnectionFactoryDBCPImpl;
35  import org.apache.ojb.broker.accesslayer.ConnectionFactoryManagedImpl;
36  import org.apache.ojb.broker.accesslayer.LookupException;
37  import org.apache.ojb.broker.metadata.ConnectionPoolDescriptor;
38  import org.apache.ojb.broker.metadata.ConnectionRepository;
39  import org.apache.ojb.broker.metadata.JdbcConnectionDescriptor;
40  import org.apache.ojb.broker.metadata.JdbcMetadataUtils;
41  import org.apache.ojb.broker.metadata.MetadataManager;
42  import org.apache.ojb.broker.util.ClassHelper;
43  import org.springframework.beans.factory.BeanNameAware;
44  import org.springframework.beans.factory.InitializingBean;
45  
46  /***
47   * A JavaBean that configures an entry in OJB's ConnectionRepository
48   * according to its properties. If a JCD alias is not specified, it defaults
49   * to the bean's name in the Spring configuration. If the JDBC connection
50   * descriptor already exists (e.g. because it has been defined in OJB's
51   * configuration) the properties are merged into the existing descriptor
52   * (see note about "platform" below), else the JDBC connection descriptor
53   * is created.<P>
54   * 
55   * If a JNDI name is set, the bean automatically configures a JDBC connection 
56   * descriptor with a connection factory of type 
57   * <code>ConnectionFactoryManagedImpl</code>, else it uses 
58   * <code>ConectionFactoryDBCPImpl</code>. This may be overridden my setting 
59   * the connection factory property explicitly.<P>
60   * 
61   * Properties "driverClassName", "url", "username" and "password" are used
62   * only if no JNDI name is set, i.e. if the connection factory uses the
63   * driver to create data sources.<P>
64   * 
65   * The bean derives the RDBMS platform setting from the configured 
66   * data source or database driver using OJB's <code>JdbcMetadataUtils</code>
67   * class. At least until OJB 1.0.3, however, this class does not properly 
68   * distinguish the platforms "Oracle" and "Oracle9i"; it always assigns 
69   * "Oracle". In case of "Oracle", this bean therefore opens a connection,
70   * obtains the version information from the database server and adjusts the
71   * platform accordingly. This behaviour may be overridden by setting the 
72   * <code>platform</code> property of the bean explicitly. Note that the
73   * attribute "platform" of an already existing JCD is ignored. An already
74   * existing JCD stems most likely from a configuration file "repository.xml".
75   * As the DTD for "repository.xml" ("repository.dtd") defines a default
76   * value for attribute "platform" ("Hsqldb"), it is in general impossible 
77   * to find out whether the platform attribute of an existing JCD has been set 
78   * explicitly or has simply assumed its default value.      
79   *
80   * @author Michael Lipp
81   * @version $Id$
82   */
83  public class ConnectionRepositoryEntry
84      extends BasicDataSource
85      implements BeanNameAware, InitializingBean
86  {
87      private static final Log log = LogFactory.getLog(ConnectionRepositoryEntry.class);
88      
89      // general properties
90      private String jcdAlias = null;
91      private String platform = null;
92      private String connectionFactoryClass = null;
93      // properties for obtaining data source from JNDI
94      private String jndiName = null;
95      // properties for creating independant data source 
96      private String driverClassName = null;
97      private String url = null;
98      private String username = null;
99      private String password = null;
100     private boolean jetspeedEngineScoped = true;
101 
102     public ConnectionRepositoryEntry()
103     {
104         super();
105     }
106     
107     /***
108      * @see org.springframework.beans.factory.BeanNameAware#setBeanName(java.lang.String)
109      */
110     public void setBeanName(String beanName) 
111     {
112         // Use the bean's name as fallback if a JCD alias is not set
113         // explicitly
114         if (jcdAlias == null) 
115         {
116             jcdAlias = beanName;
117         }
118     }
119     
120     /***
121      * @return Returns the jcdAlias.
122      */
123     public String getJcdAlias() 
124     {
125         return jcdAlias;
126     }
127     
128     /***
129      * @param jcdAlias The jcdAlias to set.
130      */
131     public void setJcdAlias(String jcdAlias)
132     {
133         this.jcdAlias = jcdAlias;
134     }
135 
136     /***
137      * @return Returns the jndiName.
138      */
139     public String getJndiName() 
140     {
141         return jndiName;
142     }
143 
144     /***
145      * @param jndiName The jndiName to set.
146      */
147     public void setJndiName(String jndiName) 
148     {
149         this.jndiName = jndiName;
150     }
151 
152     /***
153      * @return Returns the driverClassName.
154      */
155     public String getDriverClassName() 
156     {
157         return driverClassName;        
158     }
159 
160     /***
161      * @param driverClassName The driverClassName to set.
162      */
163     public void setDriverClassName(String driverClassName) 
164     {
165         super.setDriverClassName(driverClassName);
166         this.driverClassName = driverClassName;
167     }
168 
169     /***
170      * @return Returns the password.
171      */
172     public String getPassword() 
173     {
174         return password;
175     }
176 
177     /***
178      * @param password The password to set.
179      */
180     public void setPassword(String password) 
181     {
182         super.setPassword(password);
183         this.password = password;
184     }
185 
186     /***
187      * @return Returns the url.
188      */
189     public String getUrl() 
190     {
191         return url;
192     }
193 
194     /***
195      * @param url The url to set.
196      */
197     public void setUrl(String url) 
198     {
199         super.setUrl(url);
200         this.url = url;
201     }
202 
203     /***
204      * @return Returns the username.
205      */
206     public String getUsername() 
207     {
208         return username;
209     }
210 
211     /***
212      * @param username The username to set.
213      */
214     public void setUsername(String username) 
215     {
216         super.setUsername(username);
217         this.username = username;
218     }
219     
220     /***
221      * @return Returns the platform.
222      */
223     public String getPlatform() 
224     {
225         return platform;
226     }
227 
228     /***
229      * Set the platform attribute of the JCD. Setting this property overrides
230      * the value derived from the data source or database driver. 
231      * @param platform The platform to set.
232      */
233     public void setPlatform(String platform) 
234     {        
235         this.platform = platform;
236     }
237 
238     /***
239      * @see setJetspeedEngineScoped
240      * @return Returns if Jetspeed engine's ENC is used for JNDI lookups.
241      */
242     public boolean isJetspeedEngineScoped() 
243     {
244         return jetspeedEngineScoped;
245     }
246 
247     /***
248      * Sets the attribute "<code>org.apache.jetspeed.engineScoped</code>"
249      * of the JDBC connection descriptor to "<code>true</code>" or
250      * "<code>false</code>". If set, JNDI lookups of the connection will
251      * be done using the environment naming context (ENC) of the Jetspeed 
252      * engine.
253      * @param jetspeedEngineScoped whether to use Jetspeed engine's ENC.
254      */
255     public void setJetspeedEngineScoped(boolean jetspeedEngineScoped) 
256     {
257         this.jetspeedEngineScoped = jetspeedEngineScoped;
258     }
259 
260     public void afterPropertiesSet () throws Exception 
261     {
262         // Try to find JCD
263         ConnectionRepository cr = MetadataManager.getInstance().connectionRepository();
264         JdbcConnectionDescriptor jcd = cr.getDescriptor(new PBKey(jcdAlias));
265         if (jcd == null)
266         {
267             jcd = new JdbcConnectionDescriptor();
268             jcd.setJcdAlias(jcdAlias);
269             cr.addDescriptor(jcd);
270         }
271         if (platform != null && platform.length() == 0)
272         {
273             platform = null;
274         }
275         DataSource ds = null;
276         JdbcMetadataUtils jdbcMetadataUtils = new JdbcMetadataUtils ();
277         if (jndiName != null)
278         {
279             // using "preconfigured" data source
280             if (connectionFactoryClass == null) 
281             {
282                 connectionFactoryClass = ConnectionFactoryManagedImpl.class.getName ();
283             }
284             Context initialContext = new InitialContext();
285             ds = (DataSource) initialContext.lookup(jndiName);
286             jcd.setDatasourceName(jndiName);
287         } 
288         else 
289         {
290             // have to get data source ourselves
291             if (connectionFactoryClass == null) 
292             {
293                 connectionFactoryClass = ConnectionFactoryDBCPImpl.class.getName ();
294             }
295             jcd.setDriver(driverClassName);
296             Map conData = jdbcMetadataUtils.parseConnectionUrl(url);
297             jcd.setDbms(platform);
298             jcd.setProtocol((String)conData.get(JdbcMetadataUtils.PROPERTY_PROTOCOL));
299             jcd.setSubProtocol((String)conData.get(JdbcMetadataUtils.PROPERTY_SUBPROTOCOL));
300             jcd.setDbAlias((String)conData.get(JdbcMetadataUtils.PROPERTY_DBALIAS));
301             jcd.setUserName(username);
302             jcd.setPassWord(password);
303             // Wrapping the connection factory in a DataSource introduces a bit 
304             // of redundancy (url is parsed again and platform determined again).
305             // But although JdbcMetadataUtils exposes the methods used in 
306             // fillJCDFromDataSource as public (and these do not require a DataSource)
307             // the method itself does more than is made available by the exposed methods.
308             // ds = new MinimalDataSource (jcd);
309             ds = this;             
310         }
311         ConnectionPoolDescriptor cpd = jcd.getConnectionPoolDescriptor();
312         if (cpd == null)
313         {
314             cpd = new ConnectionPoolDescriptor();
315             jcd.setConnectionPoolDescriptor(cpd);
316         }
317         Class conFacCls = ClassHelper.getClass(connectionFactoryClass);
318         cpd.setConnectionFactory(conFacCls);
319 
320         jdbcMetadataUtils.fillJCDFromDataSource(jcd, ds, null, null);
321         
322         if (platform == null && JdbcMetadataUtils.PLATFORM_ORACLE.equals(jcd.getDbms())) {
323             // Postprocess to find Oracle version.
324             updateOraclePlatform (jcd, ds);
325         }
326         // if platform has explicitly been set, the value takes precedence
327         if (platform != null) {
328             if (!platform.equals(jcd.getDbms())) {
329                 log.warn ("Automatically derived RDBMS platform \"" + jcd.getDbms()
330                           + "\" differs from explicitly set platform \"" + platform + "\""); 
331             }
332             jcd.setDbms(platform);
333         } else {
334             platform = jcd.getDbms();
335         }
336         
337         // special attributes
338         jcd.addAttribute("org.apache.jetspeed.engineScoped", 
339                          Boolean.toString(jetspeedEngineScoped));
340     }
341 
342     /***
343      * @param jcd
344      * @throws LookupException
345      * @throws IllegalAccessException
346      * @throws InstantiationException
347      * throws SQLException
348      */
349     private void updateOraclePlatform(JdbcConnectionDescriptor jcd, DataSource ds)
350     	throws LookupException, IllegalAccessException, InstantiationException, SQLException 
351     {
352         Connection con = null;
353         try 
354         {
355             con = ds.getConnection();
356             DatabaseMetaData metaData = con.getMetaData();
357             int rdbmsVersion = 0;
358             try 
359             {
360                 // getDatabaseMajorVersion exists since 1.4, so it may
361                 // not be defined for the driver used.
362                 rdbmsVersion = metaData.getDatabaseMajorVersion();
363             } catch (Throwable t) {
364                 String dbVersion = metaData.getDatabaseProductVersion();
365                 String relKey = "Release";
366                 String major = dbVersion;
367                 int startPos = dbVersion.indexOf(relKey);
368                 if (startPos < 0)
369                 {
370                     log.warn ("Cannot determine Oracle version, no \"Release\" in procuct version: \"" + dbVersion + "\"");
371                     return;
372                 }
373                 startPos += relKey.length();
374                 int dotPos = dbVersion.indexOf('.', startPos);
375                 if (dotPos > 0) {
376                     major = dbVersion.substring(startPos, dotPos).trim();
377                 }
378                 try
379                 {
380                     rdbmsVersion = Integer.parseInt(major);
381                 }
382                 catch (NumberFormatException e)
383                 {
384                     log.warn ("Cannot determine Oracle version, product version \"" + dbVersion + "\" not layed out as \"... Release N.M.....\"");
385                     return;
386                 }
387                 if (log.isDebugEnabled())
388                 {
389                     log.debug ("Extracted Oracle major version " + rdbmsVersion + " from product version \"" + dbVersion + "\"");
390                 }
391             }
392             if (rdbmsVersion >= 9) {
393                 jcd.setDbms(JdbcMetadataUtils.PLATFORM_ORACLE9I);
394             }
395         }
396         finally
397         {
398             if (con != null) {
399                 con.close ();
400             }
401         }
402     }
403 
404     /***
405      * a minimal DataSource implementation that satisfies the requirements
406      * of JdbcMetadataUtil.
407      */
408     private class MinimalDataSource implements DataSource 
409     {
410         private JdbcConnectionDescriptor jcd = null;
411         
412         /***
413          * Create a new instance using the given JCD.
414          */
415         public MinimalDataSource (JdbcConnectionDescriptor jcd)
416         {
417             this.jcd = jcd;
418         }
419         
420         /* (non-Javadoc)
421          * @see javax.sql.DataSource#getConnection()
422          */
423         public Connection getConnection() throws SQLException {
424             // Use JDBC DriverManager as we may not rely on JCD to be sufficiently
425             // initialized to use any of the ConnectionFactories.
426             try {
427                 // loads the driver - NB call to newInstance() added to force initialisation
428                 ClassHelper.getClass(jcd.getDriver(), true);
429                 String url = jcd.getProtocol() + ":" + jcd.getSubProtocol() + ":" + jcd.getDbAlias();
430                 if (jcd.getUserName() == null)
431                 {
432                     return DriverManager.getConnection(url);
433                 }
434                 else
435                 {
436                     return DriverManager.getConnection(url, jcd.getUserName(), jcd.getPassWord());
437                 }
438             }
439             catch (ClassNotFoundException e)
440             {
441                 throw (IllegalStateException)
442                     (new IllegalStateException (e.getMessage ())).initCause (e);
443             }
444         }
445         
446         /***
447          * @see javax.sql.DataSource#getConnection(java.lang.String, java.lang.String)
448          */
449         public Connection getConnection(String username, String password)
450                 throws SQLException {
451             return getConnection ();
452         }
453 
454         /***
455          * @see javax.sql.DataSource#getLoginTimeout()
456          */
457         public int getLoginTimeout() throws SQLException 
458         {
459             return 0;
460         }
461 
462         /***
463          * @see javax.sql.DataSource#getLogWriter()
464          */
465         public PrintWriter getLogWriter() throws SQLException {
466             return null;
467         }
468 
469         /***
470          * @see javax.sql.DataSource#setLoginTimeout(int)
471          */
472         public void setLoginTimeout(int seconds) throws SQLException {
473         }
474 
475         /***
476          * @see javax.sql.DataSource#setLogWriter(java.io.PrintWriter)
477          */
478         public void setLogWriter(PrintWriter out) throws SQLException {
479         }
480     }
481 
482 }