diff --git a/build.xml b/build.xml
index a08dc39..69c663c 100644
--- a/build.xml
+++ b/build.xml
@@ -55,9 +55,16 @@ ${jar-jdk14.file}"/>
+
+
-->
@@ -74,6 +81,7 @@ ${jar-jdk14.file}"/>
+
@@ -253,6 +261,7 @@ lib/commons-vfs.jar,
lib/commons-logging.jar,
lib/commons-math-1.0.jar,
lib/javacup.jar,
+lib/xercesImpl.jar,
lib/log4j-1.2.9.jar,
lib/asm-2.2.3.jar,
lib/asm-commons-2.2.3.jar,
@@ -372,7 +381,7 @@ META-INF/**"/>
classpathref="project.test.classpath"
destdir="${javadoc.dir}"
packagenames="org.olap4j.*"
- excludepackagenames="org.olap4j.driver.*,org.olap4j.mdx.parser.impl.*,org.olap4j.sample.*"
+ excludepackagenames="org.olap4j.driver.*,org.olap4j.impl.*,org.olap4j.mdx.parser.impl.*,org.olap4j.sample.*"
overview="${src.dir}/overview.html"
footer="<a href="http://sourceforge.net/projects/olap4j"><img src="http://sourceforge.net/sflogo.php?group_id=35302&type=1" width="88" height="31" border="0" alt="SourceForge.net_Logo"></a>"
author="true"
@@ -441,7 +450,7 @@ META-INF/**"/>
classpathref="project.test.classpath"
destdir="${javadoc.dir}"
packagenames="org.olap4j.*"
- excludepackagenames="org.olap4j.driver.*,org.olap4j.mdx.parser.impl.*,org.olap4j.sample.*"
+ excludepackagenames="org.olap4j.driver.*,org.olap4j.impl.*,org.olap4j.mdx.parser.impl.*,org.olap4j.sample.*"
overview="${src.dir}/overview.html"
footer="<a href="http://sourceforge.net/projects/olap4j"><img src="http://sourceforge.net/sflogo.php?group_id=35302&type=1" width="88" height="31" border="0" alt="SourceForge.net_Logo"></a>"
author="true"
@@ -499,8 +508,8 @@ META-INF/**"/>
-
-
+
@@ -510,6 +519,7 @@ META-INF/**"/>
+
diff --git a/doc/tasks.txt b/doc/tasks.txt
index 964b5f6..6d69d26 100644
--- a/doc/tasks.txt
+++ b/doc/tasks.txt
@@ -28,7 +28,8 @@ Javadoc
Code
----
-
+
+* add method(s) to Cube to look up a member and its relatives by unique name
* fix 4 TODOs in ParserTest
Testing
@@ -51,7 +52,34 @@ Not in scope for olap4j 0.9
* convert parser to javacc
* XMLA driver
-
+
+XMLA driver tasks
+-----------------
+
+* Member cache as part of cube (weak hash map)
+** Use member cache for XmlaOlap4jHierarchy.getDefaultMember (store member in hierarchy, so Hierarchy.getDefaultMember never throws OlapException)
+** Use member cache for XmlaOlap4jHierarchy.getRootMembers
+** Consider using member cache for XmlaOlap4jLevel.getMembers
+** Use member cache for XmlaOlap4jMember.getParentMember (lookup parent member before creating member, and store pointer rather than name of parent member)
+
+* On creating XmlaOlap4jDimension, lookup and store default hierarchy; obsolete defaultHierarchyUniqueName field
+
+* Create enum Measure.DataType (possibly related to Property.Datatype), and change method 'int Measure.dataType()' to 'Datatype Measure.getDatatype()'
+
+* Reduce memory usage by converting some of XmlaOlap4jMember's fields into properties (e.g. ordinal, cardinality)
+
+* Add test for Member.getAncestorMembers
+
+* Remove dependency on xerces; use JDK XML parser.
+
+Changes since 0.9
+-----------------
+
+* Various methods now throw OlapException
+
+* Rename 'enum Property.Scope' to 'Property.TypeFlag', and method
+ 'Scope Property.getScope()' becomes 'Set Property.getType()'.
+
General guidelines
------------------
diff --git a/lib/xercesImpl.jar b/lib/xercesImpl.jar
new file mode 100755
index 0000000..3197095
Binary files /dev/null and b/lib/xercesImpl.jar differ
diff --git a/src/mondrian/olap4j/EmptyResultSet.java b/src/mondrian/olap4j/EmptyResultSet.java
index ff9148f..36f0682 100644
--- a/src/mondrian/olap4j/EmptyResultSet.java
+++ b/src/mondrian/olap4j/EmptyResultSet.java
@@ -27,6 +27,9 @@
* methods for querying object types where those object types never have
* any instances for this particular driver.
*
+ *
This class has sub-classes which implement JDBC 3.0 and JDBC 4.0 APIs;
+ * it is instantiated using {@link Factory#newEmptyResultSet}.
Specification as for XML/A MDSCHEMA_MEMBERS schema rowset.
+ *
Specification as for XML/A MDSCHEMA_MEMBERS schema rowset. Rows
+ * are sorted by level number then by ordinal.
*
*
The treeOps parameter allows you to retrieve members
* relative to a given member. It is only applicable if a
diff --git a/src/org/olap4j/driver/xmla/DeferredNamedListImpl.java b/src/org/olap4j/driver/xmla/DeferredNamedListImpl.java
new file mode 100644
index 0000000..3df3988
--- /dev/null
+++ b/src/org/olap4j/driver/xmla/DeferredNamedListImpl.java
@@ -0,0 +1,113 @@
+/*
+// This software is subject to the terms of the Common Public License
+// Agreement, available at the following URL:
+// http://www.opensource.org/licenses/cpl.html.
+// Copyright (C) 2007-2007 Julian Hyde
+// All Rights Reserved.
+// You must accept the terms of that agreement to use this software.
+*/
+package org.olap4j.driver.xmla;
+
+import org.olap4j.OlapException;
+import org.olap4j.impl.Named;
+import org.olap4j.impl.NamedListImpl;
+import org.olap4j.metadata.NamedList;
+
+import java.util.AbstractList;
+
+/**
+ * Named list which instantiates itself on first use.
+ *
+ *
DeferredNamedListImpl is useful way to load an object model
+ * representing a hierarchical schema. If a catalog contains schemas, which
+ * contain cubes, which contain dimensions, and so forth, and if all
+ * collections loaded immediately, loading the catalog would immediately load
+ * all sub-objects into memory, taking a lot of memory and time.
+ *
+ *
(The above description is only intended to be illustrative. The XMLA
+ * driver schema does not use deferred lists at every level; in particular,
+ * it loads each cube in one swoop, fetching all dimensions, hierarchies and
+ * levels of that cube, in order to reduce the number of metadata requests
+ * submitted.)
+ *
+ *
This class is not gc-friendly at present. Once populated,
+ * DeferredNamedListImpl holds hard references
+ * to the objects it contains, so they are not available to be
+ * garbage-collected. Support for weak references might be a future enhancement
+ * to this class.
+ *
+ * @author jhyde
+ * @version $Id: $
+ * @since Dec 4, 2007
+ */
+class DeferredNamedListImpl
+ extends AbstractList
+ implements NamedList
+{
+ private final NamedList list = new NamedListImpl();
+ private State state = State.NEW;
+
+ protected final XmlaOlap4jConnection.MetadataRequest metadataRequest;
+ protected final XmlaOlap4jConnection.Context context;
+ protected final XmlaOlap4jConnection.Handler handler;
+
+ DeferredNamedListImpl(
+ XmlaOlap4jConnection.MetadataRequest metadataRequest,
+ XmlaOlap4jConnection.Context context,
+ XmlaOlap4jConnection.Handler handler)
+ {
+ this.metadataRequest = metadataRequest;
+ this.context = context;
+ this.handler = handler;
+ }
+
+ private NamedList getList() {
+ switch (state) {
+ case POPULATING:
+ throw new RuntimeException("recursive population");
+ case NEW:
+ try {
+ state = State.POPULATING;
+ populateList(list);
+ state = State.POPULATED;
+ } catch (OlapException e) {
+ // todo: fetch metadata on getCollection() method, so we
+ // can't get an exception while traversing the list
+ throw new RuntimeException(e);
+ }
+ // fall through
+ case POPULATED:
+ default:
+ return list;
+ }
+ }
+
+ public T get(int index) {
+ return getList().get(index);
+ }
+
+ public int size() {
+ return getList().size();
+ }
+
+ public T get(String name) {
+ return getList().get(name);
+ }
+
+ public int indexOfName(String name) {
+ return getList().indexOfName(name);
+ }
+
+ protected void populateList(NamedList list) throws OlapException {
+ context.olap4jConnection.populateList(
+ list, context, metadataRequest, handler);
+ }
+
+ private enum State {
+ NEW,
+ POPULATING,
+ POPULATED
+ }
+}
+
+// End DeferredNamedListImpl.java
diff --git a/src/org/olap4j/driver/xmla/EmptyResultSet.java b/src/org/olap4j/driver/xmla/EmptyResultSet.java
index d3c6d11..6f412ac 100644
--- a/src/org/olap4j/driver/xmla/EmptyResultSet.java
+++ b/src/org/olap4j/driver/xmla/EmptyResultSet.java
@@ -10,12 +10,13 @@
import org.olap4j.OlapWrapper;
+import javax.sql.rowset.RowSetMetaDataImpl;
import java.sql.*;
+import java.sql.Date;
import java.math.BigDecimal;
import java.io.InputStream;
import java.io.Reader;
-import java.util.Map;
-import java.util.Calendar;
+import java.util.*;
import java.net.URL;
/**
@@ -25,19 +26,64 @@
* methods for querying object types where those object types never have
* any instances for this particular driver.
*
+ *
This class has sub-classes which implement JDBC 3.0 and JDBC 4.0 APIs;
+ * it is instantiated using {@link Factory#newEmptyResultSet}.
+ *
* @author jhyde
* @version $Id$
* @since May 24, 2007
*/
abstract class EmptyResultSet implements ResultSet, OlapWrapper {
final XmlaOlap4jConnection olap4jConnection;
-
- EmptyResultSet(XmlaOlap4jConnection olap4jConnection) {
+ private final List headerList;
+ private final List> rowList;
+ private int rowOrdinal = -1;
+ private final RowSetMetaDataImpl metaData = new RowSetMetaDataImpl();
+
+ EmptyResultSet(
+ XmlaOlap4jConnection olap4jConnection,
+ List headerList,
+ List> rowList)
+ {
this.olap4jConnection = olap4jConnection;
+ this.headerList = headerList;
+ this.rowList = rowList;
+ try {
+ metaData.setColumnCount(headerList.size());
+ for (int i = 0; i < headerList.size(); i++) {
+ metaData.setColumnName(i + 1, headerList.get(i));
+ }
+ } catch (SQLException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Returns the value of a given column
+ * @param columnOrdinal 0-based ordinal
+ * @return Value
+ */
+ private Object getColumn(int columnOrdinal) {
+ return rowList.get(rowOrdinal).get(columnOrdinal);
+ }
+
+ private Object getColumn(String columnLabel) throws SQLException {
+ int column = headerList.indexOf(columnLabel);
+ if (column < 0) {
+ throw new SQLException("Column not found: " + columnLabel);
+ }
+ return rowList.get(rowOrdinal).get(column);
}
+ // implement ResultSet
+
public boolean next() throws SQLException {
- return false;
+ // note that if rowOrdinal == rowList.size - 1, we move but then return
+ // false
+ if (rowOrdinal < rowList.size()) {
+ ++rowOrdinal;
+ }
+ return rowOrdinal < rowList.size();
}
public void close() throws SQLException {
@@ -48,35 +94,48 @@ public boolean wasNull() throws SQLException {
}
public String getString(int columnIndex) throws SQLException {
- throw new UnsupportedOperationException();
+ return String.valueOf(getColumn(columnIndex - 1));
}
public boolean getBoolean(int columnIndex) throws SQLException {
- throw new UnsupportedOperationException();
+ Object o = getColumn(columnIndex - 1);
+ if (o instanceof Boolean) {
+ return (Boolean) o;
+ } else if (o instanceof String) {
+ return Boolean.valueOf((String) o);
+ } else {
+ return !o.equals(0);
+ }
}
public byte getByte(int columnIndex) throws SQLException {
- throw new UnsupportedOperationException();
+ Object o = getColumn(columnIndex - 1);
+ return ((Number) o).byteValue();
}
public short getShort(int columnIndex) throws SQLException {
- throw new UnsupportedOperationException();
+ Object o = getColumn(columnIndex - 1);
+ return ((Number) o).shortValue();
}
public int getInt(int columnIndex) throws SQLException {
- throw new UnsupportedOperationException();
+ Object o = getColumn(columnIndex - 1);
+ return ((Number) o).intValue();
}
public long getLong(int columnIndex) throws SQLException {
- throw new UnsupportedOperationException();
+ Object o = getColumn(columnIndex - 1);
+ return ((Number) o).longValue();
}
public float getFloat(int columnIndex) throws SQLException {
- throw new UnsupportedOperationException();
+ Object o = getColumn(columnIndex - 1);
+ return ((Number) o).floatValue();
}
public double getDouble(int columnIndex) throws SQLException {
- throw new UnsupportedOperationException();
+ Object o = getColumn(columnIndex - 1);
+ return ((Number) o).doubleValue();
}
public BigDecimal getBigDecimal(
@@ -85,19 +144,23 @@ public BigDecimal getBigDecimal(
}
public byte[] getBytes(int columnIndex) throws SQLException {
- throw new UnsupportedOperationException();
+ Object o = getColumn(columnIndex - 1);
+ return (byte[]) o;
}
public Date getDate(int columnIndex) throws SQLException {
- throw new UnsupportedOperationException();
+ Object o = getColumn(columnIndex - 1);
+ return (Date) o;
}
public Time getTime(int columnIndex) throws SQLException {
- throw new UnsupportedOperationException();
+ Object o = getColumn(columnIndex - 1);
+ return (Time) o;
}
public Timestamp getTimestamp(int columnIndex) throws SQLException {
- throw new UnsupportedOperationException();
+ Object o = getColumn(columnIndex - 1);
+ return (Timestamp) o;
}
public InputStream getAsciiStream(int columnIndex) throws SQLException {
@@ -113,35 +176,49 @@ public InputStream getBinaryStream(int columnIndex) throws SQLException {
}
public String getString(String columnLabel) throws SQLException {
- throw new UnsupportedOperationException();
+ Object o = getColumn(columnLabel);
+ return String.valueOf(o);
}
public boolean getBoolean(String columnLabel) throws SQLException {
- throw new UnsupportedOperationException();
+ Object o = getColumn(columnLabel);
+ if (o instanceof Boolean) {
+ return (Boolean) o;
+ } else if (o instanceof String) {
+ return Boolean.valueOf((String) o);
+ } else {
+ return !o.equals(0);
+ }
}
public byte getByte(String columnLabel) throws SQLException {
- throw new UnsupportedOperationException();
+ Object o = getColumn(columnLabel);
+ return ((Number) o).byteValue();
}
public short getShort(String columnLabel) throws SQLException {
- throw new UnsupportedOperationException();
+ Object o = getColumn(columnLabel);
+ return ((Number) o).shortValue();
}
public int getInt(String columnLabel) throws SQLException {
- throw new UnsupportedOperationException();
+ Object o = getColumn(columnLabel);
+ return ((Number) o).intValue();
}
public long getLong(String columnLabel) throws SQLException {
- throw new UnsupportedOperationException();
+ Object o = getColumn(columnLabel);
+ return ((Number) o).longValue();
}
public float getFloat(String columnLabel) throws SQLException {
- throw new UnsupportedOperationException();
+ Object o = getColumn(columnLabel);
+ return ((Number) o).floatValue();
}
public double getDouble(String columnLabel) throws SQLException {
- throw new UnsupportedOperationException();
+ Object o = getColumn(columnLabel);
+ return ((Number) o).doubleValue();
}
public BigDecimal getBigDecimal(
@@ -150,19 +227,23 @@ public BigDecimal getBigDecimal(
}
public byte[] getBytes(String columnLabel) throws SQLException {
- throw new UnsupportedOperationException();
+ Object o = getColumn(columnLabel);
+ return (byte[]) o;
}
public Date getDate(String columnLabel) throws SQLException {
- throw new UnsupportedOperationException();
+ Object o = getColumn(columnLabel);
+ return (Date) o;
}
public Time getTime(String columnLabel) throws SQLException {
- throw new UnsupportedOperationException();
+ Object o = getColumn(columnLabel);
+ return (Time) o;
}
public Timestamp getTimestamp(String columnLabel) throws SQLException {
- throw new UnsupportedOperationException();
+ Object o = getColumn(columnLabel);
+ return (Timestamp) o;
}
public InputStream getAsciiStream(String columnLabel) throws SQLException {
@@ -190,7 +271,7 @@ public String getCursorName() throws SQLException {
}
public ResultSetMetaData getMetaData() throws SQLException {
- throw new UnsupportedOperationException();
+ return metaData;
}
public Object getObject(int columnIndex) throws SQLException {
@@ -222,51 +303,78 @@ public BigDecimal getBigDecimal(String columnLabel) throws SQLException {
}
public boolean isBeforeFirst() throws SQLException {
- throw new UnsupportedOperationException();
+ return rowOrdinal < 0;
}
public boolean isAfterLast() throws SQLException {
- throw new UnsupportedOperationException();
+ return rowOrdinal >= rowList.size();
}
public boolean isFirst() throws SQLException {
- throw new UnsupportedOperationException();
+ return rowOrdinal == 0;
}
public boolean isLast() throws SQLException {
- throw new UnsupportedOperationException();
+ return rowOrdinal == rowList.size() - 1;
}
public void beforeFirst() throws SQLException {
- throw new UnsupportedOperationException();
+ rowOrdinal = -1;
}
public void afterLast() throws SQLException {
- throw new UnsupportedOperationException();
+ rowOrdinal = rowList.size();
}
public boolean first() throws SQLException {
- throw new UnsupportedOperationException();
+ if (rowList.size() == 0) {
+ return false;
+ } else {
+ rowOrdinal = 0;
+ return true;
+ }
}
public boolean last() throws SQLException {
- throw new UnsupportedOperationException();
+ if (rowList.size() == 0) {
+ return false;
+ } else {
+ rowOrdinal = rowList.size() - 1;
+ return true;
+ }
}
public int getRow() throws SQLException {
- throw new UnsupportedOperationException();
+ return rowOrdinal + 1; // 1-based
}
public boolean absolute(int row) throws SQLException {
- throw new UnsupportedOperationException();
+ int newRowOrdinal = row - 1;// convert to 0-based
+ if (newRowOrdinal >= 0 && newRowOrdinal < rowList.size()) {
+ rowOrdinal = newRowOrdinal;
+ return true;
+ } else {
+ return false;
+ }
}
public boolean relative(int rows) throws SQLException {
- throw new UnsupportedOperationException();
+ int newRowOrdinal = rowOrdinal + (rows - 1);
+ if (newRowOrdinal >= 0 && newRowOrdinal < rowList.size()) {
+ rowOrdinal = newRowOrdinal;
+ return true;
+ } else {
+ return false;
+ }
}
public boolean previous() throws SQLException {
- throw new UnsupportedOperationException();
+ // converse of next(); note that if rowOrdinal == 0, we decrement
+ // but return false
+ if (rowOrdinal >= 0) {
+ --rowOrdinal;
+ }
+ return rowOrdinal >= 0;
}
public void setFetchDirection(int direction) throws SQLException {
diff --git a/src/org/olap4j/driver/xmla/Factory.java b/src/org/olap4j/driver/xmla/Factory.java
index 7277942..094ab51 100644
--- a/src/org/olap4j/driver/xmla/Factory.java
+++ b/src/org/olap4j/driver/xmla/Factory.java
@@ -8,10 +8,11 @@
*/
package org.olap4j.driver.xmla;
-import java.sql.Connection;
-import java.sql.SQLException;
+import org.olap4j.OlapException;
+
+import java.sql.*;
import java.util.Properties;
-import java.io.InputStream;
+import java.util.List;
/**
* Instantiates classes to implement the olap4j API against the
@@ -30,14 +31,20 @@ Connection newConnection(
String url,
Properties info) throws SQLException;
- EmptyResultSet newEmptyResultSet(XmlaOlap4jConnection olap4jConnection);
+ EmptyResultSet newEmptyResultSet(
+ XmlaOlap4jConnection olap4jConnection);
+
+ ResultSet newFixedResultSet(
+ XmlaOlap4jConnection olap4jConnection,
+ List headerList,
+ List> rowList);
XmlaOlap4jCellSet newCellSet(
- XmlaOlap4jStatement olap4jStatement, /*,
- Result result */InputStream is);
+ XmlaOlap4jStatement olap4jStatement) throws OlapException;
XmlaOlap4jPreparedStatement newPreparedStatement(
- String mdx, XmlaOlap4jConnection olap4jConnection);
+ String mdx,
+ XmlaOlap4jConnection olap4jConnection) throws OlapException;
XmlaOlap4jDatabaseMetaData newDatabaseMetaData(
XmlaOlap4jConnection olap4jConnection);
diff --git a/src/org/olap4j/driver/xmla/FactoryJdbc3Impl.java b/src/org/olap4j/driver/xmla/FactoryJdbc3Impl.java
index 751903e..ff90dd6 100644
--- a/src/org/olap4j/driver/xmla/FactoryJdbc3Impl.java
+++ b/src/org/olap4j/driver/xmla/FactoryJdbc3Impl.java
@@ -8,10 +8,10 @@
*/
package org.olap4j.driver.xmla;
-import java.sql.Connection;
-import java.sql.SQLException;
-import java.util.Properties;
-import java.io.InputStream;
+import org.olap4j.OlapException;
+
+import java.sql.*;
+import java.util.*;
/**
* Implementation of {@link Factory} for JDBC 3.0.
@@ -34,28 +34,40 @@ public Connection newConnection(
public EmptyResultSet newEmptyResultSet(
XmlaOlap4jConnection olap4jConnection)
{
- return new FactoryJdbc3Impl.EmptyResultSetJdbc3(olap4jConnection);
+ List headerList = Collections.emptyList();
+ List> rowList = Collections.emptyList();
+ return new FactoryJdbc3Impl.EmptyResultSetJdbc3(
+ olap4jConnection, headerList, rowList);
+ }
+
+ public ResultSet newFixedResultSet(
+ XmlaOlap4jConnection olap4jConnection,
+ List headerList,
+ List> rowList)
+ {
+ return new EmptyResultSetJdbc3(olap4jConnection, headerList, rowList);
}
public XmlaOlap4jCellSet newCellSet(
- XmlaOlap4jStatement olap4jStatement,
- InputStream is)
+ XmlaOlap4jStatement olap4jStatement) throws OlapException
{
return new FactoryJdbc3Impl.XmlaOlap4jCellSetJdbc3(
- olap4jStatement, is);
+ olap4jStatement);
}
public XmlaOlap4jPreparedStatement newPreparedStatement(
String mdx,
XmlaOlap4jConnection olap4jConnection)
{
- return new FactoryJdbc3Impl.XmlaOlap4jPreparedStatementJdbc3(olap4jConnection, mdx);
+ return new FactoryJdbc3Impl.XmlaOlap4jPreparedStatementJdbc3(
+ olap4jConnection, mdx);
}
public XmlaOlap4jDatabaseMetaData newDatabaseMetaData(
XmlaOlap4jConnection olap4jConnection)
{
- return new FactoryJdbc3Impl.XmlaOlap4jDatabaseMetaDataJdbc3(olap4jConnection);
+ return new FactoryJdbc3Impl.XmlaOlap4jDatabaseMetaDataJdbc3(
+ olap4jConnection);
}
// Inner classes
@@ -72,18 +84,19 @@ public XmlaOlap4jPreparedStatementJdbc3(
private static class XmlaOlap4jCellSetJdbc3
extends XmlaOlap4jCellSet {
public XmlaOlap4jCellSetJdbc3(
- XmlaOlap4jStatement olap4jStatement,
- InputStream is)
+ XmlaOlap4jStatement olap4jStatement) throws OlapException
{
- super(olap4jStatement, is);
+ super(olap4jStatement);
}
}
private static class EmptyResultSetJdbc3 extends EmptyResultSet {
public EmptyResultSetJdbc3(
- XmlaOlap4jConnection olap4jConnection)
+ XmlaOlap4jConnection olap4jConnection,
+ List headerList,
+ List> rowList)
{
- super(olap4jConnection);
+ super(olap4jConnection, headerList, rowList);
}
}
diff --git a/src/org/olap4j/driver/xmla/FactoryJdbc4Impl.java b/src/org/olap4j/driver/xmla/FactoryJdbc4Impl.java
index 3272bd2..a9ff1fa 100644
--- a/src/org/olap4j/driver/xmla/FactoryJdbc4Impl.java
+++ b/src/org/olap4j/driver/xmla/FactoryJdbc4Impl.java
@@ -9,7 +9,7 @@
package org.olap4j.driver.xmla;
import java.sql.*;
-import java.util.Properties;
+import java.util.*;
import java.io.Reader;
import java.io.InputStream;
@@ -27,43 +27,57 @@ public Connection newConnection(
XmlaOlap4jDriver.Proxy proxy,
String url,
Properties info)
- throws SQLException {
- return new FactoryJdbc4Impl.XmlaOlap4jConnectionJdbc4(
+ throws SQLException
+ {
+ return new XmlaOlap4jConnectionJdbc4(
this, proxy, url, info);
}
public EmptyResultSet newEmptyResultSet(
XmlaOlap4jConnection olap4jConnection)
{
- return new FactoryJdbc4Impl.EmptyResultSetJdbc4(olap4jConnection);
+ List headerList = Collections.emptyList();
+ List> rowList = Collections.emptyList();
+ return new EmptyResultSetJdbc4(olap4jConnection, headerList, rowList);
+ }
+
+ public ResultSet newFixedResultSet(
+ XmlaOlap4jConnection olap4jConnection,
+ List headerList,
+ List> rowList)
+ {
+ return new EmptyResultSetJdbc4(
+ olap4jConnection, headerList, rowList);
}
public XmlaOlap4jCellSet newCellSet(
- XmlaOlap4jStatement olap4jStatement,
- InputStream is)
+ XmlaOlap4jStatement olap4jStatement) throws OlapException
{
- return new FactoryJdbc4Impl.XmlaOlap4jCellSetJdbc4(
- olap4jStatement, is);
+ return new XmlaOlap4jCellSetJdbc4(olap4jStatement);
}
public XmlaOlap4jPreparedStatement newPreparedStatement(
String mdx,
- XmlaOlap4jConnection olap4jConnection)
+ XmlaOlap4jConnection olap4jConnection) throws OlapException
{
- return new FactoryJdbc4Impl.XmlaOlap4jPreparedStatementJdbc4(olap4jConnection, mdx);
+ return new XmlaOlap4jPreparedStatementJdbc4(olap4jConnection, mdx);
}
public XmlaOlap4jDatabaseMetaData newDatabaseMetaData(
XmlaOlap4jConnection olap4jConnection)
{
- return new FactoryJdbc4Impl.XmlaOlap4jDatabaseMetaDataJdbc4(olap4jConnection);
+ return new XmlaOlap4jDatabaseMetaDataJdbc4(olap4jConnection);
}
// Inner classes
private static class EmptyResultSetJdbc4 extends EmptyResultSet {
- public EmptyResultSetJdbc4(XmlaOlap4jConnection olap4jConnection) {
- super(olap4jConnection);
+ EmptyResultSetJdbc4(
+ XmlaOlap4jConnection olap4jConnection,
+ List headerList,
+ List> rowList)
+ {
+ super(olap4jConnection, headerList, rowList);
}
// implement java.sql.ResultSet methods
@@ -94,12 +108,14 @@ public boolean isClosed() throws SQLException {
}
public void updateNString(
- int columnIndex, String nString) throws SQLException {
+ int columnIndex, String nString) throws SQLException
+ {
throw new UnsupportedOperationException();
}
public void updateNString(
- String columnLabel, String nString) throws SQLException {
+ String columnLabel, String nString) throws SQLException
+ {
throw new UnsupportedOperationException();
}
@@ -108,7 +124,8 @@ public void updateNClob(int columnIndex, NClob nClob) throws SQLException {
}
public void updateNClob(
- String columnLabel, NClob nClob) throws SQLException {
+ String columnLabel, NClob nClob) throws SQLException
+ {
throw new UnsupportedOperationException();
}
@@ -129,12 +146,14 @@ public SQLXML getSQLXML(String columnLabel) throws SQLException {
}
public void updateSQLXML(
- int columnIndex, SQLXML xmlObject) throws SQLException {
+ int columnIndex, SQLXML xmlObject) throws SQLException
+ {
throw new UnsupportedOperationException();
}
public void updateSQLXML(
- String columnLabel, SQLXML xmlObject) throws SQLException {
+ String columnLabel, SQLXML xmlObject) throws SQLException
+ {
throw new UnsupportedOperationException();
}
@@ -155,42 +174,50 @@ public Reader getNCharacterStream(String columnLabel) throws SQLException {
}
public void updateNCharacterStream(
- int columnIndex, Reader x, long length) throws SQLException {
+ int columnIndex, Reader x, long length) throws SQLException
+ {
throw new UnsupportedOperationException();
}
public void updateNCharacterStream(
- String columnLabel, Reader reader, long length) throws SQLException {
+ String columnLabel, Reader reader, long length) throws SQLException
+ {
throw new UnsupportedOperationException();
}
public void updateAsciiStream(
- int columnIndex, InputStream x, long length) throws SQLException {
+ int columnIndex, InputStream x, long length) throws SQLException
+ {
throw new UnsupportedOperationException();
}
public void updateBinaryStream(
- int columnIndex, InputStream x, long length) throws SQLException {
+ int columnIndex, InputStream x, long length) throws SQLException
+ {
throw new UnsupportedOperationException();
}
public void updateCharacterStream(
- int columnIndex, Reader x, long length) throws SQLException {
+ int columnIndex, Reader x, long length) throws SQLException
+ {
throw new UnsupportedOperationException();
}
public void updateAsciiStream(
- String columnLabel, InputStream x, long length) throws SQLException {
+ String columnLabel, InputStream x, long length) throws SQLException
+ {
throw new UnsupportedOperationException();
}
public void updateBinaryStream(
- String columnLabel, InputStream x, long length) throws SQLException {
+ String columnLabel, InputStream x, long length) throws SQLException
+ {
throw new UnsupportedOperationException();
}
public void updateCharacterStream(
- String columnLabel, Reader reader, long length) throws SQLException {
+ String columnLabel, Reader reader, long length) throws SQLException
+ {
throw new UnsupportedOperationException();
}
@@ -371,11 +398,11 @@ public Struct createStruct(
}
private static class XmlaOlap4jCellSetJdbc4 extends XmlaOlap4jCellSet {
- public XmlaOlap4jCellSetJdbc4(
- XmlaOlap4jStatement olap4jStatement,
- InputStream is)
+ XmlaOlap4jCellSetJdbc4(
+ XmlaOlap4jStatement olap4jStatement)
+ throws OlapException
{
- super(olap4jStatement, is);
+ super(olap4jStatement);
}
public CellSetMetaData getMetaData() {
@@ -619,7 +646,7 @@ private static class XmlaOlap4jPreparedStatementJdbc4
{
XmlaOlap4jPreparedStatementJdbc4(
XmlaOlap4jConnection olap4jConnection,
- String mdx)
+ String mdx) throws OlapException
{
super(olap4jConnection, mdx);
}
diff --git a/src/org/olap4j/driver/xmla/Named.java b/src/org/olap4j/driver/xmla/Named.java
deleted file mode 100644
index c460f88..0000000
--- a/src/org/olap4j/driver/xmla/Named.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
-// This software is subject to the terms of the Common Public License
-// Agreement, available at the following URL:
-// http://www.opensource.org/licenses/cpl.html.
-// Copyright (C) 2007-2007 Julian Hyde
-// All Rights Reserved.
-// You must accept the terms of that agreement to use this software.
-*/
-package org.olap4j.driver.xmla;
-
-/**
- * Interface which describes an object which has a name, for the purposes of
- * creating an implementation, {@link org.olap4j.driver.xmla.NamedListImpl} of
- * {@link org.olap4j.metadata.NamedList} which works on such objects.
- *
- * @author jhyde
- * @version $Id$
- * @since May 23, 2007
- */
-interface Named {
- /**
- * Returns the name of this object.
- *
- * @return name of this object
- */
- String getName();
-}
-
-// End Named.java
diff --git a/src/org/olap4j/driver/xmla/NamedListImpl.java b/src/org/olap4j/driver/xmla/NamedListImpl.java
deleted file mode 100644
index 061f86f..0000000
--- a/src/org/olap4j/driver/xmla/NamedListImpl.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
-// This software is subject to the terms of the Common Public License
-// Agreement, available at the following URL:
-// http://www.opensource.org/licenses/cpl.html.
-// Copyright (C) 2007-2007 Julian Hyde
-// All Rights Reserved.
-// You must accept the terms of that agreement to use this software.
-*/
-package org.olap4j.driver.xmla;
-
-import java.util.ArrayList;
-
-import org.olap4j.metadata.NamedList;
-
-/**
- * Implementation of {@link org.olap4j.metadata.NamedList} which uses
- * {@link java.util.ArrayList} for storage and assumes that elements implement
- * the {@link Named} interface.
- *
- * @author jhyde
- * @version $Id$
- * @since May 23, 2007
- */
-class NamedListImpl
- extends ArrayList
- implements NamedList {
-
- public T get(String name) {
- for (T t : this) {
- if (t.getName().equals(name)) {
- return t;
- }
- }
- return null;
- }
-
- public int indexOfName(String name) {
- for (int i = 0; i < size(); ++i) {
- T t = get(i);
- if (t.getName().equals(name)) {
- return i;
- }
- }
- return -1;
- }
-}
-
-// End NamedListImpl.java
diff --git a/src/org/olap4j/driver/xmla/XmlaOlap4jCatalog.java b/src/org/olap4j/driver/xmla/XmlaOlap4jCatalog.java
new file mode 100644
index 0000000..c426992
--- /dev/null
+++ b/src/org/olap4j/driver/xmla/XmlaOlap4jCatalog.java
@@ -0,0 +1,72 @@
+/*
+// $Id$
+// This software is subject to the terms of the Common Public License
+// Agreement, available at the following URL:
+// http://www.opensource.org/licenses/cpl.html.
+// Copyright (C) 2007-2007 Julian Hyde
+// All Rights Reserved.
+// You must accept the terms of that agreement to use this software.
+*/
+package org.olap4j.driver.xmla;
+
+import org.olap4j.OlapDatabaseMetaData;
+import org.olap4j.OlapException;
+import org.olap4j.impl.Named;
+import org.olap4j.metadata.*;
+
+/**
+ * Implementation of {@link org.olap4j.metadata.Catalog}
+ * for XML/A providers.
+ *
+ * @author jhyde
+ * @version $Id$
+ * @since May 23, 2007
+ */
+class XmlaOlap4jCatalog implements Catalog, Named {
+ final XmlaOlap4jDatabaseMetaData olap4jDatabaseMetaData;
+ private final String name;
+ private final DeferredNamedListImpl schemas;
+
+ XmlaOlap4jCatalog(
+ XmlaOlap4jDatabaseMetaData olap4jDatabaseMetaData,
+ String name)
+ {
+ this.olap4jDatabaseMetaData = olap4jDatabaseMetaData;
+ this.name = name;
+ this.schemas =
+ new DeferredNamedListImpl(
+ XmlaOlap4jConnection.MetadataRequest.DBSCHEMA_CATALOGS,
+ new XmlaOlap4jConnection.Context(
+ olap4jDatabaseMetaData.olap4jConnection,
+ olap4jDatabaseMetaData,
+ this,
+ null, null, null, null, null),
+ new XmlaOlap4jConnection.SchemaHandler());
+ }
+
+ public int hashCode() {
+ return name.hashCode();
+ }
+
+ public boolean equals(Object obj) {
+ if (obj instanceof XmlaOlap4jCatalog) {
+ XmlaOlap4jCatalog that = (XmlaOlap4jCatalog) obj;
+ return this.name.equals(that.name);
+ }
+ return false;
+ }
+
+ public NamedList getSchemas() throws OlapException {
+ return (NamedList) schemas;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public OlapDatabaseMetaData getMetaData() {
+ return olap4jDatabaseMetaData;
+ }
+}
+
+// End XmlaOlap4jCatalog.java
diff --git a/src/org/olap4j/driver/xmla/XmlaOlap4jCell.java b/src/org/olap4j/driver/xmla/XmlaOlap4jCell.java
new file mode 100644
index 0000000..dfd080c
--- /dev/null
+++ b/src/org/olap4j/driver/xmla/XmlaOlap4jCell.java
@@ -0,0 +1,107 @@
+/*
+// This software is subject to the terms of the Common Public License
+// Agreement, available at the following URL:
+// http://www.opensource.org/licenses/cpl.html.
+// Copyright (C) 2007-2007 Julian Hyde
+// All Rights Reserved.
+// You must accept the terms of that agreement to use this software.
+*/
+package org.olap4j.driver.xmla;
+
+import org.olap4j.*;
+import org.olap4j.impl.ArrayMap;
+import org.olap4j.metadata.Property;
+
+import java.sql.ResultSet;
+import java.util.*;
+
+/**
+ * Implementation of {@link org.olap4j.Cell}
+ * for XML/A providers.
+ *
+ * @author jhyde
+ * @version $Id: $
+ * @since Dec 5, 2007
+ */
+class XmlaOlap4jCell implements Cell {
+ private final XmlaOlap4jCellSet cellSet;
+ private final int ordinal;
+ private final Object value;
+ private final String formattedValue;
+ private final Map propertyValues;
+
+ XmlaOlap4jCell(
+ XmlaOlap4jCellSet cellSet,
+ int ordinal,
+ Object value,
+ String formattedValue,
+ Map propertyValues)
+ {
+ this.cellSet = cellSet;
+ this.ordinal = ordinal;
+ this.value = value;
+ this.formattedValue = formattedValue;
+
+ // Use emptyMap and ArrayMap for memory efficiency, because cells
+ // typically have few properties, but there are a lot of cells
+ this.propertyValues =
+ propertyValues.isEmpty()
+ ? Collections.emptyMap()
+ : new ArrayMap(propertyValues);
+ }
+
+ public CellSet getCellSet() {
+ return cellSet;
+ }
+
+ public int getOrdinal() {
+ return ordinal;
+ }
+
+ public List getCoordinateList() {
+ return cellSet.ordinalToCoordinates(ordinal);
+ }
+
+ public Object getPropertyValue(Property property) {
+ return propertyValues.get(property);
+ }
+
+ public boolean isEmpty() {
+ // FIXME
+ return isNull();
+ }
+
+ public boolean isError() {
+ return false;
+ }
+
+ public boolean isNull() {
+ return value == null;
+ }
+
+ public double getDoubleValue() throws OlapException {
+ if (value instanceof Number) {
+ return ((Number) value).doubleValue();
+ } else {
+ return Double.valueOf(String.valueOf(value));
+ }
+ }
+
+ public String getErrorText() {
+ return null; // FIXME:
+ }
+
+ public Object getValue() {
+ return value;
+ }
+
+ public String getFormattedValue() {
+ return formattedValue;
+ }
+
+ public ResultSet drillThrough() throws OlapException {
+ throw new UnsupportedOperationException();
+ }
+}
+
+// End XmlaOlap4jCell.java
diff --git a/src/org/olap4j/driver/xmla/XmlaOlap4jCellProperty.java b/src/org/olap4j/driver/xmla/XmlaOlap4jCellProperty.java
new file mode 100644
index 0000000..00499d3
--- /dev/null
+++ b/src/org/olap4j/driver/xmla/XmlaOlap4jCellProperty.java
@@ -0,0 +1,67 @@
+/*
+// This software is subject to the terms of the Common Public License
+// Agreement, available at the following URL:
+// http://www.opensource.org/licenses/cpl.html.
+// Copyright (C) 2007-2007 Julian Hyde
+// All Rights Reserved.
+// You must accept the terms of that agreement to use this software.
+*/
+package org.olap4j.driver.xmla;
+
+import org.olap4j.impl.Named;
+import org.olap4j.metadata.Datatype;
+import org.olap4j.metadata.Property;
+
+import java.util.Locale;
+import java.util.Set;
+
+/**
+ * Implementation of {@link org.olap4j.metadata.Property}
+ * for a cell in a cellset
+ * from XML/A providers.
+ *
+ * @author jhyde
+ * @version $Id: $
+ * @since Dec 8, 2007
+ */
+class XmlaOlap4jCellProperty implements Property, Named {
+ final String tag;
+ final String propertyName;
+
+ XmlaOlap4jCellProperty(
+ String tag, String propertyName)
+ {
+ this.tag = tag;
+ this.propertyName = propertyName;
+ }
+
+ public Datatype getDatatype() {
+ return Datatype.STRING;
+ }
+
+ public Set getType() {
+ return TypeFlag.forMask(TypeFlag.CELL.xmlaOrdinal);
+ }
+
+ public String getName() {
+ return propertyName;
+ }
+
+ public String getUniqueName() {
+ return propertyName;
+ }
+
+ public String getCaption(Locale locale) {
+ return propertyName;
+ }
+
+ public String getDescription(Locale locale) {
+ return "";
+ }
+
+ public ContentType getContentType() {
+ return ContentType.REGULAR;
+ }
+}
+
+// End XmlaOlap4jCellProperty.java
diff --git a/src/org/olap4j/driver/xmla/XmlaOlap4jCellSet.java b/src/org/olap4j/driver/xmla/XmlaOlap4jCellSet.java
index 262c110..0591c57 100644
--- a/src/org/olap4j/driver/xmla/XmlaOlap4jCellSet.java
+++ b/src/org/olap4j/driver/xmla/XmlaOlap4jCellSet.java
@@ -9,21 +9,26 @@
package org.olap4j.driver.xmla;
import org.olap4j.*;
-
-import java.util.List;
-import java.util.Map;
-import java.util.Calendar;
-import java.sql.*;
+import org.olap4j.impl.Olap4jUtil;
+import static org.olap4j.driver.xmla.XmlaOlap4jUtil.*;
+import org.olap4j.metadata.*;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.xml.sax.SAXException;
+
+import java.io.*;
import java.math.BigDecimal;
-import java.io.InputStream;
-import java.io.Reader;
import java.net.URL;
-
-import mondrian.olap.Util;
+import java.sql.*;
+import java.sql.Date;
+import java.util.*;
/**
* Implementation of {@link org.olap4j.CellSet}
* for XML/A providers.
+ *
+ *
This class has sub-classes which implement JDBC 3.0 and JDBC 4.0 APIs;
+ * it is instantiated using {@link Factory#newCellSet}.
*
* @author jhyde
* @version $Id$
@@ -32,25 +37,343 @@
abstract class XmlaOlap4jCellSet implements CellSet {
final XmlaOlap4jStatement olap4jStatement;
protected boolean closed;
- private final XmlaOlap4jCellSetMetaData metaData;
- private final InputStream is;
-
- public XmlaOlap4jCellSet(
- XmlaOlap4jStatement olap4jStatement,
- InputStream is)
+ private XmlaOlap4jCellSetMetaData metaData;
+ private final Map cellMap =
+ new HashMap();
+ private final List axisList =
+ new ArrayList();
+ private final List immutableAxisList =
+ Olap4jUtil.cast(Collections.unmodifiableList(axisList));
+ private XmlaOlap4jCellSetAxis filterAxis;
+ private static final boolean DEBUG = true;
+
+ private static final List standardProperties = Arrays.asList(
+ "UName", "Caption", "LName", "LNum", "DisplayInfo");
+
+ XmlaOlap4jCellSet(
+ XmlaOlap4jStatement olap4jStatement)
+ throws OlapException
{
assert olap4jStatement != null;
this.olap4jStatement = olap4jStatement;
- this.is = is;
this.closed = false;
+ }
+
+ void populate() throws OlapException {
+ byte[] bytes = olap4jStatement.getBytes();
+
+ Document doc;
+ try {
+ doc = parse(bytes);
+ } catch (IOException e) {
+ throw olap4jStatement.olap4jConnection.helper.createException(
+ "error creating CellSet", e);
+ } catch (SAXException e) {
+ throw olap4jStatement.olap4jConnection.helper.createException(
+ "error creating CellSet", e);
+ }
+ //
+ //
+ //
+ //
+ //
+ //
+ // (see below)
+ //
+ //
+ //
+ //
+ //
+ final Element envelope = doc.getDocumentElement();
+ if (DEBUG) System.out.println(XmlaOlap4jUtil.toString(doc,true));
+ assert envelope.getLocalName().equals("Envelope");
+ assert envelope.getNamespaceURI().equals(SOAP_NS);
+ Element body =
+ findChild(envelope, SOAP_NS, "Body");
+ Element fault =
+ findChild(body, SOAP_NS, "Fault");
+ if (fault != null) {
+ /*
+ Example:
+
+
+ SOAP-ENV:Client.00HSBC01
+ XMLA connection datasource not found
+ Mondrian
+
+
+ 00HSBC01
+ The Mondrian XML: Mondrian Error:Internal
+ error: no catalog named 'LOCALDB'
+
+
+
+ */
+ // TODO: log doc to logfile
+ final Element faultstring = findChild(fault, null, "faultstring");
+ String message = faultstring.getTextContent();
+ throw olap4jStatement.olap4jConnection.helper.createException(
+ "XMLA provider gave exception: " + message);
+ }
+ Element executeResponse =
+ findChild(body, XMLA_NS, "ExecuteResponse");
+ Element returnElement =
+ findChild(executeResponse, XMLA_NS, "return");
+ // has children
+ //
+ //
+ //
+ //
+ // FOO
+ //
+ //
+ //
+ // ...
+ //
+ //
+ //
+ //
+ //
+ //
+ // ...
+ //
+ //
+ //
+ // ...
+ //
+ final Element root =
+ findChild(returnElement, MDDATASET_NS, "root");
+
if (olap4jStatement instanceof XmlaOlap4jPreparedStatement) {
this.metaData =
- ((XmlaOlap4jPreparedStatement) olap4jStatement).cellSetMetaData;
+ ((XmlaOlap4jPreparedStatement) olap4jStatement)
+ .cellSetMetaData;
} else {
- this.metaData =
- new XmlaOlap4jCellSetMetaData(
- olap4jStatement);
+ this.metaData = createMetaData(olap4jStatement, root);
+ }
+
+ // todo: use CellInfo element to determine mapping of cell properties
+ // to XML tags
+ /*
+
+
+
+
+
+ */
+
+ final Element axesNode = findChild(root, MDDATASET_NS, "Axes");
+ for (Element axisNode : findChildren(axesNode, MDDATASET_NS, "Axis")) {
+ final String axisName = axisNode.getAttribute("name");
+ final Axis axis = xx(axisName);
+ final XmlaOlap4jCellSetAxis cellSetAxis =
+ new XmlaOlap4jCellSetAxis(this, axis);
+ switch (axis) {
+ case FILTER:
+ filterAxis = cellSetAxis;
+ break;
+ default:
+ axisList.add(cellSetAxis);
+ break;
+ }
+ final Element tuplesNode =
+ findChild(axisNode, MDDATASET_NS, "Tuples");
+ int ordinal = 0;
+ final Map propertyValues =
+ new HashMap();
+ for (Element tupleNode
+ : findChildren(tuplesNode, MDDATASET_NS, "Tuple"))
+ {
+ final List members = new ArrayList();
+ for (Element memberNode
+ : findChildren(tupleNode, MDDATASET_NS, "Member"))
+ {
+ String hierarchyName =
+ memberNode.getAttribute("Hierarchy");
+ String uname = stringElement(memberNode, "UName");
+ Member member =
+ metaData.cube.lookupMemberByUniqueName(uname);
+ propertyValues.clear();
+ for (Element childNode : childElements(memberNode)) {
+ XmlaOlap4jCellSetMemberProperty property =
+ ((XmlaOlap4jCellSetAxisMetaData)
+ cellSetAxis.getAxisMetaData()).lookupProperty(
+ hierarchyName,
+ childNode.getLocalName());
+ if (property != null) {
+ String value = childNode.getTextContent();
+ propertyValues.put(property, value);
+ }
+ }
+ if (!propertyValues.isEmpty()) {
+ member =
+ new XmlaOlap4jPositionMember(
+ member, propertyValues);
+ }
+ members.add(member);
+ }
+ cellSetAxis.positions.add(
+ new XmlaOlap4jPosition(members, ordinal++));
+ }
+ }
+
+ final Map propertyValues =
+ new HashMap();
+ final Element cellDataNode = findChild(root, MDDATASET_NS, "CellData");
+ for (Element cell : findChildren(cellDataNode, MDDATASET_NS, "Cell")) {
+ propertyValues.clear();
+ final int cellOrdinal =
+ Integer.valueOf(cell.getAttribute("CellOrdinal"));
+ // todo: convert to type based on attribute
+ final String value = stringElement(cell, "Value");
+ final String formattedValue = stringElement(cell, "FmtValue");
+ final String formatString = stringElement(cell, "FormatString");
+ for (Element element : childElements(cell)) {
+ String tag = element.getLocalName();
+ final Property property =
+ metaData.propertiesByTag.get(tag);
+ if (property != null) {
+ propertyValues.put(property, element.getTextContent());
+ }
+ }
+ cellMap.put(
+ cellOrdinal,
+ new XmlaOlap4jCell(
+ this,
+ cellOrdinal,
+ value,
+ formattedValue,
+ propertyValues));
+ }
+ }
+
+ private XmlaOlap4jCellSetMetaData createMetaData(
+ XmlaOlap4jStatement olap4jStatement,
+ Element root) throws OlapException
+ {
+ final Element olapInfo =
+ findChild(root, MDDATASET_NS, "OlapInfo");
+ final Element cubeInfo =
+ findChild(olapInfo, MDDATASET_NS, "CubeInfo");
+ final Element cubeNode =
+ findChild(cubeInfo, MDDATASET_NS, "Cube");
+ final Element cubeNameNode =
+ findChild(cubeNode, MDDATASET_NS, "CubeName");
+ final String cubeName = gatherText(cubeNameNode);
+ final XmlaOlap4jCube cube =
+ this.olap4jStatement.olap4jConnection.olap4jSchema.cubes.get(
+ cubeName);
+ if (cube == null) {
+ throw olap4jStatement.olap4jConnection.helper.createException(
+ "Internal error: cube '" + cubeName + "' not found");
+ }
+ final Element axesInfo =
+ findChild(olapInfo, MDDATASET_NS, "AxesInfo");
+ final List axisInfos =
+ findChildren(axesInfo, MDDATASET_NS, "AxisInfo");
+ final List axisMetaDataList =
+ new ArrayList();
+ XmlaOlap4jCellSetAxisMetaData filterAxisMetaData = null;
+ for (Element axisInfo : axisInfos) {
+ final String axisName = axisInfo.getAttribute("name");
+ Axis axis = xx(axisName);
+ final List hierarchyInfos =
+ findChildren(axisInfo, MDDATASET_NS, "HierarchyInfo");
+ final List hierarchyList =
+ new ArrayList();
+ /*
+
+
+
+
+
+
+
+
+
+
+
+ ...
+
+
+
+
+
+
+
+ */
+ final List propertyList =
+ new ArrayList();
+ for (Element hierarchyInfo : hierarchyInfos) {
+ final String hierarchyName =
+ hierarchyInfo.getAttribute("name");
+ final Hierarchy hierarchy =
+ cube.getHierarchies().get(hierarchyName);
+ if (hierarchy == null) {
+ throw olap4jStatement.olap4jConnection.helper.createException(
+ "Internal error: hierarchy '" + hierarchyName
+ + "' not found in cube '" + cubeName + "'");
+ }
+ hierarchyList.add(hierarchy);
+ for (Element childNode : childElements(hierarchyInfo)) {
+ String tag = childNode.getLocalName();
+ if (standardProperties.contains(tag)) {
+ continue;
+ }
+ final String propertyUniqueName =
+ childNode.getAttribute("name");
+ final XmlaOlap4jCellSetMemberProperty property =
+ new XmlaOlap4jCellSetMemberProperty(
+ propertyUniqueName,
+ hierarchy,
+ tag);
+ propertyList.add(property);
+ }
+ }
+ final XmlaOlap4jCellSetAxisMetaData axisMetaData =
+ new XmlaOlap4jCellSetAxisMetaData(
+ olap4jStatement.olap4jConnection,
+ axis,
+ hierarchyList,
+ propertyList);
+ switch (axis) {
+ case FILTER:
+ filterAxisMetaData = axisMetaData;
+ break;
+ default:
+ axisMetaDataList.add(axisMetaData);
+ break;
+ }
+ }
+ final Element cellInfo =
+ findChild(olapInfo, MDDATASET_NS, "CellInfo");
+ List cellProperties =
+ new ArrayList();
+ for (Element element : childElements(cellInfo)) {
+ cellProperties.add(
+ new XmlaOlap4jCellProperty(
+ element.getLocalName(),
+ element.getAttribute("name")));
}
+ return
+ new XmlaOlap4jCellSetMetaData(
+ olap4jStatement,
+ cube,
+ filterAxisMetaData,
+ axisMetaDataList,
+ cellProperties);
+ }
+
+ private Axis xx(String axisName) {
+ Axis axis;
+ if (axisName.startsWith("Axis")) {
+ final Integer ordinal =
+ Integer.valueOf(axisName.substring("Axis".length()));
+ axis = Axis.values()[Axis.COLUMNS.ordinal() + ordinal];
+ } else {
+ axis = Axis.FILTER;
+ }
+ return axis;
}
public CellSetMetaData getMetaData() {
@@ -58,31 +381,110 @@ public CellSetMetaData getMetaData() {
}
public Cell getCell(List coordinates) {
- throw Util.needToImplement(this);
+ return getCellInternal(coordinatesToOrdinal(coordinates));
}
public Cell getCell(int ordinal) {
- throw Util.needToImplement(this);
+ return getCellInternal(ordinal);
}
public Cell getCell(Position... positions) {
- throw Util.needToImplement(this);
+ if (positions.length != getAxes().size()) {
+ throw new IllegalArgumentException(
+ "cell coordinates should have dimension " + getAxes().size());
+ }
+ List coords = new ArrayList(positions.length);
+ for (Position position : positions) {
+ coords.add(position.getOrdinal());
+ }
+ return getCell(coords);
+ }
+
+ private Cell getCellInternal(int pos) {
+ final Cell cell = cellMap.get(pos);
+ if (cell == null) {
+ if (pos < 0 || pos >= maxOrdinal()) {
+ throw new IndexOutOfBoundsException();
+ } else {
+ // Cell is within bounds, but is not held in the cache because
+ // it has no value. Manufacture a cell with an empty value.
+ return new XmlaOlap4jCell(
+ this, pos, null, null,
+ Collections.emptyMap());
+ }
+ }
+ return cell;
+ }
+
+ private String getBoundsAsString() {
+ StringBuilder buf = new StringBuilder();
+ int k = 0;
+ for (CellSetAxis axis : getAxes()) {
+ if (k++ > 0) {
+ buf.append(", ");
+ }
+ buf.append(axis.getPositionCount());
+ }
+ return buf.toString();
}
public List getAxes() {
- throw Util.needToImplement(this);
+ return immutableAxisList;
}
public CellSetAxis getFilterAxis() {
- throw Util.needToImplement(this);
+ return filterAxis;
+ }
+
+ private int maxOrdinal() {
+ int modulo = 1;
+ for (CellSetAxis axis : axisList) {
+ modulo *= axis.getPositionCount();
+ }
+ return modulo;
}
public List ordinalToCoordinates(int ordinal) {
- throw new UnsupportedOperationException();
+ List axes = getAxes();
+ final List list = new ArrayList(axes.size());
+ int modulo = 1;
+ for (CellSetAxis axis : axes) {
+ int prevModulo = modulo;
+ modulo *= axis.getPositionCount();
+ list.add((ordinal % modulo) / prevModulo);
+ }
+ if (ordinal < 0 || ordinal >= modulo) {
+ throw new IndexOutOfBoundsException(
+ "Cell ordinal " + ordinal
+ + ") lies outside CellSet bounds ("
+ + getBoundsAsString() + ")");
+ }
+ return list;
}
public int coordinatesToOrdinal(List coordinates) {
- throw new UnsupportedOperationException();
+ List axes = getAxes();
+ if (coordinates.size() != axes.size()) {
+ throw new IllegalArgumentException(
+ "Coordinates have different dimension " + coordinates.size()
+ + " than axes " + axes.size());
+ }
+ int modulo = 1;
+ int ordinal = 0;
+ int k = 0;
+ for (CellSetAxis axis : axes) {
+ final Integer coordinate = coordinates.get(k++);
+ if (coordinate < 0 || coordinate >= axis.getPositionCount()) {
+ throw new IndexOutOfBoundsException(
+ "Coordinate " + coordinate
+ + " of axis " + k
+ + " is out of range ("
+ + getBoundsAsString() + ")");
+ }
+ ordinal += coordinate * modulo;
+ modulo *= axis.getPositionCount();
+ }
+ return ordinal;
}
public boolean next() throws SQLException {
@@ -665,6 +1067,7 @@ public T unwrap(Class iface) throws SQLException {
public boolean isWrapperFor(Class> iface) throws SQLException {
throw new UnsupportedOperationException();
}
+
}
// End XmlaOlap4jCellSet.java
diff --git a/src/org/olap4j/driver/xmla/XmlaOlap4jCellSetAxis.java b/src/org/olap4j/driver/xmla/XmlaOlap4jCellSetAxis.java
new file mode 100644
index 0000000..715559a
--- /dev/null
+++ b/src/org/olap4j/driver/xmla/XmlaOlap4jCellSetAxis.java
@@ -0,0 +1,70 @@
+/*
+// This software is subject to the terms of the Common Public License
+// Agreement, available at the following URL:
+// http://www.opensource.org/licenses/cpl.html.
+// Copyright (C) 2007-2007 Julian Hyde
+// All Rights Reserved.
+// You must accept the terms of that agreement to use this software.
+*/
+package org.olap4j.driver.xmla;
+
+import org.olap4j.*;
+
+import java.util.*;
+
+/**
+ * Implementation of {@link org.olap4j.CellSetAxis}
+ * for XML/A providers.
+ *
+ * @author jhyde
+ * @version $Id: $
+ * @since Dec 5, 2007
+ */
+class XmlaOlap4jCellSetAxis implements CellSetAxis {
+ private final XmlaOlap4jCellSet olap4jCellSet;
+ private final Axis axis;
+ final List positions = new ArrayList();
+ private final List immutablePositions =
+ Collections.unmodifiableList(positions);
+
+ public XmlaOlap4jCellSetAxis(
+ XmlaOlap4jCellSet olap4jCellSet,
+ Axis axis)
+ {
+ this.olap4jCellSet = olap4jCellSet;
+ this.axis = axis;
+ }
+
+ public Axis getAxisOrdinal() {
+ return axis;
+ }
+
+ public CellSet getCellSet() {
+ return olap4jCellSet;
+ }
+
+ public CellSetAxisMetaData getAxisMetaData() {
+ final CellSetMetaData cellSetMetaData = olap4jCellSet.getMetaData();
+ switch (axis) {
+ case FILTER:
+ return cellSetMetaData.getFilterAxisMetaData();
+ default:
+ return cellSetMetaData.getAxesMetaData().get(
+ axis.axisOrdinal());
+ }
+ }
+
+ public List getPositions() {
+ return immutablePositions;
+ }
+
+ public int getPositionCount() {
+ return positions.size();
+ }
+
+ public ListIterator iterator() {
+ return (ListIterator) immutablePositions.iterator();
+ }
+}
+
+// End XmlaOlap4jCellSetAxis.java
diff --git a/src/org/olap4j/driver/xmla/XmlaOlap4jCellSetAxisMetaData.java b/src/org/olap4j/driver/xmla/XmlaOlap4jCellSetAxisMetaData.java
new file mode 100755
index 0000000..2ad0c17
--- /dev/null
+++ b/src/org/olap4j/driver/xmla/XmlaOlap4jCellSetAxisMetaData.java
@@ -0,0 +1,69 @@
+/*
+// This software is subject to the terms of the Common Public License
+// Agreement, available at the following URL:
+// http://www.opensource.org/licenses/cpl.html.
+// Copyright (C) 2007-2007 Julian Hyde
+// All Rights Reserved.
+// You must accept the terms of that agreement to use this software.
+*/
+package org.olap4j.driver.xmla;
+
+import org.olap4j.Axis;
+import org.olap4j.CellSetAxisMetaData;
+import org.olap4j.impl.Olap4jUtil;
+import org.olap4j.metadata.Hierarchy;
+import org.olap4j.metadata.Property;
+
+import java.util.*;
+
+/**
+ * Implementation of {@link org.olap4j.CellSetMetaData}
+ * for XML/A providers.
+ *
+ * @author jhyde
+ * @version $Id: MondrianOlap4jCellSetAxisMetaData.java 43 2007-11-21 01:57:47Z jhyde $
+* @since Nov 17, 2007
+*/
+class XmlaOlap4jCellSetAxisMetaData implements CellSetAxisMetaData {
+ private final Axis axis;
+ private final List hierarchyList;
+ private final List propertyList;
+
+ XmlaOlap4jCellSetAxisMetaData(
+ XmlaOlap4jConnection olap4jConnection,
+ Axis axis,
+ List hierarchyList,
+ List propertyList)
+ {
+ this.axis = axis;
+ this.hierarchyList = hierarchyList;
+ this.propertyList = propertyList;
+ }
+
+ public Axis getAxisOrdinal() {
+ return axis;
+ }
+
+ public List getHierarchies() {
+ return hierarchyList;
+ }
+
+ public List getProperties() {
+ return Olap4jUtil.cast(propertyList);
+ }
+
+ XmlaOlap4jCellSetMemberProperty lookupProperty(
+ String hierarchyName,
+ String tag)
+ {
+ for (XmlaOlap4jCellSetMemberProperty property : propertyList) {
+ if (property.hierarchy.getName().equals(hierarchyName)
+ && property.tag.equals(tag)) {
+ return property;
+ }
+ }
+ return null;
+ }
+}
+
+// End XmlaOlap4jCellSetAxisMetaData.java
diff --git a/src/org/olap4j/driver/xmla/XmlaOlap4jCellSetMemberProperty.java b/src/org/olap4j/driver/xmla/XmlaOlap4jCellSetMemberProperty.java
new file mode 100644
index 0000000..88eb5ee
--- /dev/null
+++ b/src/org/olap4j/driver/xmla/XmlaOlap4jCellSetMemberProperty.java
@@ -0,0 +1,70 @@
+/*
+// This software is subject to the terms of the Common Public License
+// Agreement, available at the following URL:
+// http://www.opensource.org/licenses/cpl.html.
+// Copyright (C) 2007-2007 Julian Hyde
+// All Rights Reserved.
+// You must accept the terms of that agreement to use this software.
+*/
+package org.olap4j.driver.xmla;
+
+import org.olap4j.impl.Named;
+import org.olap4j.metadata.*;
+
+import java.util.Locale;
+import java.util.Set;
+
+/**
+ * Implementation of {@link org.olap4j.metadata.Property}
+ * for a member returned on an axis in a cellset
+ * from an XML/A provider.
+ *
+ * @author jhyde
+ * @version $Id: $
+ * @since Dec 7, 2007
+ */
+class XmlaOlap4jCellSetMemberProperty implements Property, Named {
+ private final String propertyUniqueName;
+ final Hierarchy hierarchy;
+ final String tag;
+
+ XmlaOlap4jCellSetMemberProperty(
+ String propertyUniqueName,
+ Hierarchy hierarchy,
+ String tag)
+ {
+ this.propertyUniqueName = propertyUniqueName;
+ this.hierarchy = hierarchy;
+ this.tag = tag;
+ }
+
+ public Datatype getDatatype() {
+ return Datatype.STRING;
+ }
+
+ public Set getType() {
+ return TypeFlag.forMask(TypeFlag.MEMBER.xmlaOrdinal);
+ }
+
+ public String getName() {
+ return tag;
+ }
+
+ public String getUniqueName() {
+ return propertyUniqueName;
+ }
+
+ public String getCaption(Locale locale) {
+ return propertyUniqueName;
+ }
+
+ public String getDescription(Locale locale) {
+ return "";
+ }
+
+ public ContentType getContentType() {
+ return ContentType.REGULAR;
+ }
+}
+
+// End XmlaOlap4jCellSetMemberProperty.java
diff --git a/src/org/olap4j/driver/xmla/XmlaOlap4jCellSetMetaData.java b/src/org/olap4j/driver/xmla/XmlaOlap4jCellSetMetaData.java
index 3915bfc..de1abf0 100644
--- a/src/org/olap4j/driver/xmla/XmlaOlap4jCellSetMetaData.java
+++ b/src/org/olap4j/driver/xmla/XmlaOlap4jCellSetMetaData.java
@@ -8,47 +8,114 @@
*/
package org.olap4j.driver.xmla;
-import mondrian.olap.Util;
import org.olap4j.CellSetAxisMetaData;
import org.olap4j.CellSetMetaData;
+import org.olap4j.impl.ArrayNamedListImpl;
+import org.olap4j.impl.Olap4jUtil;
import org.olap4j.metadata.*;
import java.sql.SQLException;
-import java.util.List;
+import java.util.*;
/**
* Implementation of {@link org.olap4j.CellSetMetaData}
- * for the Mondrian OLAP engine.
+ * for XML/A providers.
*
* @author jhyde
* @version $Id$
* @since Jun 13, 2007
*/
class XmlaOlap4jCellSetMetaData implements CellSetMetaData {
- private final XmlaOlap4jStatement olap4jStatement;
+ final XmlaOlap4jCube cube;
+ private final NamedList axisMetaDataList =
+ new ArrayNamedListImpl() {
+ protected String getName(CellSetAxisMetaData axisMetaData) {
+ return axisMetaData.getAxisOrdinal().name();
+ }
+ };
+ private final XmlaOlap4jCellSetAxisMetaData filterAxisMetaData;
+ private final NamedList cellProperties =
+ new ArrayNamedListImpl() {
+ protected String getName(Property property) {
+ return property.getName();
+ }
+ };
+ final Map propertiesByTag;
XmlaOlap4jCellSetMetaData(
- XmlaOlap4jStatement olap4jStatement)
+ XmlaOlap4jStatement olap4jStatement,
+ XmlaOlap4jCube cube,
+ XmlaOlap4jCellSetAxisMetaData filterAxisMetaData,
+ List axisMetaDataList,
+ List cellProperties)
{
- this.olap4jStatement = olap4jStatement;
+ assert olap4jStatement != null;
+ assert cube != null;
+ assert filterAxisMetaData != null;
+ this.cube = cube;
+ this.filterAxisMetaData = filterAxisMetaData;
+ this.axisMetaDataList.addAll(axisMetaDataList);
+ this.propertiesByTag = new HashMap();
+ for (XmlaOlap4jCellProperty cellProperty : cellProperties) {
+ Property property;
+ try {
+ property = Property.StandardCellProperty.valueOf(
+ cellProperty.propertyName);
+ this.propertiesByTag.put(cellProperty.tag, property);
+ } catch (IllegalArgumentException e) {
+ property = cellProperty;
+ this.propertiesByTag.put(property.getName(), property);
+ }
+ this.cellProperties.add(property);
+ }
+ }
+
+ private XmlaOlap4jCellSetMetaData(
+ XmlaOlap4jStatement olap4jStatement,
+ XmlaOlap4jCube cube,
+ XmlaOlap4jCellSetAxisMetaData filterAxisMetaData,
+ List axisMetaDataList,
+ Map propertiesByTag,
+ List cellProperties)
+ {
+ assert olap4jStatement != null;
+ assert cube != null;
+ assert filterAxisMetaData != null;
+ this.cube = cube;
+ this.filterAxisMetaData = filterAxisMetaData;
+ this.axisMetaDataList.addAll(axisMetaDataList);
+ this.propertiesByTag = propertiesByTag;
+ this.cellProperties.addAll(cellProperties);
+ }
+
+ XmlaOlap4jCellSetMetaData cloneFor(
+ XmlaOlap4jPreparedStatement preparedStatement)
+ {
+ return new XmlaOlap4jCellSetMetaData(
+ preparedStatement,
+ cube,
+ filterAxisMetaData,
+ axisMetaDataList,
+ propertiesByTag,
+ cellProperties);
}
// implement CellSetMetaData
public NamedList getCellProperties() {
- throw Util.needToImplement(this);
+ return Olap4jUtil.cast(cellProperties);
}
public Cube getCube() {
- throw Util.needToImplement(this);
+ return cube;
}
public NamedList getAxesMetaData() {
- throw Util.needToImplement(this);
+ return axisMetaDataList;
}
public CellSetAxisMetaData getFilterAxisMetaData() {
- throw Util.needToImplement(this);
+ return filterAxisMetaData;
}
// implement ResultSetMetaData
diff --git a/src/org/olap4j/driver/xmla/XmlaOlap4jConnection.java b/src/org/olap4j/driver/xmla/XmlaOlap4jConnection.java
index 9af1087..f61e095 100644
--- a/src/org/olap4j/driver/xmla/XmlaOlap4jConnection.java
+++ b/src/org/olap4j/driver/xmla/XmlaOlap4jConnection.java
@@ -9,22 +9,31 @@
package org.olap4j.driver.xmla;
import org.olap4j.*;
+import static org.olap4j.driver.xmla.XmlaOlap4jUtil.*;
+import org.olap4j.impl.*;
+import org.olap4j.mdx.ParseTreeWriter;
+import org.olap4j.mdx.SelectNode;
import org.olap4j.mdx.parser.*;
import org.olap4j.mdx.parser.impl.DefaultMdxParserImpl;
-import org.olap4j.metadata.Catalog;
-import org.olap4j.metadata.NamedList;
+import org.olap4j.metadata.*;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.xml.sax.SAXException;
-import java.util.*;
-import java.sql.*;
-import java.net.URL;
+import java.io.*;
import java.net.MalformedURLException;
-
-import mondrian.olap.Util;
+import java.net.URL;
+import java.sql.*;
+import java.util.*;
+import java.util.regex.Pattern;
/**
* Implementation of {@link org.olap4j.OlapConnection}
* for XML/A providers.
*
+ *
This class has sub-classes which implement JDBC 3.0 and JDBC 4.0 APIs;
+ * it is instantiated using {@link Factory#newConnection}.
+ *
* @author jhyde
* @version $Id$
* @since May 23, 2007
@@ -38,19 +47,15 @@ abstract class XmlaOlap4jConnection implements OlapConnection {
/**
* Current schema.
*/
- XmlaOlap4jSchema olap4jSchema;
+ final XmlaOlap4jSchema olap4jSchema;
private final XmlaOlap4jDatabaseMetaData olap4jDatabaseMetaData;
- /**
- * The name of the sole catalog.
- */
- static final String LOCALDB_CATALOG_NAME = "LOCALDB";
private static final String CONNECT_STRING_PREFIX = "jdbc:xmla:";
final Factory factory;
- XmlaOlap4jDriver.Proxy proxy;
+ final XmlaOlap4jDriver.Proxy proxy;
private boolean closed;
@@ -60,6 +65,8 @@ abstract class XmlaOlap4jConnection implements OlapConnection {
final URL serverUrl;
private Locale locale;
+ private String catalogName;
+ private static final boolean DEBUG = true;
/**
* Creates an Olap4j connection an XML/A provider.
@@ -92,16 +99,24 @@ abstract class XmlaOlap4jConnection implements OlapConnection {
"does not start with '" + CONNECT_STRING_PREFIX + "'");
}
String x = url.substring(CONNECT_STRING_PREFIX.length());
- Util.PropertyList list = Util.parseConnectString(x);
+ Map map =
+ ConnectStringParser.parseConnectString(x);
for (Map.Entry entry : toMap(info).entrySet()) {
- list.put(entry.getKey(), entry.getValue());
+ map.put(entry.getKey(), entry.getValue());
}
+
+ this.catalogName = map.get(XmlaOlap4jDriver.Property.Catalog.name());
+
this.olap4jDatabaseMetaData =
factory.newDatabaseMetaData(this);
- this.olap4jSchema = null; // todo:
+ final XmlaOlap4jCatalog catalog =
+ (XmlaOlap4jCatalog)
+ this.olap4jDatabaseMetaData.getCatalogObjects().get(
+ catalogName);
+ this.olap4jSchema = new XmlaOlap4jSchema(catalog, catalogName);
// Set URL of HTTP server.
- String serverUrl = list.get(XmlaOlap4jDriver.Property.Server.name());
+ String serverUrl = map.get(XmlaOlap4jDriver.Property.Server.name());
if (serverUrl == null) {
throw helper.createException("Connection property '"
+ XmlaOlap4jDriver.Property.Server.name()
@@ -119,6 +134,12 @@ static boolean acceptsURL(String url) {
return url.startsWith(CONNECT_STRING_PREFIX);
}
+ // not part of public API
+ String getDataSourceInfo() {
+ // todo:
+ return "MondrianFoodMart";
+ }
+
public OlapStatement createStatement() {
return new XmlaOlap4jStatement(this);
}
@@ -176,13 +197,11 @@ public boolean isReadOnly() throws SQLException {
}
public void setCatalog(String catalog) throws SQLException {
- if (!catalog.equals(LOCALDB_CATALOG_NAME)) {
- throw new UnsupportedOperationException();
- }
+ this.catalogName = catalog;
}
- public String getCatalog() throws SQLException {
- return LOCALDB_CATALOG_NAME;
+ public String getCatalog() {
+ return catalogName;
}
public void setTransactionIsolation(int level) throws SQLException {
@@ -318,7 +337,7 @@ public MdxParser createMdxParser(OlapConnection connection) {
}
public MdxValidator createMdxValidator(OlapConnection connection) {
- throw Util.needToImplement(this);
+ return new XmlaOlap4jMdxValidator(connection);
}
};
}
@@ -330,7 +349,7 @@ public org.olap4j.metadata.Schema getSchema() throws OlapException {
public static Map toMap(final Properties properties) {
return new AbstractMap() {
public Set> entrySet() {
- return (Set>) (Set) properties.entrySet();
+ return Olap4jUtil.cast(properties.entrySet());
}
};
}
@@ -341,10 +360,13 @@ public Set> entrySet() {
* @return URL
*/
String getURL() {
- throw Util.needToImplement(this);
+ throw Olap4jUtil.needToImplement(this);
}
public void setLocale(Locale locale) {
+ if (locale == null) {
+ throw new IllegalArgumentException("locale must not be null");
+ }
this.locale = locale;
}
@@ -355,14 +377,146 @@ public Locale getLocale() {
return locale;
}
+ void populateList(
+ List list,
+ Context context,
+ MetadataRequest metadataRequest,
+ Handler handler,
+ String... restrictions) throws OlapException
+ {
+ String request =
+ generateRequest(context, metadataRequest, restrictions);
+ Element root = xxx(request);
+ for (Element o : childElements(root)) {
+ if (o.getLocalName().equals("row")) {
+ handler.handle(o, context, list);
+ }
+ }
+ }
+
+ Element xxx(String request) throws OlapException {
+ byte[] bytes;
+ try {
+ bytes = proxy.get(serverUrl, request);
+ } catch (IOException e) {
+ throw helper.createException(null, e);
+ }
+ Document doc;
+ try {
+ doc = parse(bytes);
+ } catch (IOException e) {
+ throw helper.createException(
+ "error discovering metadata", e);
+ } catch (SAXException e) {
+ throw helper.createException(
+ "error discovering metadata", e);
+ }
+ //
+ //
+ //
+ //
+ //
+ //
+ // (see below)
+ //
+ //
+ //
+ //
+ //
+ final Element envelope = doc.getDocumentElement();
+ if (DEBUG) System.out.println(XmlaOlap4jUtil.toString(doc,true));
+ assert envelope.getLocalName().equals("Envelope");
+ assert envelope.getNamespaceURI().equals(SOAP_NS);
+ Element body =
+ findChild(envelope, SOAP_NS, "Body");
+ Element fault =
+ findChild(body, SOAP_NS, "Fault");
+ if (fault != null) {
+ /*
+
+ SOAP-ENV:Client.00HSBC01
+ XMLA connection datasource not found
+ Mondrian
+
+
+ 00HSBC01
+ The Mondrian XML: Mondrian Error:Internal
+ error: no catalog named 'LOCALDB'
+
+
+
+ */
+ // TODO: log doc to logfile
+ final Element faultstring = findChild(fault, null, "faultstring");
+ String message = faultstring.getTextContent();
+ throw helper.createException(
+ "XMLA provider gave exception: " + message
+ + "; request: " + request);
+ }
+ Element discoverResponse =
+ findChild(body, XMLA_NS, "DiscoverResponse");
+ Element returnElement =
+ findChild(discoverResponse, XMLA_NS, "return");
+ return findChild(returnElement, ROWSET_NS, "root");
+ }
+
+ public String generateRequest(
+ Context context,
+ MetadataRequest metadataRequest,
+ String... restrictions)
+ {
+ final String dataSourceInfo =
+ context.olap4jConnection.getDataSourceInfo();
+ final String content = "Data";
+ final StringBuilder buf = new StringBuilder(
+ "\n"
+ + " \n"
+ + " \n"
+ + " ");
+ buf.append(metadataRequest.name());
+ buf.append("\n"
+ + " \n"
+ + " \n");
+ if (restrictions.length > 0) {
+ if (restrictions.length % 2 != 0) {
+ throw new IllegalArgumentException();
+ }
+ for (int i = 0; i < restrictions.length; i += 2) {
+ final String restriction = restrictions[i];
+ final String value = restrictions[i + 1];
+ buf.append("<").append(restriction).append(">");
+ // TODO: escape value
+ buf.append(value);
+ buf.append("").append(restriction).append(">");
+ }
+ }
+ buf.append(" \n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + " ");
+ buf.append(dataSourceInfo);
+ buf.append("\n"
+ + " " + content + "\n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + "\n"
+ + "");
+ return buf.toString();
+ }
+
// ~ inner classes --------------------------------------------------------
static class Helper {
- SQLException createException(String msg) {
- return new SQLException(msg);
+ OlapException createException(String msg) {
+ return new OlapException(msg);
}
- SQLException createException(String msg, Throwable cause) {
+ OlapException createException(String msg, Throwable cause) {
return new OlapException(msg, cause);
}
@@ -372,7 +526,9 @@ OlapException createException(Cell context, String msg) {
return exception;
}
- OlapException createException(Cell context, String msg, Throwable cause) {
+ OlapException createException(
+ Cell context, String msg, Throwable cause)
+ {
OlapException exception = new OlapException(msg, cause);
exception.setContext(context);
return exception;
@@ -386,6 +542,830 @@ public OlapException toOlapException(SQLException e) {
}
}
}
+
+ static class CatalogHandler
+ extends HandlerImpl
+ {
+ public void handle(Element row, Context context, List list) {
+ /*
+ Example:
+
+
+ FoodMart
+ No description available
+ California manager,No HR Cube
+
+ */
+ String catalogName = XmlaOlap4jUtil.stringElement(row, "CATALOG_NAME");
+ // Unused: DESCRIPTION, ROLES
+ list.add(
+ new XmlaOlap4jCatalog(
+ context.olap4jDatabaseMetaData, catalogName));
+ }
+ }
+
+ static class CubeHandler extends HandlerImpl {
+ public void handle(Element row, Context context, List list)
+ throws OlapException
+ {
+ /*
+ Example:
+
+
+ FoodMart
+ FoodMart
+ HR
+ CUBE
+ true
+ false
+ false
+ false
+ FoodMart Schema - HR Cube
+
+ */
+ // Unused: CATALOG_NAME, SCHEMA_NAME, CUBE_TYPE,
+ // IS_DRILLTHROUGH_ENABLED, IS_WRITE_ENABLED, IS_LINKABLE,
+ // IS_SQL_ENABLED
+ String cubeName = stringElement(row, "CUBE_NAME");
+ String description = stringElement(row, "DESCRIPTION");
+ list.add(
+ new XmlaOlap4jCube(
+ context.olap4jSchema, cubeName, description));
+ }
+ }
+
+ static class DimensionHandler extends HandlerImpl {
+ public void handle(Element row, Context context, List list) {
+ /*
+ Example:
+
+
+ FoodMart
+ FoodMart
+ HR
+ Department
+ [Department]
+ Department
+ 6
+ 3
+ 13
+ [Department]
+ HR Cube - Department Dimension
+ false
+ false
+ 0
+ true
+
+
+ */
+ final String dimensionName =
+ stringElement(row, "DIMENSION_NAME");
+ final String dimensionUniqueName =
+ stringElement(row, "DIMENSION_UNIQUE_NAME");
+ final String dimensionCaption =
+ stringElement(row, "DIMENSION_CAPTION");
+ final String description =
+ stringElement(row, "DESCRIPTION");
+ final int dimensionType =
+ integerElement(row, "DIMENSION_TYPE");
+ final Dimension.Type type =
+ Dimension.Type.values()[dimensionType];
+ final String defaultHierarchyUniqueName =
+ stringElement(row, "DEFAULT_HIERARCHY");
+ list.add(
+ new XmlaOlap4jDimension(
+ context.olap4jCube, dimensionUniqueName, dimensionName,
+ dimensionCaption, description, type,
+ defaultHierarchyUniqueName));
+ }
+ }
+
+ static class HierarchyHandler extends HandlerImpl {
+ public void handle(Element row, Context context, List list) {
+ /*
+ Example:
+
+
+ FoodMart
+ FoodMart
+ Sales
+ [Customers]
+ Customers
+ [Customers]
+ Customers
+ 3
+ 10407
+ [Customers].[All Customers]
+ [Customers].[All Customers]
+ Sales Cube - Customers Hierarchy
+ 0
+ false
+ false
+ 0
+ true
+ 9
+ true
+ false
+
+
+ */
+ final String dimensionUniqueName =
+ stringElement(row, "DIMENSION_UNIQUE_NAME");
+ final XmlaOlap4jDimension dimension =
+ context.olap4jCube.dimensionsByUname.get(dimensionUniqueName);
+ final String hierarchyName =
+ stringElement(row, "HIERARCHY_NAME");
+ final String hierarchyUniqueName =
+ stringElement(row, "HIERARCHY_UNIQUE_NAME");
+ final String hierarchyCaption =
+ stringElement(row, "HIERARCHY_CAPTION");
+ final String description =
+ stringElement(row, "DESCRIPTION");
+ final String allMember =
+ stringElement(row, "ALL_MEMBER");
+ final String defaultMemberUniqueName =
+ stringElement(row, "DEFAULT_MEMBER");
+ list.add(
+ new XmlaOlap4jHierarchy(
+ context.getDimension(row), hierarchyUniqueName, hierarchyName,
+ hierarchyCaption, description, allMember != null,
+ defaultMemberUniqueName));
+ }
+ }
+
+ static class LevelHandler extends HandlerImpl {
+ public void handle(Element row, Context context, List list) {
+ /*
+ Example:
+
+
+ FoodMart
+ FoodMart
+ Sales
+ [Customers]
+ [Customers]
+ (All)
+ [Customers].[(All)]
+ (All)
+ 0
+ 1
+ 1
+ 0
+ 3
+ true
+ Sales Cube - Customers Hierarchy(All) Level
+
+
+ */
+ final String levelName =
+ stringElement(row, "LEVEL_NAME");
+ final String levelUniqueName =
+ stringElement(row, "LEVEL_UNIQUE_NAME");
+ final String levelCaption =
+ stringElement(row, "LEVEL_CAPTION");
+ final String description =
+ stringElement(row, "DESCRIPTION");
+ final int levelNumber =
+ integerElement(row, "LEVEL_NUMBER");
+ final Level.Type levelType =
+ Level.Type.forXmlaOrdinal(integerElement(row, "LEVEL_TYPE"));
+ final int levelCardinality =
+ integerElement(row, "LEVEL_CARDINALITY");
+ list.add(
+ new XmlaOlap4jLevel(
+ context.getHierarchy(row), levelUniqueName, levelName,
+ levelCaption, description, levelNumber, levelType,
+ levelCardinality));
+ }
+ }
+
+ static class MeasureHandler extends HandlerImpl {
+ public void handle(Element row, Context context, List list) {
+ /*
+ Example:
+
+
+ FoodMart
+ FoodMart
+ Sales
+ Profit
+ [Measures].[Profit]
+ Profit
+ 127
+ 130
+ true
+ Sales Cube - Profit Member
+
+
+ */
+ final String measureName =
+ stringElement(row, "MEASURE_NAME");
+ final String measureUniqueName =
+ stringElement(row, "MEASURE_UNIQUE_NAME");
+ final String measureCaption =
+ stringElement(row, "MEASURE_CAPTION");
+ final String description =
+ stringElement(row, "DESCRIPTION");
+ final Measure.Aggregator measureAggregator =
+ Measure.Aggregator.forXmlaOrdinal(
+ integerElement(row, "MEASURE_AGGREGATOR"));
+ final Datatype datatype =
+ Datatype.forXmlaOrdinal(
+ integerElement(row, "DATA_TYPE"));
+ final boolean measureIsVisible =
+ booleanElement(row, "MEASURE_IS_VISIBLE");
+ // REVIEW: We're making a lot of assumptions about where Measures
+ // live.
+ final XmlaOlap4jLevel measuresLevel =
+ (XmlaOlap4jLevel)
+ context.getCube(row).getHierarchies().get("Measures")
+ .getLevels().get(0);
+ list.add(
+ new XmlaOlap4jMeasure(
+ measuresLevel, measureUniqueName, measureName,
+ measureCaption, description, null, measureAggregator,
+ datatype, measureIsVisible));
+ }
+ }
+
+ static class MemberHandler extends HandlerImpl {
+ public void handle(Element row, Context context, List list) {
+ /*
+ Example:
+
+
+ FoodMart
+ FoodMart
+ Sales
+ [Gender]
+ [Gender]
+ [Gender].[Gender]
+ 1
+ 1
+ F
+ [Gender].[All Gender].[F]
+ 1
+ F
+ 0
+ 0
+ [Gender].[All Gender]
+ 1
+ 1
+
+
+ */
+ int levelNumber = integerElement(row, "LEVEL_NUMBER");
+ int memberOrdinal = integerElement(row, "MEMBER_ORDINAL");
+ String memberUniqueName =
+ stringElement(row, "MEMBER_UNIQUE_NAME");
+ String memberName =
+ stringElement(row, "MEMBER_NAME");
+ String parentUniqueName =
+ stringElement(row, "PARENT_UNIQUE_NAME");
+ Member.Type memberType =
+ Member.Type.values()[
+ integerElement(row, "MEMBER_TYPE")];
+ String memberCaption =
+ stringElement(row, "MEMBER_CAPTION");
+ int childrenCardinality =
+ integerElement(row, "CHILDREN_CARDINALITY");
+ list.add(
+ new XmlaOlap4jMember(
+ context.getLevel(row), memberUniqueName, memberName,
+ memberCaption, "", parentUniqueName, memberType,
+ childrenCardinality, memberOrdinal));
+ }
+ }
+
+ static class NamedSetHandler extends HandlerImpl {
+ public void handle(Element row, Context context, List list) {
+ /*
+ Example:
+
+
+ FoodMart
+ FoodMart
+ Warehouse
+ [Top Sellers]
+ 1
+
+
+ */
+ final String setName =
+ stringElement(row, "SET_NAME");
+ list.add(
+ new XmlaOlap4jNamedSet(
+ context.getCube(row), setName));
+ }
+ }
+
+ static class SchemaHandler extends HandlerImpl {
+ public void handle(Element row, Context context, List list) {
+ /*
+
+ LOCALDB
+ FoodMart
+ dbo
+
+ */
+ String schemaName = stringElement(row, "CATALOG_NAME");
+ list.add(
+ new XmlaOlap4jSchema(
+ context.getCatalog(row),
+ schemaName));
+ }
+ }
+
+ static class PropertyHandler extends HandlerImpl {
+ public void handle(
+ Element row,
+ Context context, List list) throws OlapException
+ {
+ /*
+ Example:
+
+
+ FoodMart
+ FoodMart
+ HR
+ [Store]
+ [Store]
+ [Store].[Store Name]
+ Store Manager
+ Store Manager
+ 1
+ 130
+ 0
+ HR Cube - Store Hierarchy - Store
+ Name Level - Store Manager Property
+
+ */
+ String cubeName = stringElement(row, "CUBE_NAME");
+ String description = stringElement(row, "DESCRIPTION");
+ String uniqueName = stringElement(row, "DESCRIPTION");
+ String caption = stringElement(row, "PROPERTY_CAPTION");
+ String name = stringElement(row, "PROPERTY_NAME");
+ Datatype dataType =
+ Datatype.forXmlaOrdinal(
+ integerElement(row, "DATA_TYPE"));
+ Property.ContentType contentType =
+ Property.ContentType.forXmlaOrdinal(
+ integerElement(row, "PROPERTY_CONTENT_TYPE"));
+ int propertyType = integerElement(row, "PROPERTY_TYPE");
+ Set type = Property.TypeFlag.forMask(propertyType);
+ list.add(
+ new XmlaOlap4jProperty(
+ uniqueName, name, caption, description, dataType, type,
+ contentType));
+ }
+ }
+
+ interface Handler {
+ void handle(
+ Element row,
+ Context context, List list) throws OlapException;
+ }
+
+ static abstract class HandlerImpl implements Handler {
+ }
+
+ static class Context {
+ final XmlaOlap4jConnection olap4jConnection;
+ final XmlaOlap4jDatabaseMetaData olap4jDatabaseMetaData;
+ final XmlaOlap4jCatalog olap4jCatalog;
+ final XmlaOlap4jSchema olap4jSchema;
+ final XmlaOlap4jCube olap4jCube;
+ final XmlaOlap4jDimension olap4jDimension;
+ final XmlaOlap4jHierarchy olap4jHierarchy;
+ final XmlaOlap4jLevel olap4jLevel;
+
+ /**
+ * Creates a Context.
+ *
+ * @param olap4jConnection Connection (must not be null)
+ * @param olap4jDatabaseMetaData DatabaseMetaData (may be null)
+ * @param olap4jCatalog Catalog (may be null if DatabaseMetaData is null)
+ * @param olap4jSchema Schema (may be null if Catalog is null)
+ * @param olap4jCube Cube (may be null if Schema is null)
+ * @param olap4jDimension Dimension (may be null if Cube is null)
+ * @param olap4jHierarchy Hierarchy (may be null if Dimension is null)
+ * @param olap4jLevel Level (may be null if Hierarchy is null)
+ */
+ Context(
+ XmlaOlap4jConnection olap4jConnection,
+ XmlaOlap4jDatabaseMetaData olap4jDatabaseMetaData,
+ XmlaOlap4jCatalog olap4jCatalog,
+ XmlaOlap4jSchema olap4jSchema,
+ XmlaOlap4jCube olap4jCube,
+ XmlaOlap4jDimension olap4jDimension,
+ XmlaOlap4jHierarchy olap4jHierarchy,
+ XmlaOlap4jLevel olap4jLevel)
+ {
+ this.olap4jConnection = olap4jConnection;
+ this.olap4jDatabaseMetaData = olap4jDatabaseMetaData;
+ this.olap4jCatalog = olap4jCatalog;
+ this.olap4jSchema = olap4jSchema;
+ this.olap4jCube = olap4jCube;
+ this.olap4jDimension = olap4jDimension;
+ this.olap4jHierarchy = olap4jHierarchy;
+ this.olap4jLevel = olap4jLevel;
+ assert (olap4jDatabaseMetaData != null || olap4jCatalog == null)
+ && (olap4jCatalog != null || olap4jSchema == null)
+ && (olap4jSchema != null || olap4jCube == null)
+ && (olap4jCube != null || olap4jDimension == null)
+ && (olap4jDimension != null || olap4jHierarchy == null)
+ && (olap4jHierarchy != null || olap4jLevel == null);
+ }
+
+ /**
+ * Shorthand way to create a Context at Cube level or finer.
+ *
+ * @param olap4jCube Cube (must not be null)
+ * @param olap4jDimension Dimension (may be null)
+ * @param olap4jHierarchy Hierarchy (may be null if Dimension is null)
+ * @param olap4jLevel Level (may be null if Hierarchy is null)
+ */
+ Context(
+ XmlaOlap4jCube olap4jCube,
+ XmlaOlap4jDimension olap4jDimension,
+ XmlaOlap4jHierarchy olap4jHierarchy,
+ XmlaOlap4jLevel olap4jLevel)
+ {
+ this(
+ olap4jCube.olap4jSchema.olap4jCatalog.olap4jDatabaseMetaData
+ .olap4jConnection,
+ olap4jCube.olap4jSchema.olap4jCatalog.olap4jDatabaseMetaData,
+ olap4jCube.olap4jSchema.olap4jCatalog,
+ olap4jCube.olap4jSchema,
+ olap4jCube,
+ olap4jDimension,
+ olap4jHierarchy,
+ olap4jLevel);
+ }
+
+ /**
+ * Shorthand way to create a Context at Level level.
+ *
+ * @param olap4jLevel Level (must not be null)
+ */
+ Context(XmlaOlap4jLevel olap4jLevel)
+ {
+ this(
+ olap4jLevel.olap4jHierarchy.olap4jDimension.olap4jCube,
+ olap4jLevel.olap4jHierarchy.olap4jDimension,
+ olap4jLevel.olap4jHierarchy,
+ olap4jLevel);
+ }
+
+ XmlaOlap4jHierarchy getHierarchy(Element row) {
+ if (olap4jHierarchy != null) {
+ return olap4jHierarchy;
+ }
+ final String hierarchyUniqueName =
+ stringElement(row, "HIERARCHY_UNIQUE_NAME");
+ return getCube(row).hierarchiesByUname.get(hierarchyUniqueName);
+ }
+
+ XmlaOlap4jCube getCube(Element row) {
+ if (olap4jCube != null) {
+ return olap4jCube;
+ }
+ throw new UnsupportedOperationException(); // todo:
+ }
+
+ XmlaOlap4jDimension getDimension(Element row) {
+ if (olap4jDimension != null) {
+ return olap4jDimension;
+ }
+ final String dimensionUniqueName =
+ stringElement(row, "DIMENSION_UNIQUE_NAME");
+ return getCube(row).dimensionsByUname.get(dimensionUniqueName);
+ }
+
+ public XmlaOlap4jLevel getLevel(Element row) {
+ if (olap4jLevel != null) {
+ return olap4jLevel;
+ }
+ final String levelUniqueName =
+ stringElement(row, "LEVEL_UNIQUE_NAME");
+ return getCube(row).levelsByUname.get(levelUniqueName);
+ }
+
+ public XmlaOlap4jCatalog getCatalog(Element row) {
+ if (olap4jCatalog != null) {
+ return olap4jCatalog;
+ }
+ final String catalogName =
+ stringElement(row, "CATALOG_NAME");
+ return (XmlaOlap4jCatalog) olap4jConnection.getCatalogs().get(
+ catalogName);
+ }
+ }
+
+ enum MetadataRequest {
+ DISCOVER_DATASOURCES(
+ new MetadataColumn("DataSourceName"),
+ new MetadataColumn("DataSourceDescription"),
+ new MetadataColumn("URL"),
+ new MetadataColumn("DataSourceInfo"),
+ new MetadataColumn("ProviderName"),
+ new MetadataColumn("ProviderType"),
+ new MetadataColumn("AuthenticationMode")),
+ DISCOVER_SCHEMA_ROWSETS(
+ new MetadataColumn("SchemaName"),
+ new MetadataColumn("SchemaGuid"),
+ new MetadataColumn("Restrictions"),
+ new MetadataColumn("Description")),
+ DISCOVER_ENUMERATORS(
+ new MetadataColumn("EnumName"),
+ new MetadataColumn("EnumDescription"),
+ new MetadataColumn("EnumType"),
+ new MetadataColumn("ElementName"),
+ new MetadataColumn("ElementDescription"),
+ new MetadataColumn("ElementValue")),
+ DISCOVER_PROPERTIES(
+ new MetadataColumn("PropertyName"),
+ new MetadataColumn("PropertyDescription"),
+ new MetadataColumn("PropertyType"),
+ new MetadataColumn("PropertyAccessType"),
+ new MetadataColumn("IsRequired"),
+ new MetadataColumn("Value")),
+ DISCOVER_KEYWORDS(
+ new MetadataColumn("Keyword")),
+ DISCOVER_LITERALS(
+ new MetadataColumn("LiteralName"),
+ new MetadataColumn("LiteralValue"),
+ new MetadataColumn("LiteralInvalidChars"),
+ new MetadataColumn("LiteralInvalidStartingChars"),
+ new MetadataColumn("LiteralMaxLength")),
+ DBSCHEMA_CATALOGS(
+ new MetadataColumn("CATALOG_NAME"),
+ new MetadataColumn("DESCRIPTION"),
+ new MetadataColumn("ROLES"),
+ new MetadataColumn("DATE_MODIFIED")),
+ DBSCHEMA_COLUMNS(
+ new MetadataColumn("TABLE_CATALOG"),
+ new MetadataColumn("TABLE_SCHEMA"),
+ new MetadataColumn("TABLE_NAME"),
+ new MetadataColumn("COLUMN_NAME"),
+ new MetadataColumn("ORDINAL_POSITION"),
+ new MetadataColumn("COLUMN_HAS_DEFAULT"),
+ new MetadataColumn("COLUMN_FLAGS"),
+ new MetadataColumn("IS_NULLABLE"),
+ new MetadataColumn("DATA_TYPE"),
+ new MetadataColumn("CHARACTER_MAXIMUM_LENGTH"),
+ new MetadataColumn("CHARACTER_OCTET_LENGTH"),
+ new MetadataColumn("NUMERIC_PRECISION"),
+ new MetadataColumn("NUMERIC_SCALE")),
+ DBSCHEMA_PROVIDER_TYPES(
+ new MetadataColumn("TYPE_NAME"),
+ new MetadataColumn("DATA_TYPE"),
+ new MetadataColumn("COLUMN_SIZE"),
+ new MetadataColumn("LITERAL_PREFIX"),
+ new MetadataColumn("LITERAL_SUFFIX"),
+ new MetadataColumn("IS_NULLABLE"),
+ new MetadataColumn("CASE_SENSITIVE"),
+ new MetadataColumn("SEARCHABLE"),
+ new MetadataColumn("UNSIGNED_ATTRIBUTE"),
+ new MetadataColumn("FIXED_PREC_SCALE"),
+ new MetadataColumn("AUTO_UNIQUE_VALUE"),
+ new MetadataColumn("IS_LONG"),
+ new MetadataColumn("BEST_MATCH")),
+ DBSCHEMA_TABLES(
+ new MetadataColumn("TABLE_CATALOG"),
+ new MetadataColumn("TABLE_SCHEMA"),
+ new MetadataColumn("TABLE_NAME"),
+ new MetadataColumn("TABLE_TYPE"),
+ new MetadataColumn("TABLE_GUID"),
+ new MetadataColumn("DESCRIPTION"),
+ new MetadataColumn("TABLE_PROPID"),
+ new MetadataColumn("DATE_CREATED"),
+ new MetadataColumn("DATE_MODIFIED")),
+ DBSCHEMA_TABLES_INFO(
+ new MetadataColumn("TABLE_CATALOG"),
+ new MetadataColumn("TABLE_SCHEMA"),
+ new MetadataColumn("TABLE_NAME"),
+ new MetadataColumn("TABLE_TYPE"),
+ new MetadataColumn("TABLE_GUID"),
+ new MetadataColumn("BOOKMARKS"),
+ new MetadataColumn("BOOKMARK_TYPE"),
+ new MetadataColumn("BOOKMARK_DATATYPE"),
+ new MetadataColumn("BOOKMARK_MAXIMUM_LENGTH"),
+ new MetadataColumn("BOOKMARK_INFORMATION"),
+ new MetadataColumn("TABLE_VERSION"),
+ new MetadataColumn("CARDINALITY"),
+ new MetadataColumn("DESCRIPTION"),
+ new MetadataColumn("TABLE_PROPID")),
+ DBSCHEMA_SCHEMATA(
+ new MetadataColumn("CATALOG_NAME"),
+ new MetadataColumn("SCHEMA_NAME"),
+ new MetadataColumn("SCHEMA_OWNER")),
+ MDSCHEMA_ACTIONS(
+ new MetadataColumn("SCHEMA_NAME"),
+ new MetadataColumn("CUBE_NAME"),
+ new MetadataColumn("ACTION_NAME"),
+ new MetadataColumn("COORDINATE"),
+ new MetadataColumn("COORDINATE_TYPE")),
+ MDSCHEMA_CUBES(
+ new MetadataColumn("CATALOG_NAME"),
+ new MetadataColumn("SCHEMA_NAME"),
+ new MetadataColumn("CUBE_NAME"),
+ new MetadataColumn("CUBE_TYPE"),
+ new MetadataColumn("CUBE_GUID"),
+ new MetadataColumn("CREATED_ON"),
+ new MetadataColumn("LAST_SCHEMA_UPDATE"),
+ new MetadataColumn("SCHEMA_UPDATED_BY"),
+ new MetadataColumn("LAST_DATA_UPDATE"),
+ new MetadataColumn("DATA_UPDATED_BY"),
+ new MetadataColumn("IS_DRILLTHROUGH_ENABLED"),
+ new MetadataColumn("IS_WRITE_ENABLED"),
+ new MetadataColumn("IS_LINKABLE"),
+ new MetadataColumn("IS_SQL_ENABLED"),
+ new MetadataColumn("DESCRIPTION")),
+ MDSCHEMA_DIMENSIONS(
+ new MetadataColumn("CATALOG_NAME"),
+ new MetadataColumn("SCHEMA_NAME"),
+ new MetadataColumn("CUBE_NAME"),
+ new MetadataColumn("DIMENSION_NAME"),
+ new MetadataColumn("DIMENSION_UNIQUE_NAME"),
+ new MetadataColumn("DIMENSION_GUID"),
+ new MetadataColumn("DIMENSION_CAPTION"),
+ new MetadataColumn("DIMENSION_ORDINAL"),
+ new MetadataColumn("DIMENSION_TYPE"),
+ new MetadataColumn("DIMENSION_CARDINALITY"),
+ new MetadataColumn("DEFAULT_HIERARCHY"),
+ new MetadataColumn("DESCRIPTION"),
+ new MetadataColumn("IS_VIRTUAL"),
+ new MetadataColumn("IS_READWRITE"),
+ new MetadataColumn("DIMENSION_UNIQUE_SETTINGS"),
+ new MetadataColumn("DIMENSION_MASTER_UNIQUE_NAME"),
+ new MetadataColumn("DIMENSION_IS_VISIBLE")),
+ MDSCHEMA_FUNCTIONS(
+ new MetadataColumn("FUNCTION_NAME"),
+ new MetadataColumn("DESCRIPTION"),
+ new MetadataColumn("PARAMETER_LIST"),
+ new MetadataColumn("RETURN_TYPE"),
+ new MetadataColumn("ORIGIN"),
+ new MetadataColumn("INTERFACE_NAME"),
+ new MetadataColumn("LIBRARY_NAME"),
+ new MetadataColumn("CAPTION")),
+ MDSCHEMA_HIERARCHIES(
+ new MetadataColumn("CATALOG_NAME"),
+ new MetadataColumn("SCHEMA_NAME"),
+ new MetadataColumn("CUBE_NAME"),
+ new MetadataColumn("DIMENSION_UNIQUE_NAME"),
+ new MetadataColumn("HIERARCHY_NAME"),
+ new MetadataColumn("HIERARCHY_UNIQUE_NAME"),
+ new MetadataColumn("HIERARCHY_GUID"),
+ new MetadataColumn("HIERARCHY_CAPTION"),
+ new MetadataColumn("DIMENSION_TYPE"),
+ new MetadataColumn("HIERARCHY_CARDINALITY"),
+ new MetadataColumn("DEFAULT_MEMBER"),
+ new MetadataColumn("ALL_MEMBER"),
+ new MetadataColumn("DESCRIPTION"),
+ new MetadataColumn("STRUCTURE"),
+ new MetadataColumn("IS_VIRTUAL"),
+ new MetadataColumn("IS_READWRITE"),
+ new MetadataColumn("DIMENSION_UNIQUE_SETTINGS"),
+ new MetadataColumn("DIMENSION_IS_VISIBLE"),
+ new MetadataColumn("HIERARCHY_ORDINAL"),
+ new MetadataColumn("DIMENSION_IS_SHARED"),
+ new MetadataColumn("PARENT_CHILD")),
+ MDSCHEMA_LEVELS(
+ new MetadataColumn("CATALOG_NAME"),
+ new MetadataColumn("SCHEMA_NAME"),
+ new MetadataColumn("CUBE_NAME"),
+ new MetadataColumn("DIMENSION_UNIQUE_NAME"),
+ new MetadataColumn("HIERARCHY_UNIQUE_NAME"),
+ new MetadataColumn("LEVEL_NAME"),
+ new MetadataColumn("LEVEL_UNIQUE_NAME"),
+ new MetadataColumn("LEVEL_GUID"),
+ new MetadataColumn("LEVEL_CAPTION"),
+ new MetadataColumn("LEVEL_NUMBER"),
+ new MetadataColumn("LEVEL_CARDINALITY"),
+ new MetadataColumn("LEVEL_TYPE"),
+ new MetadataColumn("CUSTOM_ROLLUP_SETTINGS"),
+ new MetadataColumn("LEVEL_UNIQUE_SETTINGS"),
+ new MetadataColumn("LEVEL_IS_VISIBLE"),
+ new MetadataColumn("DESCRIPTION")),
+ MDSCHEMA_MEASURES(
+ new MetadataColumn("CATALOG_NAME"),
+ new MetadataColumn("SCHEMA_NAME"),
+ new MetadataColumn("CUBE_NAME"),
+ new MetadataColumn("MEASURE_NAME"),
+ new MetadataColumn("MEASURE_UNIQUE_NAME"),
+ new MetadataColumn("MEASURE_CAPTION"),
+ new MetadataColumn("MEASURE_GUID"),
+ new MetadataColumn("MEASURE_AGGREGATOR"),
+ new MetadataColumn("DATA_TYPE"),
+ new MetadataColumn("MEASURE_IS_VISIBLE"),
+ new MetadataColumn("LEVELS_LIST"),
+ new MetadataColumn("DESCRIPTION")),
+ MDSCHEMA_MEMBERS(
+ new MetadataColumn("CATALOG_NAME"),
+ new MetadataColumn("SCHEMA_NAME"),
+ new MetadataColumn("CUBE_NAME"),
+ new MetadataColumn("DIMENSION_UNIQUE_NAME"),
+ new MetadataColumn("HIERARCHY_UNIQUE_NAME"),
+ new MetadataColumn("LEVEL_UNIQUE_NAME"),
+ new MetadataColumn("LEVEL_NUMBER"),
+ new MetadataColumn("MEMBER_ORDINAL"),
+ new MetadataColumn("MEMBER_NAME"),
+ new MetadataColumn("MEMBER_UNIQUE_NAME"),
+ new MetadataColumn("MEMBER_TYPE"),
+ new MetadataColumn("MEMBER_GUID"),
+ new MetadataColumn("MEMBER_CAPTION"),
+ new MetadataColumn("CHILDREN_CARDINALITY"),
+ new MetadataColumn("PARENT_LEVEL"),
+ new MetadataColumn("PARENT_UNIQUE_NAME"),
+ new MetadataColumn("PARENT_COUNT"),
+ new MetadataColumn("TREE_OP"),
+ new MetadataColumn("DEPTH")),
+ MDSCHEMA_PROPERTIES(
+ new MetadataColumn("CATALOG_NAME"),
+ new MetadataColumn("SCHEMA_NAME"),
+ new MetadataColumn("CUBE_NAME"),
+ new MetadataColumn("DIMENSION_UNIQUE_NAME"),
+ new MetadataColumn("HIERARCHY_UNIQUE_NAME"),
+ new MetadataColumn("LEVEL_UNIQUE_NAME"),
+ new MetadataColumn("MEMBER_UNIQUE_NAME"),
+ new MetadataColumn("PROPERTY_NAME"),
+ new MetadataColumn("PROPERTY_CAPTION"),
+ new MetadataColumn("PROPERTY_TYPE"),
+ new MetadataColumn("DATA_TYPE"),
+ new MetadataColumn("PROPERTY_CONTENT_TYPE"),
+ new MetadataColumn("DESCRIPTION")),
+ MDSCHEMA_SETS(
+ new MetadataColumn("CATALOG_NAME"),
+ new MetadataColumn("SCHEMA_NAME"),
+ new MetadataColumn("CUBE_NAME"),
+ new MetadataColumn("SET_NAME"),
+ new MetadataColumn("SCOPE"));
+
+ final List columns;
+
+ MetadataRequest(MetadataColumn... columns) {
+ if (name().equals("DBSCHEMA_CATALOGS")) {
+ // DatabaseMetaData.getCatalogs() is defined by JDBC not XMLA,
+ // so has just one column. Ignore the 4 columns from XMLA.
+ columns = new MetadataColumn[] {
+ new MetadataColumn("CATALOG_NAME", "TABLE_CAT")
+ };
+ } else if (name().equals("DBSCHEMA_SCHEMATA")) {
+ // DatabaseMetaData.getCatalogs() is defined by JDBC not XMLA,
+ // so has just one column. Ignore the 4 columns from XMLA.
+ columns = new MetadataColumn[] {
+ new MetadataColumn("SCHEMA_NAME", "TABLE_SCHEM"),
+ new MetadataColumn("CATALOG_NAME", "TABLE_CAT")
+ };
+ }
+ this.columns =
+ Collections.unmodifiableList(
+ Arrays.asList(columns));
+ }
+ }
+
+ private static final Pattern LOWERCASE_PATTERN = Pattern.compile(".*[a-z].*");
+
+ static class MetadataColumn {
+ final String name;
+ final String xmlaName;
+
+ MetadataColumn(String xmlaName, String name) {
+ this.xmlaName = xmlaName;
+ this.name = name;
+ }
+
+ MetadataColumn(String xmlaName) {
+ this.xmlaName = xmlaName;
+ String name = xmlaName;
+ if (LOWERCASE_PATTERN.matcher(name).matches()) {
+ name = Olap4jUtil.camelToUpper(name);
+ }
+ // VALUE is a SQL reserved word
+ if (name.equals("VALUE")) {
+ name = "PROPERTY_VALUE";
+ }
+ this.name = name;
+ }
+ }
+
+ private static class XmlaOlap4jMdxValidator implements MdxValidator {
+ private final OlapConnection connection;
+
+ XmlaOlap4jMdxValidator(OlapConnection connection) {
+ this.connection = connection;
+ }
+
+ public SelectNode validateSelect(SelectNode selectNode) throws OlapException {
+ StringWriter sw = new StringWriter();
+ selectNode.unparse(new ParseTreeWriter(new PrintWriter(sw)));
+ String mdx = sw.toString();
+ final XmlaOlap4jConnection olap4jConnection =
+ (XmlaOlap4jConnection) connection;
+ return selectNode;
+ }
+ }
}
// End XmlaOlap4jConnection.java
diff --git a/src/org/olap4j/driver/xmla/XmlaOlap4jCube.java b/src/org/olap4j/driver/xmla/XmlaOlap4jCube.java
new file mode 100644
index 0000000..515f224
--- /dev/null
+++ b/src/org/olap4j/driver/xmla/XmlaOlap4jCube.java
@@ -0,0 +1,376 @@
+/*
+// This software is subject to the terms of the Common Public License
+// Agreement, available at the following URL:
+// http://www.opensource.org/licenses/cpl.html.
+// Copyright (C) 2007-2007 Julian Hyde
+// All Rights Reserved.
+// You must accept the terms of that agreement to use this software.
+*/
+package org.olap4j.driver.xmla;
+
+import org.olap4j.OlapException;
+import org.olap4j.impl.*;
+import org.olap4j.mdx.IdentifierNode;
+import org.olap4j.metadata.*;
+
+import java.util.*;
+
+/**
+ * Implementation of {@link Cube}
+ * for XML/A providers.
+ *
+ * @author jhyde
+ * @version $Id: $
+ * @since Dec 4, 2007
+ */
+class XmlaOlap4jCube implements Cube, Named
+{
+ final XmlaOlap4jSchema olap4jSchema;
+ private final String name;
+ private final String description;
+
+ final NamedList dimensions =
+ new NamedListImpl();
+ final Map dimensionsByUname =
+ new HashMap();
+ private final NamedList hierarchies =
+ new NamedListImpl();
+ final Map hierarchiesByUname =
+ new HashMap();
+ final Map levelsByUname =
+ new HashMap();
+ private final NamedList measures =
+ new NamedListImpl();
+ private final NamedList namedSets =
+ new NamedListImpl();
+
+ XmlaOlap4jCube(
+ XmlaOlap4jSchema olap4jSchema,
+ String name,
+ String description) throws OlapException
+ {
+ assert olap4jSchema != null;
+ assert description != null;
+ assert name != null;
+ this.olap4jSchema = olap4jSchema;
+ this.name = name;
+ this.description = description;
+ final XmlaOlap4jConnection olap4jConnection =
+ olap4jSchema.olap4jCatalog.olap4jDatabaseMetaData.olap4jConnection;
+
+ final XmlaOlap4jConnection.Context context =
+ new XmlaOlap4jConnection.Context(this, null, null, null);
+
+ String[] restrictions = {
+ "CATALOG_NAME", olap4jSchema.olap4jCatalog.getName(),
+ "SCHEMA_NAME", olap4jSchema.getName(),
+ "CUBE_NAME", getName()
+ };
+ // populate dimensions (without their hierarchies at first)
+ olap4jConnection.populateList(
+ dimensions, context,
+ XmlaOlap4jConnection.MetadataRequest.MDSCHEMA_DIMENSIONS,
+ new XmlaOlap4jConnection.DimensionHandler(),
+ restrictions);
+ for (XmlaOlap4jDimension dimension : dimensions) {
+ dimensionsByUname.put(dimension.getUniqueName(), dimension);
+ }
+ // populate hierarchies (referencing dimensions)
+ olap4jConnection.populateList(
+ hierarchies, context,
+ XmlaOlap4jConnection.MetadataRequest.MDSCHEMA_HIERARCHIES,
+ new XmlaOlap4jConnection.HierarchyHandler(),
+ restrictions);
+ // now we have hierarchies, populate dimension->hierarchy and
+ // cube->hierarchy mappings
+ for (XmlaOlap4jHierarchy hierarchy : hierarchies) {
+ hierarchy.olap4jDimension.hierarchies.add(hierarchy);
+ hierarchiesByUname.put(hierarchy.getUniqueName(), hierarchy);
+ }
+ // populate levels (referencing hierarchies); use a temp list because
+ // we don't need a mapping from cube->level
+ NamedList levels =
+ new NamedListImpl();
+ olap4jConnection.populateList(
+ levels, context,
+ XmlaOlap4jConnection.MetadataRequest.MDSCHEMA_LEVELS,
+ new XmlaOlap4jConnection.LevelHandler(),
+ restrictions);
+ // now we have levels, populate hierarchy->level and cube->level
+ // mappings
+ for (XmlaOlap4jLevel level : levels) {
+ level.olap4jHierarchy.levels.add(level);
+ levelsByUname.put(level.getUniqueName(), level);
+ }
+ // populate measures
+ olap4jConnection.populateList(
+ measures, context,
+ XmlaOlap4jConnection.MetadataRequest.MDSCHEMA_MEASURES,
+ new XmlaOlap4jConnection.MeasureHandler(),
+ restrictions);
+ // populate named sets
+ olap4jConnection.populateList(
+ namedSets, context,
+ XmlaOlap4jConnection.MetadataRequest.MDSCHEMA_SETS,
+ new XmlaOlap4jConnection.NamedSetHandler(),
+ restrictions);
+ }
+
+ public Schema getSchema() {
+ return olap4jSchema;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getUniqueName() {
+ return "[" + name + "]";
+ }
+
+ public String getCaption(Locale locale) {
+ return name;
+ }
+
+ public String getDescription(Locale locale) {
+ return description;
+ }
+
+ public NamedList getDimensions() {
+ return Olap4jUtil.cast(dimensions);
+ }
+
+ public NamedList getHierarchies() {
+ return Olap4jUtil.cast(hierarchies);
+ }
+
+ public List getMeasures() {
+ return Olap4jUtil.cast(measures);
+ }
+
+ public NamedList getSets() {
+ return Olap4jUtil.cast(namedSets);
+ }
+
+ public Collection getSupportedLocales() {
+ return Collections.singletonList(Locale.getDefault());
+ }
+
+ public Member lookupMember(String... nameParts) throws OlapException {
+ List segmentList =
+ new ArrayList();
+ for (String namePart : nameParts) {
+ segmentList.add(new IdentifierNode.Segment(namePart));
+ }
+ return lookupMember(segmentList);
+ }
+
+ private Member lookupMember(
+ List segmentList) throws OlapException
+ {
+ if (true) {
+ StringBuilder buf = new StringBuilder();
+ for (IdentifierNode.Segment segment : segmentList) {
+ if (buf.length() > 0) {
+ buf.append('.');
+ }
+ buf.append(segment.toString());
+ }
+ final String uniqueName = buf.toString();
+ return lookupMemberByUniqueName(uniqueName);
+ } else {
+ final Hierarchy hierarchy =
+ getHierarchies().get(segmentList.get(0).getName());
+ final NamedList rootMembers = hierarchy.getRootMembers();
+ Member member = rootMembers.get(segmentList.get(1).getName());
+ int k = 1;
+ if (member == null) {
+ if (rootMembers.size() == 1
+ && rootMembers.get(0).isAll()) {
+ member = rootMembers.get(0);
+ ++k;
+ } else {
+ return null;
+ }
+ }
+ while (k < segmentList.size()) {
+
+ }
+ return member;
+ }
+ }
+
+ /**
+ * Looks up a member by its unique name.
+ *
+ *
Not part of public olap4j API.
+ *
+ * @param memberUniqueName Unique name of member
+ * @return Member, or null if not found
+ * @throws OlapException if error occurs
+ */
+ XmlaOlap4jMember lookupMemberByUniqueName(
+ String memberUniqueName)
+ throws OlapException
+ {
+ NamedList list =
+ new NamedListImpl();
+ lookupMembersByUniqueName(
+ EnumSet.of(Member.TreeOp.SELF), memberUniqueName, list);
+ switch (list.size()) {
+ case 0:
+ return null;
+ case 1:
+ return list.get(0);
+ default:
+ throw new IllegalArgumentException(
+ "more than one member with unique name '"
+ + memberUniqueName
+ + "'");
+ }
+ }
+
+ /**
+ * Looks a member by its unique name and returns members related by
+ * the specified tree-operations.
+ *
+ *
Not part of public olap4j API.
+ *
+ * @param memberUniqueName Unique name of member
+ *
+ * @param treeOps Collection of tree operations to travel relative to
+ * given member in order to create list of members
+ *
+ * @param list list to be populated with members related to the given
+ * member, or empty set if the member is not found
+ *
+ * @throws OlapException if error occurs
+ */
+ void lookupMembersByUniqueName(
+ Set treeOps,
+ String memberUniqueName,
+ List list) throws OlapException
+ {
+ final XmlaOlap4jConnection.Context context =
+ new XmlaOlap4jConnection.Context(this, null, null, null);
+ int treeOpMask = 0;
+ for (Member.TreeOp treeOp : treeOps) {
+ treeOpMask |= treeOp.xmlaOrdinal();
+ }
+ olap4jSchema.olap4jCatalog.olap4jDatabaseMetaData.olap4jConnection
+ .populateList(
+ list,
+ context,
+ XmlaOlap4jConnection.MetadataRequest.MDSCHEMA_MEMBERS,
+ new XmlaOlap4jConnection.MemberHandler(),
+ "CATALOG_NAME", olap4jSchema.olap4jCatalog.getName(),
+ "SCHEMA_NAME", olap4jSchema.getName(),
+ "CUBE_NAME", getName(),
+ "MEMBER_UNIQUE_NAME", memberUniqueName,
+ "TREE_OP", String.valueOf(treeOpMask));
+ }
+
+ /**
+ * Returns true if two objects are equal, or are both null.
+ *
+ * @param s First object
+ * @param t Second object
+ * @return whether objects are equal
+ */
+ static boolean equal(T s, T t) {
+ return (s == null) ? (t == null) : s.equals(t);
+ }
+
+ public List lookupMembers(
+ Set treeOps,
+ String... nameParts) throws OlapException
+ {
+ StringBuilder buf = new StringBuilder();
+ for (String namePart : nameParts) {
+ if (buf.length() > 0) {
+ buf.append('.');
+ }
+ buf.append(new IdentifierNode.Segment(namePart));
+ }
+ final String uniqueName = buf.toString();
+ final List list =
+ new ArrayList();
+ lookupMembersByUniqueName(treeOps, uniqueName, list);
+// Collections.sort(list, new MemberComparator());
+ return Olap4jUtil.cast(list);
+ }
+
+ /**
+ * Looks a member by its unique name and returns members related by
+ * the specified tree-operations.
+ *
+ *
Not part of public olap4j API.
+ *
+ * @param level Level
+ *
+ * @param list list to be populated with members related to the given level
+ *
+ * @throws OlapException if error occurs
+ */
+ void lookupLevelMembers(
+ XmlaOlap4jLevel level,
+ List list) throws OlapException
+ {
+ assert level.olap4jHierarchy.olap4jDimension.olap4jCube == this;
+ final XmlaOlap4jConnection.Context context =
+ new XmlaOlap4jConnection.Context(level);
+ olap4jSchema.olap4jCatalog.olap4jDatabaseMetaData.olap4jConnection
+ .populateList(
+ list,
+ context,
+ XmlaOlap4jConnection.MetadataRequest.MDSCHEMA_MEMBERS,
+ new XmlaOlap4jConnection.MemberHandler(),
+ "CATALOG_NAME", olap4jSchema.olap4jCatalog.getName(),
+ "SCHEMA_NAME", olap4jSchema.getName(),
+ "CUBE_NAME", getName(),
+ "DIMENSION_UNIQUE_NAME",
+ level.olap4jHierarchy.olap4jDimension.getUniqueName(),
+ "HIERARCHY_UNIQUE_NAME",
+ level.olap4jHierarchy.getUniqueName(),
+ "LEVEL_UNIQUE_NAME", level.getUniqueName());
+ }
+
+ // NOT USED
+ private static class MemberComparator
+ implements Comparator
+ {
+ public int compare(XmlaOlap4jMember m1, XmlaOlap4jMember m2) {
+ if (equal(m1, m2)) {
+ return 0;
+ }
+ while (true) {
+ int depth1 = m1.getDepth(),
+ depth2 = m2.getDepth();
+ if (depth1 < depth2) {
+ m2 = m2.getParentMember();
+ if (Olap4jUtil.equal(m1, m2)) {
+ return -1;
+ }
+ } else if (depth1 > depth2) {
+ m1 = m1.getParentMember();
+ if (equal(m1, m2)) {
+ return 1;
+ }
+ } else {
+ m1 = m1.getParentMember();
+ m2 = m2.getParentMember();
+ if (equal(m1, m2)) {
+ // The previous values of m1 and m2 are siblings.
+ // We do not have access to the ordering key, if
+ // we assume that (a) the siblings were returned in
+ // the correct order, and (b) the sort is stable,
+ // then the first member is the earlier one.
+ return -1;
+ }
+ }
+ }
+ }
+ }
+}
+
+// End XmlaOlap4jCube.java
diff --git a/src/org/olap4j/driver/xmla/XmlaOlap4jDatabaseMetaData.java b/src/org/olap4j/driver/xmla/XmlaOlap4jDatabaseMetaData.java
index 748aae9..0ee949e 100644
--- a/src/org/olap4j/driver/xmla/XmlaOlap4jDatabaseMetaData.java
+++ b/src/org/olap4j/driver/xmla/XmlaOlap4jDatabaseMetaData.java
@@ -8,22 +8,25 @@
*/
package org.olap4j.driver.xmla;
-import org.olap4j.OlapDatabaseMetaData;
-import org.olap4j.OlapConnection;
-import org.olap4j.OlapException;
-import org.olap4j.metadata.Catalog;
-import org.olap4j.metadata.NamedList;
-import org.olap4j.metadata.Member;
-import mondrian.olap.Util;
+import org.olap4j.*;
+import org.olap4j.impl.ArrayMap;
+import org.olap4j.impl.Olap4jUtil;
+import org.olap4j.metadata.*;
+import org.w3c.dom.Element;
-import java.sql.SQLException;
import java.sql.ResultSet;
-import java.util.Set;
+import java.sql.SQLException;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/**
* Implementation of {@link org.olap4j.OlapDatabaseMetaData}
* for XML/A providers.
*
+ *
This class has sub-classes which implement JDBC 3.0 and JDBC 4.0 APIs;
+ * it is instantiated using {@link Factory#newDatabaseMetaData}.
+ *
* @author jhyde
* @version $Id$
* @since May 23, 2007
@@ -31,15 +34,116 @@
abstract class XmlaOlap4jDatabaseMetaData implements OlapDatabaseMetaData {
final XmlaOlap4jConnection olap4jConnection;
+ private final NamedList catalogs;
+
XmlaOlap4jDatabaseMetaData(
XmlaOlap4jConnection olap4jConnection)
{
this.olap4jConnection = olap4jConnection;
+ this.catalogs =
+ new DeferredNamedListImpl(
+ XmlaOlap4jConnection.MetadataRequest.DBSCHEMA_CATALOGS,
+ new XmlaOlap4jConnection.Context(
+ olap4jConnection, this, null, null, null, null, null,
+ null),
+ new XmlaOlap4jConnection.CatalogHandler());
}
- // package-protected
+ // package-protected: not part of olap4j API
NamedList getCatalogObjects() {
- throw Util.needToImplement(this);
+ return Olap4jUtil.cast(catalogs);
+ }
+
+ /**
+ * Executes a metadata query and returns the result as a JDBC
+ * {@link ResultSet}.
+ *
+ * @param metadataRequest Name of the metadata request. Corresponds to the XMLA
+ * method name, e.g. "MDSCHEMA_CUBES"
+ *
+ * @param patternValues Array of alternating parameter name and value
+ * pairs. If the parameter value is null, it is ignored.
+ *
+ * @return Result set of metadata
+ *
+ * @throws org.olap4j.OlapException on error
+ */
+ private ResultSet getMetadata(
+ XmlaOlap4jConnection.MetadataRequest metadataRequest,
+ Object... patternValues) throws OlapException
+ {
+ assert patternValues.length % 2 == 0;
+ final XmlaOlap4jConnection.Context context =
+ new XmlaOlap4jConnection.Context(
+ olap4jConnection, null, null, null, null, null, null, null);
+ List patternValueList = new ArrayList();
+ Map predicateList = new ArrayMap();
+ for (int i = 0; i < patternValues.length; i += 2) {
+ String name = (String) patternValues[i];
+ Object value = patternValues[i + 1];
+ if (value == null) {
+ // ignore
+ } else if (value instanceof Wildcard) {
+ final Wildcard wildcard = (Wildcard) value;
+ if (wildcard.pattern.indexOf('%') < 0
+ && wildcard.pattern.indexOf('_') < 0) {
+ patternValueList.add(name);
+ patternValueList.add(wildcard.pattern);
+ } else {
+ String regexp =
+ Olap4jUtil.wildcardToRegexp(
+ Collections.singletonList(wildcard.pattern));
+ final Matcher matcher = Pattern.compile(regexp).matcher("");
+ predicateList.put(name, matcher);
+ }
+ } else {
+ patternValueList.add(name);
+ patternValueList.add((String) value);
+ }
+ }
+ String request =
+ olap4jConnection.generateRequest(
+ context,
+ metadataRequest,
+ patternValueList.toArray(
+ new String[patternValueList.size()]));
+ final Element root = olap4jConnection.xxx(request);
+ List> rowList = new ArrayList>();
+ rowLoop:
+ for (Element row : XmlaOlap4jUtil.childElements(root)) {
+ final ArrayList