package mondrian.rolap;
import mondrian.olap.*;
import mondrian.spi.Dialect;
import mondrian.test.TestContext;
import mondrian.util.Pair;
import junit.framework.TestCase;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.*;
import javax.naming.*;
import javax.naming.spi.*;
import javax.sql.DataSource;
/**
* Unit test for {@link RolapConnection}.
*
* @author jng
* @since 16 April, 2004
*/
public class RolapConnectionTest extends TestCase {
private static final ThreadLocal<InitialContext> THREAD_INITIAL_CONTEXT =
new ThreadLocal<InitialContext>();
public RolapConnectionTest(String name) {
super(name);
}
protected void setUp() throws Exception {
super.setUp();
if (!NamingManager.hasInitialContextFactoryBuilder()) {
NamingManager.setInitialContextFactoryBuilder(
new InitialContextFactoryBuilder() {
public InitialContextFactory createInitialContextFactory(
Hashtable<?, ?> environment)
throws NamingException
{
return new InitialContextFactory() {
public Context getInitialContext(
Hashtable<?, ?> environment)
throws NamingException
{
return THREAD_INITIAL_CONTEXT.get();
}
};
}
}
);
}
}
public void testPooledConnectionWithProperties() throws SQLException {
Util.PropertyList properties =
TestContext.instance().getConnectionProperties().clone();
// Only the JDBC-ODBC bridge gives the error necessary for this
// test to succeed. So trivially succeed for all other JDBC
// drivers.
final String jdbc = properties.get("Jdbc");
if (jdbc != null
&& !jdbc.startsWith("jdbc:odbc:"))
{
return;
}
// JDBC-ODBC driver does not support UTF-16, so this test succeeds
// because creating the connection from the DataSource will fail.
properties.put("jdbc.charSet", "UTF-16");
final StringBuilder buf = new StringBuilder();
DataSource dataSource =
RolapConnection.createDataSource(null, properties, buf);
final String desc = buf.toString();
assertTrue(desc.startsWith("Jdbc="));
Connection connection;
try {
connection = dataSource.getConnection();
connection.close();
fail("Expected exception");
} catch (SQLException e) {
if (e.getClass().getName().equals(
"org.apache.commons.dbcp.DbcpException"))
{
// This is expected. (We use string-comparison so that the
// compiler doesn't warn about using a deprecated class.)
} else if (e.getClass() == SQLException.class
&& e.getCause() == null
&& e.getMessage() != null
&& e.getMessage().equals(""))
{
// This is expected, from a later version of Dbcp.
} else {
fail("Expected exception, but got a different one: " + e);
}
} catch (IllegalArgumentException e) {
handleIllegalArgumentException(properties, e);
} finally {
RolapConnectionPool.instance().clearPool();
}
}
public void testNonPooledConnectionWithProperties() {
Util.PropertyList properties =
TestContext.instance().getConnectionProperties().clone();
// Only the JDBC-ODBC bridge gives the error necessary for this
// test to succeed. So trivially succeed for all other JDBC
// drivers.
final String jdbc = properties.get("Jdbc");
if (jdbc != null
&& !jdbc.startsWith("jdbc:odbc:"))
{
return;
}
// This test is just like the test testPooledConnectionWithProperties
// except with non-pooled connections.
properties.put("jdbc.charSet", "UTF-16");
properties.put(RolapConnectionProperties.PoolNeeded.name(), "false");
final StringBuilder buf = new StringBuilder();
DataSource dataSource =
RolapConnection.createDataSource(null, properties, buf);
final String desc = buf.toString();
assertTrue(desc.startsWith("Jdbc="));
Connection connection = null;
try {
connection = dataSource.getConnection();
fail("Expected exception");
} catch (SQLException se) {
// this is expected
} catch (IllegalArgumentException e) {
handleIllegalArgumentException(properties, e);
} finally {
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
// ignore
}
}
}
}
/**
* Handle an {@link IllegalArgumentException} which occurs when have
* tried to create a connection with an illegal charset.
*/
private void handleIllegalArgumentException(
Util.PropertyList properties,
IllegalArgumentException e)
{
// Workaround Java bug #6504538 (see http://bugs.sun.com) with synopsis
// "DriverManager.getConnection throws IllegalArgumentException".
if (System.getProperties().getProperty("java.version")
.startsWith("1.6."))
{
properties.remove("jdbc.charSet");
final StringBuilder buf = new StringBuilder();
DataSource dataSource =
RolapConnection.createDataSource(null, properties, buf);
final String desc = buf.toString();
assertTrue(desc.startsWith("Jdbc="));
try {
Connection connection1 = dataSource.getConnection();
connection1.close();
} catch (SQLException e1) {
// ignore
}
} else {
fail("Expect IllegalArgumentException only in JDK 1.6, got " + e);
}
}
/**
* Tests that the FORMAT function uses the connection's locale.
*/
public void testFormatLocale() {
String expr = "FORMAT(1234.56, \"#,##.#\")";
checkLocale("es_ES", expr, "1.234,6", false);
checkLocale("es_MX", expr, "1,234.6", false);
checkLocale("en_US", expr, "1,234.6", false);
}
/**
* Tests that measures are formatted using the connection's locale.
*/
public void testFormatStringLocale() {
checkLocale("es_ES", "1234.56", "1.234,6", true);
checkLocale("es_MX", "1234.56", "1,234.6", true);
checkLocale("en_US", "1234.56", "1,234.6", true);
}
private static void checkLocale(
final String localeName, String expr, String expected, boolean isQuery)
{
TestContext testContextSpain = new TestContext() {
public mondrian.olap.Connection getConnection() {
Util.PropertyList properties =
Util.parseConnectString(getConnectString());
properties.put(
RolapConnectionProperties.Locale.name(),
localeName);
return DriverManager.getConnection(properties, null);
}
};
if (isQuery) {
String query = "WITH MEMBER [Measures].[Foo] AS '" + expr + "',\n"
+ " FORMAT_STRING = '#,##.#' \n"
+ "SELECT {[MEasures].[Foo]} ON COLUMNS FROM [Sales]";
String expected2 =
"Axis #0:\n"
+ "{}\n"
+ "Axis #1:\n"
+ "{[Measures].[Foo]}\n"
+ "Row #0: " + expected + "\n";
testContextSpain.assertQueryReturns(query, expected2);
} else {
testContextSpain.assertExprReturns(expr, expected);
}
}
public void testConnectSansCatalogFails() {
Util.PropertyList properties =
TestContext.instance().getConnectionProperties().clone();
properties.remove(RolapConnectionProperties.Catalog.name());
properties.remove(RolapConnectionProperties.CatalogContent.name());
if (RolapUtil.SQL_LOGGER.isDebugEnabled()) {
RolapUtil.SQL_LOGGER.debug(
this.getName() + "\n [Connection Properties | " + properties
+ "]\n");
} else {
System.out.println(properties);
}
try {
DriverManager.getConnection(
properties,
null);
fail("expected exception");
} catch (MondrianException e) {
assertTrue(
e.getMessage().indexOf(
"Connect string must contain property 'Catalog' or "
+ "property 'CatalogContent'")
>= 0);
}
}
public void testJndiConnection() throws NamingException {
// Cannot guarantee that this test will work if they have chosen to
// resolve data sources other than by JNDI.
if (MondrianProperties.instance().DataSourceResolverClass.isSet()) {
return;
}
// get a regular connection
Util.PropertyList properties =
TestContext.instance().getConnectionProperties().clone();
final StringBuilder buf = new StringBuilder();
final DataSource dataSource =
RolapConnection.createDataSource(null, properties, buf);
// Don't know what the connect string is - it differs with database
// and with the user's set up - but we know that it contains a JDBC
// connect string. Best we can do is check that createDataSource is
// setting it to something.
final String desc = buf.toString();
assertTrue(desc, desc.startsWith("Jdbc="));
final List<String> lookupCalls = new ArrayList<String>();
// mock the JNDI naming manager to provide that datasource
THREAD_INITIAL_CONTEXT.set(
// Use lazy initialization. Otherwise during initialization of this
// initial context JNDI tries to create a default initial context
// and bumps into itself coming the other way.
new InitialContext(true) {
public Object lookup(String str) {
lookupCalls.add("Called");
return dataSource;
}
}
);
// Use the datasource property to connect to the database.
// Remove user and password, because some data sources (those using
// pools) don't allow you to override user.
Util.PropertyList properties2 =
TestContext.instance().getConnectionProperties().clone();
properties2.remove(RolapConnectionProperties.Jdbc.name());
properties2.remove(RolapConnectionProperties.JdbcUser.name());
properties2.remove(RolapConnectionProperties.JdbcPassword.name());
properties2.put(
RolapConnectionProperties.DataSource.name(), "jnditest");
DriverManager.getConnection(properties2, null);
// if we've made it here with lookupCalls,
// we've successfully used JNDI
assertTrue(lookupCalls.size() > 0);
}
public void testDataSourceOverrideUserPass()
throws SQLException, NamingException
{
// use the datasource property to connect to the database
Util.PropertyList properties =
TestContext.instance().getConnectionProperties().clone();
final Dialect dialect = TestContext.instance().getDialect();
if (dialect.getDatabaseProduct() == Dialect.DatabaseProduct.ACCESS) {
// Access doesn't accept user/password, so this test is pointless.
return;
}
final String jdbcUser =
properties.get(RolapConnectionProperties.JdbcUser.name());
final String jdbcPassword =
properties.get(RolapConnectionProperties.JdbcPassword.name());
if (jdbcUser == null || jdbcPassword == null) {
// Can only run this test if username and password are explicit.
return;
}
// Define a data source with bogus user and password.
properties.put(
RolapConnectionProperties.JdbcUser.name(),
"bogususer");
properties.put(
RolapConnectionProperties.JdbcPassword.name(),
"boguspassword");
properties.put(
RolapConnectionProperties.PoolNeeded.name(),
"false");
final StringBuilder buf = new StringBuilder();
final DataSource dataSource =
RolapConnection.createDataSource(null, properties, buf);
final String desc = buf.toString();
assertTrue(desc, desc.startsWith("Jdbc="));
assertTrue(
desc,
desc.indexOf("JdbcUser=bogususer; JdbcPassword=boguspassword")
>= 0);
final String jndiName = "jndiDataSource";
THREAD_INITIAL_CONTEXT.set(
new InitialContext() {
public Object lookup(String str) {
return str.equals(jndiName)
? dataSource
: null;
}
}
);
// Create a property list that we will use for the actual mondrian
// connection. Replace the original JDBC info with the data source we
// just created.
final Util.PropertyList properties2 = new Util.PropertyList();
for (Pair<String, String> entry : properties) {
properties2.put(entry.getKey(), entry.getValue());
}
properties2.remove(RolapConnectionProperties.Jdbc.name());
properties2.put(
RolapConnectionProperties.DataSource.name(),
jndiName);
// With JdbcUser and JdbcPassword credentials in the mondrian connect
// string, the data source's "user" and "password" properties are
// overridden and the connection succeeds.
properties2.put(
RolapConnectionProperties.JdbcUser.name(),
jdbcUser);
properties2.put(
RolapConnectionProperties.JdbcPassword.name(),
jdbcPassword);
mondrian.olap.Connection connection = null;
try {
connection =
DriverManager.getConnection(properties2, null);
Query query = connection.parseQuery("select from [Sales]");
final Result result = connection.execute(query);
assertNotNull(result);
} finally {
if (connection != null) {
connection.close();
connection = null;
}
}
// If we don't specify JdbcUser and JdbcPassword in the mondrian
// connection properties, mondrian uses the data source's
// bogus credentials, and the connection fails.
properties2.remove(RolapConnectionProperties.JdbcUser.name());
properties2.remove(RolapConnectionProperties.JdbcPassword.name());
for (String poolNeeded : Arrays.asList("false", "true")) {
// Important to test with & without pooling. Connection pools
// typically do not let you change user, so it's important that
// mondrian handles these right.
properties2.put(
RolapConnectionProperties.PoolNeeded.name(), poolNeeded);
try {
connection = DriverManager.getConnection(properties2, null);
fail("Expected exception");
} catch (MondrianException e) {
final String s = TestContext.getStackTrace(e);
assertTrue(
s,
s.indexOf(
"Error while creating SQL connection: "
+ "DataSource=jndiDataSource") >= 0);
switch (dialect.getDatabaseProduct()) {
case DERBY:
assertTrue(
s,
s.indexOf(
"Caused by: java.sql.SQLException: "
+ "Schema 'BOGUSUSER' does not exist") >= 0);
break;
case ORACLE:
assertTrue(
s,
s.indexOf(
"Caused by: java.sql.SQLException: ORA-01017: "
+ "invalid username/password; logon denied") >= 0);
break;
case MYSQL:
assertTrue(
s,
s.indexOf(
"Caused by: java.sql.SQLException: Access denied "
+ "for user 'bogususer'@'localhost' (using "
+ "password: YES)") >= 0);
break;
case POSTGRESQL:
assertTrue(
s,
s.indexOf(
"Caused by: org.postgresql.util.PSQLException: "
+ "FATAL: password authentication failed for "
+ "user \"bogususer\"") >= 0);
break;
}
} finally {
if (connection != null) {
connection.close();
connection = null;
}
}
}
}
}
// End RolapConnectionTest.java