001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. 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: package org.apache.jetspeed.components.rdbms.ojb;
018:
019: import java.io.PrintWriter;
020: import java.sql.Connection;
021: import java.sql.DatabaseMetaData;
022: import java.sql.DriverManager;
023: import java.sql.SQLException;
024: import java.util.Map;
025:
026: import javax.naming.Context;
027: import javax.naming.InitialContext;
028: import javax.sql.DataSource;
029:
030: import org.apache.commons.dbcp.BasicDataSource;
031: import org.apache.commons.logging.Log;
032: import org.apache.commons.logging.LogFactory;
033: import org.apache.ojb.broker.PBKey;
034: import org.apache.ojb.broker.accesslayer.ConnectionFactoryDBCPImpl;
035: import org.apache.ojb.broker.accesslayer.ConnectionFactoryManagedImpl;
036: import org.apache.ojb.broker.accesslayer.LookupException;
037: import org.apache.ojb.broker.metadata.ConnectionPoolDescriptor;
038: import org.apache.ojb.broker.metadata.ConnectionRepository;
039: import org.apache.ojb.broker.metadata.JdbcConnectionDescriptor;
040: import org.apache.ojb.broker.metadata.JdbcMetadataUtils;
041: import org.apache.ojb.broker.metadata.MetadataManager;
042: import org.apache.ojb.broker.util.ClassHelper;
043: import org.springframework.beans.factory.BeanNameAware;
044: import org.springframework.beans.factory.InitializingBean;
045:
046: /**
047: * A JavaBean that configures an entry in OJB's ConnectionRepository
048: * according to its properties. If a JCD alias is not specified, it defaults
049: * to the bean's name in the Spring configuration. If the JDBC connection
050: * descriptor already exists (e.g. because it has been defined in OJB's
051: * configuration) the properties are merged into the existing descriptor
052: * (see note about "platform" below), else the JDBC connection descriptor
053: * is created.<P>
054: *
055: * If a JNDI name is set, the bean automatically configures a JDBC connection
056: * descriptor with a connection factory of type
057: * <code>ConnectionFactoryManagedImpl</code>, else it uses
058: * <code>ConectionFactoryDBCPImpl</code>. This may be overridden my setting
059: * the connection factory property explicitly.<P>
060: *
061: * Properties "driverClassName", "url", "username" and "password" are used
062: * only if no JNDI name is set, i.e. if the connection factory uses the
063: * driver to create data sources.<P>
064: *
065: * The bean derives the RDBMS platform setting from the configured
066: * data source or database driver using OJB's <code>JdbcMetadataUtils</code>
067: * class. At least until OJB 1.0.3, however, this class does not properly
068: * distinguish the platforms "Oracle" and "Oracle9i"; it always assigns
069: * "Oracle". In case of "Oracle", this bean therefore opens a connection,
070: * obtains the version information from the database server and adjusts the
071: * platform accordingly. This behaviour may be overridden by setting the
072: * <code>platform</code> property of the bean explicitly. Note that the
073: * attribute "platform" of an already existing JCD is ignored. An already
074: * existing JCD stems most likely from a configuration file "repository.xml".
075: * As the DTD for "repository.xml" ("repository.dtd") defines a default
076: * value for attribute "platform" ("Hsqldb"), it is in general impossible
077: * to find out whether the platform attribute of an existing JCD has been set
078: * explicitly or has simply assumed its default value.
079: *
080: * @author Michael Lipp
081: * @version $Id$
082: */
083: public class ConnectionRepositoryEntry extends BasicDataSource
084: implements BeanNameAware, InitializingBean {
085: private static final Log log = LogFactory
086: .getLog(ConnectionRepositoryEntry.class);
087:
088: // general properties
089: private String jcdAlias = null;
090: private String platform = null;
091: private String connectionFactoryClass = null;
092: // properties for obtaining data source from JNDI
093: private String jndiName = null;
094: // properties for creating independant data source
095: private String driverClassName = null;
096: private String url = null;
097: private String username = null;
098: private String password = null;
099: private boolean jetspeedEngineScoped = true;
100:
101: private DataSource externalDs;
102:
103: public ConnectionRepositoryEntry() {
104: super ();
105: }
106:
107: /**
108: * @see org.springframework.beans.factory.BeanNameAware#setBeanName(java.lang.String)
109: */
110: public void setBeanName(String beanName) {
111: // Use the bean's name as fallback if a JCD alias is not set
112: // explicitly
113: if (jcdAlias == null) {
114: jcdAlias = beanName;
115: }
116: }
117:
118: /**
119: * @return Returns the jcdAlias.
120: */
121: public String getJcdAlias() {
122: return jcdAlias;
123: }
124:
125: /**
126: * @param jcdAlias The jcdAlias to set.
127: */
128: public void setJcdAlias(String jcdAlias) {
129: this .jcdAlias = jcdAlias;
130: }
131:
132: /**
133: * @return Returns the jndiName.
134: */
135: public String getJndiName() {
136: return jndiName;
137: }
138:
139: /**
140: * @param jndiName The jndiName to set.
141: */
142: public void setJndiName(String jndiName) {
143: this .jndiName = jndiName;
144: }
145:
146: /**
147: * @return Returns the driverClassName.
148: */
149: public String getDriverClassName() {
150: return driverClassName;
151: }
152:
153: /**
154: * @param driverClassName The driverClassName to set.
155: */
156: public void setDriverClassName(String driverClassName) {
157: super .setDriverClassName(driverClassName);
158: this .driverClassName = driverClassName;
159: }
160:
161: /**
162: * @return Returns the password.
163: */
164: public String getPassword() {
165: return password;
166: }
167:
168: /**
169: * @param password The password to set.
170: */
171: public void setPassword(String password) {
172: super .setPassword(password);
173: this .password = password;
174: }
175:
176: /**
177: * @return Returns the url.
178: */
179: public String getUrl() {
180: return url;
181: }
182:
183: /**
184: * @param url The url to set.
185: */
186: public void setUrl(String url) {
187: super .setUrl(url);
188: this .url = url;
189: }
190:
191: /**
192: * @return Returns the username.
193: */
194: public String getUsername() {
195: return username;
196: }
197:
198: /**
199: * @param username The username to set.
200: */
201: public void setUsername(String username) {
202: super .setUsername(username);
203: this .username = username;
204: }
205:
206: /**
207: * @return Returns the platform.
208: */
209: public String getPlatform() {
210: return platform;
211: }
212:
213: /**
214: * Set the platform attribute of the JCD. Setting this property overrides
215: * the value derived from the data source or database driver.
216: * @param platform The platform to set.
217: */
218: public void setPlatform(String platform) {
219: this .platform = platform;
220: }
221:
222: /**
223: * @see setJetspeedEngineScoped
224: * @return Returns if Jetspeed engine's ENC is used for JNDI lookups.
225: */
226: public boolean isJetspeedEngineScoped() {
227: return jetspeedEngineScoped;
228: }
229:
230: /**
231: * Sets the attribute "<code>org.apache.jetspeed.engineScoped</code>"
232: * of the JDBC connection descriptor to "<code>true</code>" or
233: * "<code>false</code>". If set, JNDI lookups of the connection will
234: * be done using the environment naming context (ENC) of the Jetspeed
235: * engine.
236: * @param jetspeedEngineScoped whether to use Jetspeed engine's ENC.
237: */
238: public void setJetspeedEngineScoped(boolean jetspeedEngineScoped) {
239: this .jetspeedEngineScoped = jetspeedEngineScoped;
240: }
241:
242: public void afterPropertiesSet() throws Exception {
243: // Try to find JCD
244: ConnectionRepository cr = MetadataManager.getInstance()
245: .connectionRepository();
246: JdbcConnectionDescriptor jcd = cr.getDescriptor(new PBKey(
247: jcdAlias));
248: if (jcd == null) {
249: jcd = new JdbcConnectionDescriptor();
250: jcd.setJcdAlias(jcdAlias);
251: cr.addDescriptor(jcd);
252: }
253: if (platform != null && platform.length() == 0) {
254: platform = null;
255: }
256: DataSource ds = null;
257: JdbcMetadataUtils jdbcMetadataUtils = new JdbcMetadataUtils();
258: if (jndiName != null) {
259: // using "preconfigured" data source
260: if (connectionFactoryClass == null) {
261: connectionFactoryClass = ConnectionFactoryManagedImpl.class
262: .getName();
263: }
264: Context initialContext = new InitialContext();
265: ds = (DataSource) initialContext.lookup(jndiName);
266: externalDs = ds;
267: jcd.setDatasourceName(jndiName);
268: } else {
269: // have to get data source ourselves
270: if (connectionFactoryClass == null) {
271: connectionFactoryClass = ConnectionFactoryDBCPImpl.class
272: .getName();
273: }
274: jcd.setDriver(driverClassName);
275: Map conData = jdbcMetadataUtils.parseConnectionUrl(url);
276: jcd.setDbms(platform);
277: jcd.setProtocol((String) conData
278: .get(JdbcMetadataUtils.PROPERTY_PROTOCOL));
279: jcd.setSubProtocol((String) conData
280: .get(JdbcMetadataUtils.PROPERTY_SUBPROTOCOL));
281: jcd.setDbAlias((String) conData
282: .get(JdbcMetadataUtils.PROPERTY_DBALIAS));
283: jcd.setUserName(username);
284: jcd.setPassWord(password);
285: // Wrapping the connection factory in a DataSource introduces a bit
286: // of redundancy (url is parsed again and platform determined again).
287: // But although JdbcMetadataUtils exposes the methods used in
288: // fillJCDFromDataSource as public (and these do not require a DataSource)
289: // the method itself does more than is made available by the exposed methods.
290: // ds = new MinimalDataSource (jcd);
291: ds = this ;
292: }
293: ConnectionPoolDescriptor cpd = jcd
294: .getConnectionPoolDescriptor();
295: if (cpd == null) {
296: cpd = new ConnectionPoolDescriptor();
297: jcd.setConnectionPoolDescriptor(cpd);
298: }
299: Class conFacCls = ClassHelper.getClass(connectionFactoryClass);
300: cpd.setConnectionFactory(conFacCls);
301:
302: jdbcMetadataUtils.fillJCDFromDataSource(jcd, ds, null, null);
303:
304: if (platform == null
305: && JdbcMetadataUtils.PLATFORM_ORACLE.equals(jcd
306: .getDbms())) {
307: // Postprocess to find Oracle version.
308: updateOraclePlatform(jcd, ds);
309: }
310: // if platform has explicitly been set, the value takes precedence
311: if (platform != null) {
312: if (!platform.equals(jcd.getDbms())) {
313: log.warn("Automatically derived RDBMS platform \""
314: + jcd.getDbms()
315: + "\" differs from explicitly set platform \""
316: + platform + "\"");
317: }
318: jcd.setDbms(platform);
319: } else {
320: platform = jcd.getDbms();
321: }
322:
323: // special attributes
324: jcd.addAttribute("org.apache.jetspeed.engineScoped", Boolean
325: .toString(jetspeedEngineScoped));
326: }
327:
328: /**
329: * @param jcd
330: * @throws LookupException
331: * @throws IllegalAccessException
332: * @throws InstantiationException
333: * throws SQLException
334: */
335: private void updateOraclePlatform(JdbcConnectionDescriptor jcd,
336: DataSource ds) throws LookupException,
337: IllegalAccessException, InstantiationException,
338: SQLException {
339: Connection con = null;
340: try {
341: con = ds.getConnection();
342: DatabaseMetaData metaData = con.getMetaData();
343: int rdbmsVersion = 0;
344: try {
345: // getDatabaseMajorVersion exists since 1.4, so it may
346: // not be defined for the driver used.
347: rdbmsVersion = metaData.getDatabaseMajorVersion();
348: } catch (Throwable t) {
349: String dbVersion = metaData.getDatabaseProductVersion();
350: String relKey = "Release";
351: String major = dbVersion;
352: int startPos = dbVersion.indexOf(relKey);
353: if (startPos < 0) {
354: log
355: .warn("Cannot determine Oracle version, no \"Release\" in procuct version: \""
356: + dbVersion + "\"");
357: return;
358: }
359: startPos += relKey.length();
360: int dotPos = dbVersion.indexOf('.', startPos);
361: if (dotPos > 0) {
362: major = dbVersion.substring(startPos, dotPos)
363: .trim();
364: }
365: try {
366: rdbmsVersion = Integer.parseInt(major);
367: } catch (NumberFormatException e) {
368: log
369: .warn("Cannot determine Oracle version, product version \""
370: + dbVersion
371: + "\" not layed out as \"... Release N.M.....\"");
372: return;
373: }
374: if (log.isDebugEnabled()) {
375: log.debug("Extracted Oracle major version "
376: + rdbmsVersion + " from product version \""
377: + dbVersion + "\"");
378: }
379: }
380: if (rdbmsVersion >= 9) {
381: jcd.setDbms(JdbcMetadataUtils.PLATFORM_ORACLE9I);
382: }
383: } finally {
384: if (con != null) {
385: con.close();
386: }
387: }
388: }
389:
390: /**
391: * a minimal DataSource implementation that satisfies the requirements
392: * of JdbcMetadataUtil.
393: */
394: private class MinimalDataSource implements DataSource {
395: private JdbcConnectionDescriptor jcd = null;
396:
397: /**
398: * Create a new instance using the given JCD.
399: */
400: public MinimalDataSource(JdbcConnectionDescriptor jcd) {
401: this .jcd = jcd;
402: }
403:
404: /* (non-Javadoc)
405: * @see javax.sql.DataSource#getConnection()
406: */
407: public Connection getConnection() throws SQLException {
408: // Use JDBC DriverManager as we may not rely on JCD to be sufficiently
409: // initialized to use any of the ConnectionFactories.
410: try {
411: // loads the driver - NB call to newInstance() added to force initialisation
412: ClassHelper.getClass(jcd.getDriver(), true);
413: String url = jcd.getProtocol() + ":"
414: + jcd.getSubProtocol() + ":" + jcd.getDbAlias();
415: if (jcd.getUserName() == null) {
416: return DriverManager.getConnection(url);
417: } else {
418: return DriverManager.getConnection(url, jcd
419: .getUserName(), jcd.getPassWord());
420: }
421: } catch (ClassNotFoundException e) {
422: throw (IllegalStateException) (new IllegalStateException(
423: e.getMessage())).initCause(e);
424: }
425: }
426:
427: /**
428: * @see javax.sql.DataSource#getConnection(java.lang.String, java.lang.String)
429: */
430: public Connection getConnection(String username, String password)
431: throws SQLException {
432: return getConnection();
433: }
434:
435: /**
436: * @see javax.sql.DataSource#getLoginTimeout()
437: */
438: public int getLoginTimeout() throws SQLException {
439: return 0;
440: }
441:
442: /**
443: * @see javax.sql.DataSource#getLogWriter()
444: */
445: public PrintWriter getLogWriter() throws SQLException {
446: return null;
447: }
448:
449: /**
450: * @see javax.sql.DataSource#setLoginTimeout(int)
451: */
452: public void setLoginTimeout(int seconds) throws SQLException {
453: }
454:
455: /**
456: * @see javax.sql.DataSource#setLogWriter(java.io.PrintWriter)
457: */
458: public void setLogWriter(PrintWriter out) throws SQLException {
459: }
460: }
461:
462: public Connection getConnection() throws SQLException {
463: if (externalDs != null) {
464: return externalDs.getConnection();
465: } else {
466: return super .getConnection();
467: }
468: }
469:
470: public Connection getConnection(String username, String password)
471: throws SQLException {
472:
473: if (externalDs != null) {
474: return externalDs.getConnection(username, password);
475: } else {
476: return super.getConnection(username, password);
477: }
478: }
479:
480: }
|