001/* 002// This software is subject to the terms of the Eclipse Public License v1.0 003// Agreement, available at the following URL: 004// http://www.eclipse.org/legal/epl-v10.html. 005// You must accept the terms of that agreement to use this software. 006// 007// Copyright (C) 2009-2010 Pentaho 008// All Rights Reserved. 009*/ 010package mondrian.spi.impl; 011 012import mondrian.olap.Util; 013import mondrian.spi.Dialect; 014import mondrian.spi.DialectFactory; 015 016import java.lang.reflect.Constructor; 017import java.lang.reflect.InvocationTargetException; 018import java.sql.*; 019import javax.sql.DataSource; 020 021/** 022 * Implementation of {@link mondrian.spi.DialectFactory} for subclasses 023 * of {@link mondrian.spi.impl.JdbcDialectImpl}. 024 * 025 * <p>Assumes that the dialect has a public constructor that takes a 026 * {@link java.sql.Connection} as a parameter. 027 */ 028public class JdbcDialectFactory implements DialectFactory { 029 private final Dialect.DatabaseProduct databaseProduct; 030 private final Constructor<? extends JdbcDialectImpl> constructor; 031 032 /** 033 * Creates a JdbcDialectFactory. 034 * 035 * @param dialectClass Dialect class 036 * @param databaseProduct Database type (e.g. Oracle) if this is a 037 * common RDBMS, or null if it is an uncommon one 038 */ 039 public JdbcDialectFactory( 040 Class<? extends JdbcDialectImpl> dialectClass, 041 Dialect.DatabaseProduct databaseProduct) 042 { 043 this.databaseProduct = databaseProduct; 044 try { 045 constructor = dialectClass.getConstructor(Connection.class); 046 } catch (NoSuchMethodException e) { 047 throw Util.newError( 048 e, 049 "Class does not contain constructor " 050 + "'public <init>(Connection connection)' required " 051 + "for subclasses of JdbcDialectImpl"); 052 } 053 } 054 055 /** 056 * Creates a temporary connection and calls 057 * {@link mondrian.spi.DialectFactory#createDialect(javax.sql.DataSource, java.sql.Connection)}. 058 * 059 * <p>Helper method, called when {@code createDialect} is called without a 060 * {@link java.sql.Connection} and the dialect factory 061 * cannot create a dialect with {@link javax.sql.DataSource} alone. 062 * 063 * <p>It is a user error if {@code dataSource} is null (since this implies 064 * that {@code createDialect} was called with {@code dataSource} and 065 * {@code connection} both null.</p> 066 * 067 * @param factory Dialect factory 068 * @param dataSource Data source, must not be null 069 * @return Dialect, or null if factory cannot create suitable dialect 070 */ 071 public static Dialect createDialectHelper( 072 DialectFactory factory, 073 DataSource dataSource) 074 { 075 if (dataSource == null) { 076 throw new IllegalArgumentException( 077 "Must specify either dataSource or connection"); 078 } 079 Connection connection = null; 080 try { 081 connection = dataSource.getConnection(); 082 if (connection == null) { 083 // DataSource.getConnection does not return null. But 084 // a null value here would cause infinite recursion, so 085 // let's be cautious. 086 throw new IllegalArgumentException(); 087 } 088 final Dialect dialect = 089 factory.createDialect(dataSource, connection); 090 091 // Close the connection in such a way that if there is a 092 // SQLException, 093 // (a) we propagate the exception, 094 // (b) we don't try to close the connection again. 095 Connection connection2 = connection; 096 connection = null; 097 connection2.close(); 098 return dialect; 099 } catch (SQLException e) { 100 throw Util.newError( 101 e, 102 "Error while creating dialect"); 103 } finally { 104 if (connection != null) { 105 try { 106 connection.close(); 107 } catch (SQLException e) { 108 // ignore 109 } 110 } 111 } 112 } 113 114 public Dialect createDialect(DataSource dataSource, Connection connection) { 115 // If connection is null, create a temporary connection and 116 // recursively call this method. 117 if (connection == null) { 118 return createDialectHelper(this, dataSource); 119 } 120 121 assert connection != null; 122 if (acceptsConnection(connection)) { 123 try { 124 return constructor.newInstance(connection); 125 } catch (InstantiationException e) { 126 throw Util.newError( 127 e, "Error while instantiating dialect"); 128 } catch (IllegalAccessException e) { 129 throw Util.newError( 130 e, "Error while instantiating dialect"); 131 } catch (InvocationTargetException e) { 132 throw Util.newError( 133 e, "Error while instantiating dialect"); 134 } 135 } 136 return null; 137 } 138 139 /** 140 * Returns whether this dialect is suitable for the given connection. 141 * 142 * @param connection Connection 143 * @return Whether suitable 144 */ 145 protected boolean acceptsConnection(Connection connection) { 146 try { 147 final DatabaseMetaData metaData = connection.getMetaData(); 148 final String productName = metaData.getDatabaseProductName(); 149 final String productVersion = metaData.getDatabaseProductVersion(); 150 final Dialect.DatabaseProduct product = 151 JdbcDialectImpl.getProduct(productName, productVersion); 152 return product == this.databaseProduct; 153 } catch (SQLException e) { 154 throw Util.newError( 155 e, "Error while instantiating dialect"); 156 } 157 } 158} 159 160// End JdbcDialectFactory.java