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 	private DataSource externalDs;
103 
104     public ConnectionRepositoryEntry()
105     {
106         super();
107     }
108     
109     /***
110      * @see org.springframework.beans.factory.BeanNameAware#setBeanName(java.lang.String)
111      */
112     public void setBeanName(String beanName) 
113     {
114         // Use the bean's name as fallback if a JCD alias is not set
115         // explicitly
116         if (jcdAlias == null) 
117         {
118             jcdAlias = beanName;
119         }
120     }
121     
122     /***
123      * @return Returns the jcdAlias.
124      */
125     public String getJcdAlias() 
126     {
127         return jcdAlias;
128     }
129     
130     /***
131      * @param jcdAlias The jcdAlias to set.
132      */
133     public void setJcdAlias(String jcdAlias)
134     {
135         this.jcdAlias = jcdAlias;
136     }
137 
138     /***
139      * @return Returns the jndiName.
140      */
141     public String getJndiName() 
142     {
143         return jndiName;
144     }
145 
146     /***
147      * @param jndiName The jndiName to set.
148      */
149     public void setJndiName(String jndiName) 
150     {
151         this.jndiName = jndiName;
152     }
153 
154     /***
155      * @return Returns the driverClassName.
156      */
157     public String getDriverClassName() 
158     {
159         return driverClassName;        
160     }
161 
162     /***
163      * @param driverClassName The driverClassName to set.
164      */
165     public void setDriverClassName(String driverClassName) 
166     {
167         super.setDriverClassName(driverClassName);
168         this.driverClassName = driverClassName;
169     }
170 
171     /***
172      * @return Returns the password.
173      */
174     public String getPassword() 
175     {
176         return password;
177     }
178 
179     /***
180      * @param password The password to set.
181      */
182     public void setPassword(String password) 
183     {
184         super.setPassword(password);
185         this.password = password;
186     }
187 
188     /***
189      * @return Returns the url.
190      */
191     public String getUrl() 
192     {
193         return url;
194     }
195 
196     /***
197      * @param url The url to set.
198      */
199     public void setUrl(String url) 
200     {
201         super.setUrl(url);
202         this.url = url;
203     }
204 
205     /***
206      * @return Returns the username.
207      */
208     public String getUsername() 
209     {
210         return username;
211     }
212 
213     /***
214      * @param username The username to set.
215      */
216     public void setUsername(String username) 
217     {
218         super.setUsername(username);
219         this.username = username;
220     }
221     
222     /***
223      * @return Returns the platform.
224      */
225     public String getPlatform() 
226     {
227         return platform;
228     }
229 
230     /***
231      * Set the platform attribute of the JCD. Setting this property overrides
232      * the value derived from the data source or database driver. 
233      * @param platform The platform to set.
234      */
235     public void setPlatform(String platform) 
236     {        
237         this.platform = platform;
238     }
239 
240     /***
241      * @see setJetspeedEngineScoped
242      * @return Returns if Jetspeed engine's ENC is used for JNDI lookups.
243      */
244     public boolean isJetspeedEngineScoped() 
245     {
246         return jetspeedEngineScoped;
247     }
248 
249     /***
250      * Sets the attribute "<code>org.apache.jetspeed.engineScoped</code>"
251      * of the JDBC connection descriptor to "<code>true</code>" or
252      * "<code>false</code>". If set, JNDI lookups of the connection will
253      * be done using the environment naming context (ENC) of the Jetspeed 
254      * engine.
255      * @param jetspeedEngineScoped whether to use Jetspeed engine's ENC.
256      */
257     public void setJetspeedEngineScoped(boolean jetspeedEngineScoped) 
258     {
259         this.jetspeedEngineScoped = jetspeedEngineScoped;
260     }
261 
262     public void afterPropertiesSet () throws Exception 
263     {
264         // Try to find JCD
265         ConnectionRepository cr = MetadataManager.getInstance().connectionRepository();
266         JdbcConnectionDescriptor jcd = cr.getDescriptor(new PBKey(jcdAlias));
267         if (jcd == null)
268         {
269             jcd = new JdbcConnectionDescriptor();
270             jcd.setJcdAlias(jcdAlias);
271             cr.addDescriptor(jcd);
272         }
273         if (platform != null && platform.length() == 0)
274         {
275             platform = null;
276         }
277         DataSource ds = null;
278         JdbcMetadataUtils jdbcMetadataUtils = new JdbcMetadataUtils ();
279         if (jndiName != null)
280         {
281             // using "preconfigured" data source
282             if (connectionFactoryClass == null) 
283             {
284                 connectionFactoryClass = ConnectionFactoryManagedImpl.class.getName ();
285             }
286             Context initialContext = new InitialContext();
287             ds = (DataSource) initialContext.lookup(jndiName);
288             externalDs = ds;
289 			jcd.setDatasourceName(jndiName);
290         } 
291         else 
292         {
293             // have to get data source ourselves
294             if (connectionFactoryClass == null) 
295             {
296                 connectionFactoryClass = ConnectionFactoryDBCPImpl.class.getName ();
297             }
298             jcd.setDriver(driverClassName);
299             Map conData = jdbcMetadataUtils.parseConnectionUrl(url);
300             jcd.setDbms(platform);
301             jcd.setProtocol((String)conData.get(JdbcMetadataUtils.PROPERTY_PROTOCOL));
302             jcd.setSubProtocol((String)conData.get(JdbcMetadataUtils.PROPERTY_SUBPROTOCOL));
303             jcd.setDbAlias((String)conData.get(JdbcMetadataUtils.PROPERTY_DBALIAS));
304             jcd.setUserName(username);
305             jcd.setPassWord(password);
306             // Wrapping the connection factory in a DataSource introduces a bit 
307             // of redundancy (url is parsed again and platform determined again).
308             // But although JdbcMetadataUtils exposes the methods used in 
309             // fillJCDFromDataSource as public (and these do not require a DataSource)
310             // the method itself does more than is made available by the exposed methods.
311             // ds = new MinimalDataSource (jcd);
312             ds = this;             
313         }
314         ConnectionPoolDescriptor cpd = jcd.getConnectionPoolDescriptor();
315         if (cpd == null)
316         {
317             cpd = new ConnectionPoolDescriptor();
318             jcd.setConnectionPoolDescriptor(cpd);
319         }
320         Class conFacCls = ClassHelper.getClass(connectionFactoryClass);
321         cpd.setConnectionFactory(conFacCls);
322 
323         jdbcMetadataUtils.fillJCDFromDataSource(jcd, ds, null, null);
324         
325         if (platform == null && JdbcMetadataUtils.PLATFORM_ORACLE.equals(jcd.getDbms())) {
326             // Postprocess to find Oracle version.
327             updateOraclePlatform (jcd, ds);
328         }
329         // if platform has explicitly been set, the value takes precedence
330         if (platform != null) {
331             if (!platform.equals(jcd.getDbms())) {
332                 log.warn ("Automatically derived RDBMS platform \"" + jcd.getDbms()
333                           + "\" differs from explicitly set platform \"" + platform + "\""); 
334             }
335             jcd.setDbms(platform);
336         } else {
337             platform = jcd.getDbms();
338         }
339         
340         // special attributes
341         jcd.addAttribute("org.apache.jetspeed.engineScoped", 
342                          Boolean.toString(jetspeedEngineScoped));
343     }
344 
345     /***
346      * @param jcd
347      * @throws LookupException
348      * @throws IllegalAccessException
349      * @throws InstantiationException
350      * throws SQLException
351      */
352     private void updateOraclePlatform(JdbcConnectionDescriptor jcd, DataSource ds)
353     	throws LookupException, IllegalAccessException, InstantiationException, SQLException 
354     {
355         Connection con = null;
356         try 
357         {
358             con = ds.getConnection();
359             DatabaseMetaData metaData = con.getMetaData();
360             int rdbmsVersion = 0;
361             try 
362             {
363                 // getDatabaseMajorVersion exists since 1.4, so it may
364                 // not be defined for the driver used.
365                 rdbmsVersion = metaData.getDatabaseMajorVersion();
366             } catch (Throwable t) {
367                 String dbVersion = metaData.getDatabaseProductVersion();
368                 String relKey = "Release";
369                 String major = dbVersion;
370                 int startPos = dbVersion.indexOf(relKey);
371                 if (startPos < 0)
372                 {
373                     log.warn ("Cannot determine Oracle version, no \"Release\" in procuct version: \"" + dbVersion + "\"");
374                     return;
375                 }
376                 startPos += relKey.length();
377                 int dotPos = dbVersion.indexOf('.', startPos);
378                 if (dotPos > 0) {
379                     major = dbVersion.substring(startPos, dotPos).trim();
380                 }
381                 try
382                 {
383                     rdbmsVersion = Integer.parseInt(major);
384                 }
385                 catch (NumberFormatException e)
386                 {
387                     log.warn ("Cannot determine Oracle version, product version \"" + dbVersion + "\" not layed out as \"... Release N.M.....\"");
388                     return;
389                 }
390                 if (log.isDebugEnabled())
391                 {
392                     log.debug ("Extracted Oracle major version " + rdbmsVersion + " from product version \"" + dbVersion + "\"");
393                 }
394             }
395             if (rdbmsVersion >= 9) {
396                 jcd.setDbms(JdbcMetadataUtils.PLATFORM_ORACLE9I);
397             }
398         }
399         finally
400         {
401             if (con != null) {
402                 con.close ();
403             }
404         }
405     }
406 
407     /***
408      * a minimal DataSource implementation that satisfies the requirements
409      * of JdbcMetadataUtil.
410      */
411     private class MinimalDataSource implements DataSource 
412     {
413         private JdbcConnectionDescriptor jcd = null;
414         
415         /***
416          * Create a new instance using the given JCD.
417          */
418         public MinimalDataSource (JdbcConnectionDescriptor jcd)
419         {
420             this.jcd = jcd;
421         }
422         
423         /* (non-Javadoc)
424          * @see javax.sql.DataSource#getConnection()
425          */
426         public Connection getConnection() throws SQLException {
427             // Use JDBC DriverManager as we may not rely on JCD to be sufficiently
428             // initialized to use any of the ConnectionFactories.
429             try {
430                 // loads the driver - NB call to newInstance() added to force initialisation
431                 ClassHelper.getClass(jcd.getDriver(), true);
432                 String url = jcd.getProtocol() + ":" + jcd.getSubProtocol() + ":" + jcd.getDbAlias();
433                 if (jcd.getUserName() == null)
434                 {
435                     return DriverManager.getConnection(url);
436                 }
437                 else
438                 {
439                     return DriverManager.getConnection(url, jcd.getUserName(), jcd.getPassWord());
440                 }
441             }
442             catch (ClassNotFoundException e)
443             {
444                 throw (IllegalStateException)
445                     (new IllegalStateException (e.getMessage ())).initCause (e);
446             }
447         }
448         
449         /***
450          * @see javax.sql.DataSource#getConnection(java.lang.String, java.lang.String)
451          */
452         public Connection getConnection(String username, String password)
453                 throws SQLException {
454             return getConnection ();
455         }
456 
457         /***
458          * @see javax.sql.DataSource#getLoginTimeout()
459          */
460         public int getLoginTimeout() throws SQLException 
461         {
462             return 0;
463         }
464 
465         /***
466          * @see javax.sql.DataSource#getLogWriter()
467          */
468         public PrintWriter getLogWriter() throws SQLException {
469             return null;
470         }
471 
472         /***
473          * @see javax.sql.DataSource#setLoginTimeout(int)
474          */
475         public void setLoginTimeout(int seconds) throws SQLException {
476         }
477 
478         /***
479          * @see javax.sql.DataSource#setLogWriter(java.io.PrintWriter)
480          */
481         public void setLogWriter(PrintWriter out) throws SQLException {
482         }
483     }
484 	
485 	public Connection getConnection() throws SQLException {
486 		if(externalDs != null)
487 		{
488 			return externalDs.getConnection();
489 		}
490 		else
491 		{
492 		   return super.getConnection();
493 		}
494 	}
495 	
496 	public Connection getConnection(String username, String password)
497 			throws SQLException {
498 		
499 		if(externalDs != null)
500 		{
501 			return externalDs.getConnection(username, password);
502 		}
503 		else
504 		{
505 		   return super.getConnection(username, password);
506 		}		
507 	}
508 
509 }