From 6d9c74da00a59c52cc7fccab0e1a194f2a6bc9ac Mon Sep 17 00:00:00 2001
From: Julian Hyde
Date: Tue, 18 Dec 2007 22:11:57 +0000
Subject: [PATCH] Fix code examples in functional spec, and improve a few
javadoc comments. ResultSet returned from Cell.drillThrough() now closes its
connection and statement on close, thereby fixing a connection leak.
git-svn-id: https://olap4j.svn.sourceforge.net/svnroot/olap4j/trunk@54 c6a108a4-781c-0410-a6c6-c2d559e19af0
---
doc/olap4j_fs.html | 60 ++++++++++---------
src/mondrian/olap4j/MondrianOlap4jCell.java | 39 ++++++++++--
src/org/olap4j/OlapStatement.java | 3 +
src/org/olap4j/PreparedOlapStatement.java | 10 +++-
.../driver/xmla/XmlaOlap4jConnection.java | 16 ++---
testsrc/org/olap4j/ConnectionTest.java | 1 +
6 files changed, 86 insertions(+), 43 deletions(-)
diff --git a/doc/olap4j_fs.html b/doc/olap4j_fs.html
index 3c49772..dbc364a 100644
--- a/doc/olap4j_fs.html
+++ b/doc/olap4j_fs.html
@@ -79,10 +79,10 @@
olap4j Specification
Authors: Julian Hyde, Barry Klawans
-Version: 0.9 (beta)
+Version: 0.9.4 (beta)
Revision: $Id$
(log)
-Last modified: December 10th, 2007.
+Last modified: December 18th, 2007.
@@ -510,7 +510,7 @@
Class.forName("mondrian.olap4j.Driver");
OlapConnection connection =
- DriverManager.createConnection(
+ DriverManager.getConnection(
"jdbc:mondrian:local:Jdbc=jdbc:odbc:MondrianFoodMart;"
+
"Catalog=/WEB-INF/queries/FoodMart.xml;"
@@ -518,7 +518,7 @@
"Role='California manager'");
OlapWrapper wrapper = (OlapWrapper) connection;
OlapConnection olapConnection = wrapper.unwrap(OlapConnection.class);
- OlapStatement statement = olapConnection.createOlapStatement();
+ OlapStatement statement = olapConnection.createStatement();
OlapResult result =
statement.execute(
@@ -537,12 +537,12 @@
Class.forName("olap4j.impl.xmla.Driver");
OlapConnection connection =
- DriverManager.createConnection(
+ DriverManager.getConnection(
"jdbc:olap4jxmla:Server=http://localhost/xmla/msxisapi.dll;"
+
"Catalog=FoodMart");
OlapWrapper wrapper = (OlapWrapper) connection;
OlapConnection olapConnection = wrapper.unwrap(OlapConnection.class);
- OlapStatement statement = connection.createOlapStatement();
+ OlapStatement statement = connection.createStatement();
CellSet result =
statement.execute(
@@ -574,20 +574,34 @@
OlapWrapper
interface with the method method unwrap(Class)
to access the object underneath
the wrapped
-connection. For instance,
+connection. For instance, if you were using DBCP, you could define and use a
+pooling olap4j data source as follows:
-DataSource dataSource; // a data source using a connection
- pool
- Connection connection =
- DriverManager.createConnection(
- "jdbc:mondrian:local:Jdbc=jdbc:odbc:MondrianFoodMart;"
- +
- "Catalog=/WEB-INF/queries/FoodMart.xml;"
- +
- "Role='California manager'");
+
import org.apache.commons.dbcp.*;
+ import org.olap4j.*;
+
+ GenericObjectPool connectionPool =
+ new GenericObjectPool(null);
+ ConnectionFactory connectionFactory =
+ new DriverManagerConnectionFactory(
+
+ "jdbc:mondrian:local:Jdbc=jdbc:odbc:MondrianFoodMart;" +
+
+ "Catalog=/WEB-INF/queries/FoodMart.xml;" +
+ "Role='California manager'")
+ PoolableConnectionFactory poolableConnectionFactory =
+ new PoolableConnectionFactory(
+ connectionFactory, connectionPool,
+ null, null, false, true);
+ DataSource dataSource =
+ new PoolingDataSource(connectionPool);
+
+ // and some time later...
+
+ Connection connection = dataSource.getConnection();
OlapWrapper wrapper = (OlapWrapper) connection;
OlapConnection olapConnection = wrapper.unwrap(OlapConnection.class);
- OlapStatement statement = olapConnection.createOlapStatement();
+ OlapStatement statement = olapConnection.createStatement();
The OlapStatement,
PreparedOlapStatement,
@@ -609,17 +623,9 @@
java.sql.Wrapper interface introduced in JDBC 4.0,
so the code can be simplified:
-DataSource dataSource; // a data source using a connection
- pool
- Connection connection =
- DriverManager.createConnection(
- "jdbc:mondrian:local:Jdbc=jdbc:odbc:MondrianFoodMart;"
- +
- "Catalog=/WEB-INF/queries/FoodMart.xml;"
- +
- "Role='California manager'");
+
Connection connection = DriverManager.getConnection();
OlapConnection olapConnection = connection.unwrap(OlapConnection.class);
- OlapStatement statement = olapConnection.createOlapStatement();
+ OlapStatement statement = olapConnection.createStatement();
Note that the OlapWrapper
interface is not needed. This code
will work with any JDBC 4.0-compliant connection pool.
diff --git a/src/mondrian/olap4j/MondrianOlap4jCell.java b/src/mondrian/olap4j/MondrianOlap4jCell.java
index 849b451..18ab829 100644
--- a/src/mondrian/olap4j/MondrianOlap4jCell.java
+++ b/src/mondrian/olap4j/MondrianOlap4jCell.java
@@ -18,6 +18,9 @@
import java.util.List;
import java.util.ArrayList;
import java.sql.*;
+import java.lang.reflect.Proxy;
+
+import mondrian.util.DelegatingInvocationHandler;
/**
* Implementation of {@link Cell}
@@ -109,10 +112,6 @@ public String getFormattedValue() {
}
public ResultSet drillThrough() throws OlapException {
- // REVIEW: This method returns a ResultSet without closing the
- // Statement or the Connection. If we closed them, the ResultSet would
- // be useless. But as it stands, we have a connection leak. Should we
- // tell the client that they need to close the result set's connection?
if (!cell.canDrillThrough()) {
return null;
}
@@ -124,11 +123,41 @@ public ResultSet drillThrough() throws OlapException {
try {
final Connection connection = dataSource.getConnection();
final Statement statement = connection.createStatement();
- return statement.executeQuery(sql);
+ final ResultSet resultSet = statement.executeQuery(sql);
+
+ // To prevent a connection leak, wrap the result set in a proxy
+ // which automatically closes the connection (and hence also the
+ // statement and result set) when the result set is closed.
+ // The caller still has to remember to call ResultSet.close(), of
+ // course.
+ return (ResultSet) Proxy.newProxyInstance(
+ null,
+ new Class>[] {ResultSet.class},
+ new MyDelegatingInvocationHandler(resultSet));
} catch (SQLException e) {
throw olap4jConnection.helper.toOlapException(e);
}
}
+
+ // must be public for reflection to work
+ public static class MyDelegatingInvocationHandler
+ extends DelegatingInvocationHandler
+ {
+ private final ResultSet resultSet;
+
+ MyDelegatingInvocationHandler(ResultSet resultSet) {
+ this.resultSet = resultSet;
+ }
+
+ protected Object getTarget() {
+ return resultSet;
+ }
+
+ // implement ResultSet.close()
+ public void close() throws SQLException {
+ resultSet.getStatement().getConnection().close();
+ }
+ }
}
// End MondrianOlap4jCell.java
diff --git a/src/org/olap4j/OlapStatement.java b/src/org/olap4j/OlapStatement.java
index e3df6ee..34aed5d 100644
--- a/src/org/olap4j/OlapStatement.java
+++ b/src/org/olap4j/OlapStatement.java
@@ -17,6 +17,9 @@
* Object used for statically executing an MDX statement and returning a
* {@link CellSet}.
*
+ * An OlapStatement
is generally created using
+ * {@link OlapConnection#createStatement()}.
+ *
* @see PreparedOlapStatement
*
* @author jhyde
diff --git a/src/org/olap4j/PreparedOlapStatement.java b/src/org/olap4j/PreparedOlapStatement.java
index de62a54..7c3e237 100644
--- a/src/org/olap4j/PreparedOlapStatement.java
+++ b/src/org/olap4j/PreparedOlapStatement.java
@@ -21,6 +21,9 @@
* PreparedOlapStatement
object. This object can then be used to
* efficiently execute this statement multiple times.
*
+ * A PreparedOlapStatement
is generally created using
+ * {@link OlapConnection#prepareOlapStatement(String)}.
+ *
* Note: The setter methods (setShort
,
* setString
, and so on) for setting IN parameter values
* must specify types that are compatible with the defined type of
@@ -40,15 +43,16 @@
* default value until they are set, and then retain their new values for each
* subsequent execution of this PreparedOlapStatement
.
*
- * @see java.sql.Connection#prepareStatement
- * @see java.sql.ResultSet
+ * @see OlapConnection#prepareOlapStatement(String)
+ * @see CellSet
*
* @author jhyde
* @version $Id$
* @since Aug 22, 2006
*/
public interface PreparedOlapStatement
- extends PreparedStatement, OlapStatement {
+ extends PreparedStatement, OlapStatement
+{
/**
* Executes the MDX query in this PreparedOlapStatement
object
* and returns the CellSet
object generated by the query.
diff --git a/src/org/olap4j/driver/xmla/XmlaOlap4jConnection.java b/src/org/olap4j/driver/xmla/XmlaOlap4jConnection.java
index f61e095..488a12c 100644
--- a/src/org/olap4j/driver/xmla/XmlaOlap4jConnection.java
+++ b/src/org/olap4j/driver/xmla/XmlaOlap4jConnection.java
@@ -107,14 +107,6 @@ abstract class XmlaOlap4jConnection implements OlapConnection {
this.catalogName = map.get(XmlaOlap4jDriver.Property.Catalog.name());
- this.olap4jDatabaseMetaData =
- factory.newDatabaseMetaData(this);
- final XmlaOlap4jCatalog catalog =
- (XmlaOlap4jCatalog)
- this.olap4jDatabaseMetaData.getCatalogObjects().get(
- catalogName);
- this.olap4jSchema = new XmlaOlap4jSchema(catalog, catalogName);
-
// Set URL of HTTP server.
String serverUrl = map.get(XmlaOlap4jDriver.Property.Server.name());
if (serverUrl == null) {
@@ -128,6 +120,14 @@ abstract class XmlaOlap4jConnection implements OlapConnection {
throw helper.createException(
"Error while creating connection", e);
}
+
+ this.olap4jDatabaseMetaData =
+ factory.newDatabaseMetaData(this);
+ final XmlaOlap4jCatalog catalog =
+ (XmlaOlap4jCatalog)
+ this.olap4jDatabaseMetaData.getCatalogObjects().get(
+ catalogName);
+ this.olap4jSchema = new XmlaOlap4jSchema(catalog, catalogName);
}
static boolean acceptsURL(String url) {
diff --git a/testsrc/org/olap4j/ConnectionTest.java b/testsrc/org/olap4j/ConnectionTest.java
index e5d9e94..afa444c 100644
--- a/testsrc/org/olap4j/ConnectionTest.java
+++ b/testsrc/org/olap4j/ConnectionTest.java
@@ -740,6 +740,7 @@ public void testCell() throws Exception {
final ResultSet resultSet = cell.drillThrough();
assertEquals(5, resultSet.getMetaData().getColumnCount());
resultSet.close();
+ break;
}
// cell out of range using getCell(int)