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.


Contents

@@ -510,7 +510,7 @@

2.2. Connections


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 @@

2.2. Connections

        "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 @@

2.2. Connections


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 @@

2.2.1. Connection pooling

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 @@

2.2.1. Connection pooling

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)