/*
// This software is subject to the terms of the Eclipse Public License v1.0
// Agreement, available at the following URL:
// http://www.eclipse.org/legal/epl-v10.html.
// You must accept the terms of that agreement to use this software.
//
// Copyright (C) 2001-2005 Julian Hyde
// Copyright (C) 2005-2013 Pentaho and others
// All Rights Reserved.
*/
package mondrian.rolap;
import mondrian.calc.*;
import mondrian.calc.impl.DelegatingTupleList;
import mondrian.olap.*;
import mondrian.parser.MdxParserValidator;
import mondrian.resource.MondrianResource;
import mondrian.server.*;
import mondrian.spi.*;
import mondrian.spi.impl.JndiDataSourceResolver;
import mondrian.util.*;
import org.apache.log4j.Logger;
import org.eigenbase.util.property.StringProperty;
import org.olap4j.Scenario;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicInteger;
import javax.sql.DataSource;
/**
* A <code>RolapConnection</code> is a connection to a Mondrian OLAP Server.
*
* <p>Typically, you create a connection via
* {@link DriverManager#getConnection(String, mondrian.spi.CatalogLocator)}.
* {@link RolapConnectionProperties} describes allowable keywords.</p>
*
* @see RolapSchema
* @see DriverManager
* @author jhyde
* @since 2 October, 2002
*/
public class RolapConnection extends ConnectionBase {
private static final Logger LOGGER =
Logger.getLogger(RolapConnection.class);
private static final AtomicInteger ID_GENERATOR = new AtomicInteger();
private final MondrianServer server;
private final Util.PropertyList connectInfo;
/**
* Factory for JDBC connections to talk to the RDBMS. This factory will
* usually use a connection pool.
*/
private final DataSource dataSource;
private final String catalogUrl;
private final RolapSchema schema;
private SchemaReader schemaReader;
protected Role role;
private Locale locale = Locale.getDefault();
private Scenario scenario;
private boolean closed = false;
private static DataSourceResolver dataSourceResolver;
private final int id;
private final Statement internalStatement;
/**
* Creates a connection.
*
* @param server Server instance this connection belongs to
* @param connectInfo Connection properties; keywords are described in
* {@link RolapConnectionProperties}.
* @param dataSource JDBC data source
*/
public RolapConnection(
MondrianServer server,
Util.PropertyList connectInfo,
DataSource dataSource)
{
this(server, connectInfo, null, dataSource);
}
/**
* Creates a RolapConnection.
*
* <p>Only {@link RolapSchemaPool#get} calls this with
* schema != null (to create a schema's internal connection).
* Other uses retrieve a schema from the cache based upon
* the <code>Catalog</code> property.
*
* @param server Server instance this connection belongs to
* @param connectInfo Connection properties; keywords are described in
* {@link RolapConnectionProperties}.
* @param schema Schema for the connection. Must be null unless this is to
* be an internal connection.
* @param dataSource If not null an external DataSource to be used
* by Mondrian
*/
RolapConnection(
MondrianServer server,
Util.PropertyList connectInfo,
RolapSchema schema,
DataSource dataSource)
{
super();
assert server != null;
this.server = server;
this.id = ID_GENERATOR.getAndIncrement();
assert connectInfo != null;
String provider = connectInfo.get(
RolapConnectionProperties.Provider.name(), "mondrian");
Util.assertTrue(provider.equalsIgnoreCase("mondrian"));
this.connectInfo = connectInfo;
this.catalogUrl =
connectInfo.get(RolapConnectionProperties.Catalog.name());
final String jdbcUser =
connectInfo.get(RolapConnectionProperties.JdbcUser.name());
final String jdbcConnectString =
connectInfo.get(RolapConnectionProperties.Jdbc.name());
final String strDataSource =
connectInfo.get(RolapConnectionProperties.DataSource.name());
StringBuilder buf = new StringBuilder();
this.dataSource =
createDataSource(dataSource, connectInfo, buf);
Role role = null;
// Register this connection before we register its internal statement.
server.addConnection(this);
if (schema == null) {
// If RolapSchema.Pool.get were to call this with schema == null,
// we would loop.
Statement bootstrapStatement = createInternalStatement(false);
final Locus locus =
new Locus(
new Execution(bootstrapStatement, 0),
null,
"Initializing connection");
Locus.push(locus);
try {
if (dataSource == null) {
// If there is no external data source is passed in, we
// expect the properties Jdbc, JdbcUser, DataSource to be
// set, as they are used to generate the schema cache key.
final String connectionKey =
jdbcConnectString
+ getJdbcProperties(connectInfo).toString();
schema = RolapSchemaPool.instance().get(
catalogUrl,
connectionKey,
jdbcUser,
strDataSource,
connectInfo);
} else {
schema = RolapSchemaPool.instance().get(
catalogUrl,
dataSource,
connectInfo);
}
} finally {
Locus.pop(locus);
bootstrapStatement.close();
}
internalStatement =
schema.getInternalConnection().getInternalStatement();
String roleNameList =
connectInfo.get(RolapConnectionProperties.Role.name());
if (roleNameList != null) {
List<String> roleNames = Util.parseCommaList(roleNameList);
List<Role> roleList = new ArrayList<Role>();
for (String roleName : roleNames) {
final LockBox.Entry entry =
server.getLockBox().get(roleName);
Role role1;
if (entry != null) {
try {
role1 = (Role) entry.getValue();
} catch (ClassCastException e) {
role1 = null;
}
} else {
role1 = schema.lookupRole(roleName);
}
if (role1 == null) {
throw Util.newError(
"Role '" + roleName + "' not found");
}
roleList.add(role1);
}
switch (roleList.size()) {
case 0:
// If they specify 'Role=;', the list of names will be
// empty, and the effect will be as if they did specify
// Role at all.
role = null;
break;
case 1:
role = roleList.get(0);
break;
default:
role = RoleImpl.union(roleList);
break;
}
}
} else {
this.internalStatement = createInternalStatement(true);
// We are creating an internal connection. Now is a great time to
// make sure that the JDBC credentials are valid, for this
// connection and for external connections built on top of this.
Connection conn = null;
java.sql.Statement statement = null;
try {
conn = this.dataSource.getConnection();
Dialect dialect =
DialectManager.createDialect(this.dataSource, conn);
if (dialect.getDatabaseProduct()
== Dialect.DatabaseProduct.DERBY)
{
// Derby requires a little extra prodding to do the
// validation to detect an error.
statement = conn.createStatement();
statement.executeQuery("select * from bogustable");
}
} catch (SQLException e) {
if (e.getMessage().equals(
"Table/View 'BOGUSTABLE' does not exist."))
{
// Ignore. This exception comes from Derby when the
// connection is valid. If the connection were invalid, we
// would receive an error such as "Schema 'BOGUSUSER' does
// not exist"
} else {
throw Util.newError(
e,
"Error while creating SQL connection: " + buf);
}
} finally {
try {
if (statement != null) {
statement.close();
}
if (conn != null) {
conn.close();
}
} catch (SQLException e) {
// ignore
}
}
}
if (role == null) {
role = schema.getDefaultRole();
}
// Set the locale.
String localeString =
connectInfo.get(RolapConnectionProperties.Locale.name());
if (localeString != null) {
this.locale = Util.parseLocale(localeString);
assert locale != null;
}
this.schema = schema;
setRole(role);
}
@Override
protected void finalize() throws Throwable {
try {
super.finalize();
close();
} catch (Throwable t) {
LOGGER.info(
MondrianResource.instance()
.FinalizerErrorRolapConnection.baseMessage,
t);
}
}
/**
* Returns the identifier of this connection. Unique within the lifetime of
* this JVM.
*
* @return Identifier of this connection
*/
public int getId() {
return id;
}
protected Logger getLogger() {
return LOGGER;
}
/**
* Creates a JDBC data source from the JDBC credentials contained within a
* set of mondrian connection properties.
*
* <p>This method is package-level so that it can be called from the
* RolapConnectionTest unit test.
*
* @param dataSource Anonymous data source from user, or null
* @param connectInfo Mondrian connection properties
* @param buf Into which method writes a description of the JDBC credentials
* @return Data source
*/
static DataSource createDataSource(
DataSource dataSource,
Util.PropertyList connectInfo,
StringBuilder buf)
{
assert buf != null;
final String jdbcConnectString =
connectInfo.get(RolapConnectionProperties.Jdbc.name());
final String jdbcUser =
connectInfo.get(RolapConnectionProperties.JdbcUser.name());
final String jdbcPassword =
connectInfo.get(RolapConnectionProperties.JdbcPassword.name());
final String dataSourceName =
connectInfo.get(RolapConnectionProperties.DataSource.name());
if (dataSource != null) {
appendKeyValue(buf, "Anonymous data source", dataSource);
appendKeyValue(
buf, RolapConnectionProperties.JdbcUser.name(), jdbcUser);
appendKeyValue(
buf,
RolapConnectionProperties.JdbcPassword.name(),
jdbcPassword);
if (jdbcUser != null || jdbcPassword != null) {
dataSource =
new UserPasswordDataSource(
dataSource, jdbcUser, jdbcPassword);
}
return dataSource;
} else if (jdbcConnectString != null) {
// Get connection through own pooling datasource
appendKeyValue(
buf, RolapConnectionProperties.Jdbc.name(), jdbcConnectString);
appendKeyValue(
buf, RolapConnectionProperties.JdbcUser.name(), jdbcUser);
appendKeyValue(
buf,
RolapConnectionProperties.JdbcPassword.name(),
jdbcPassword);
String jdbcDrivers =
connectInfo.get(RolapConnectionProperties.JdbcDrivers.name());
if (jdbcDrivers != null) {
RolapUtil.loadDrivers(jdbcDrivers);
}
final String jdbcDriversProp =
MondrianProperties.instance().JdbcDrivers.get();
RolapUtil.loadDrivers(jdbcDriversProp);
Properties jdbcProperties = getJdbcProperties(connectInfo);
final Map<String, String> map = Util.toMap(jdbcProperties);
for (Map.Entry<String, String> entry : map.entrySet()) {
// FIXME ordering is non-deterministic
appendKeyValue(buf, entry.getKey(), entry.getValue());
}
if (jdbcUser != null) {
jdbcProperties.put("user", jdbcUser);
}
if (jdbcPassword != null) {
jdbcProperties.put("password", jdbcPassword);
}
// JDBC connections are dumb beasts, so we assume they're not
// pooled. Therefore the default is true.
final boolean poolNeeded =
connectInfo.get(
RolapConnectionProperties.PoolNeeded.name(),
"true").equalsIgnoreCase("true");
if (!poolNeeded) {
// Connection is already pooled; don't pool it again.
return new DriverManagerDataSource(
jdbcConnectString,
jdbcProperties);
}
if (jdbcConnectString.toLowerCase().indexOf("mysql") > -1) {
// mysql driver needs this autoReconnect parameter
jdbcProperties.setProperty("autoReconnect", "true");
}
return RolapConnectionPool.instance()
.getDriverManagerPoolingDataSource(
jdbcConnectString, jdbcProperties);
} else if (dataSourceName != null) {
appendKeyValue(
buf,
RolapConnectionProperties.DataSource.name(),
dataSourceName);
appendKeyValue(
buf,
RolapConnectionProperties.JdbcUser.name(),
jdbcUser);
appendKeyValue(
buf,
RolapConnectionProperties.JdbcPassword.name(),
jdbcPassword);
// Data sources are fairly smart, so we assume they look after
// their own pooling. Therefore the default is false.
final boolean poolNeeded =
connectInfo.get(
RolapConnectionProperties.PoolNeeded.name(),
"false").equalsIgnoreCase("true");
// Get connection from datasource.
DataSourceResolver dataSourceResolver = getDataSourceResolver();
try {
dataSource = dataSourceResolver.lookup(dataSourceName);
} catch (Exception e) {
throw Util.newInternal(
e,
"Error while looking up data source ("
+ dataSourceName + ")");
}
if (poolNeeded) {
dataSource =
RolapConnectionPool.instance()
.getDataSourcePoolingDataSource(
dataSource, dataSourceName, jdbcUser, jdbcPassword);
} else {
if (jdbcUser != null || jdbcPassword != null) {
dataSource =
new UserPasswordDataSource(
dataSource, jdbcUser, jdbcPassword);
}
}
return dataSource;
} else {
throw Util.newInternal(
"Connect string '" + connectInfo.toString()
+ "' must contain either '" + RolapConnectionProperties.Jdbc
+ "' or '" + RolapConnectionProperties.DataSource + "'");
}
}
/**
* Returns the instance of the {@link mondrian.spi.DataSourceResolver}
* plugin.
*
* @return data source resolver
*/
private static synchronized DataSourceResolver getDataSourceResolver() {
if (dataSourceResolver == null) {
final StringProperty property =
MondrianProperties.instance().DataSourceResolverClass;
final String className =
property.get(
JndiDataSourceResolver.class.getName());
try {
dataSourceResolver =
ClassResolver.INSTANCE.instantiateSafe(className);
} catch (ClassCastException e) {
throw Util.newInternal(
e,
"Plugin class specified by property "
+ property.getPath()
+ " must implement "
+ DataSourceResolver.class.getName());
}
}
return dataSourceResolver;
}
/**
* Appends "key=value" to a buffer, if value is not null.
*
* @param buf Buffer
* @param key Key
* @param value Value
*/
private static void appendKeyValue(
StringBuilder buf,
String key,
Object value)
{
if (value != null) {
if (buf.length() > 0) {
buf.append("; ");
}
buf.append(key).append('=').append(value);
}
}
/**
* Creates a {@link Properties} object containing all of the JDBC
* connection properties present in the
* {@link mondrian.olap.Util.PropertyList connectInfo}.
*
* @param connectInfo Connection properties
* @return The JDBC connection properties.
*/
private static Properties getJdbcProperties(Util.PropertyList connectInfo) {
Properties jdbcProperties = new Properties();
for (Pair<String, String> entry : connectInfo) {
if (entry.left.startsWith(
RolapConnectionProperties.JdbcPropertyPrefix))
{
jdbcProperties.put(
entry.left.substring(
RolapConnectionProperties.JdbcPropertyPrefix.length()),
entry.right);
}
}
return jdbcProperties;
}
public Util.PropertyList getConnectInfo() {
return connectInfo;
}
public void close() {
if (!closed) {
closed = true;
server.removeConnection(this);
}
if (internalStatement != null) {
internalStatement.close();
}
}
public RolapSchema getSchema() {
return schema;
}
public String getConnectString() {
return connectInfo.toString();
}
public String getCatalogName() {
return catalogUrl;
}
public Locale getLocale() {
return locale;
}
public void setLocale(Locale locale) {
if (locale == null) {
throw new IllegalArgumentException("locale must not be null");
}
this.locale = locale;
}
public SchemaReader getSchemaReader() {
return schemaReader;
}
public Object getProperty(String name) {
// Mask out the values of certain properties.
if (name.equals(RolapConnectionProperties.JdbcPassword.name())
|| name.equals(RolapConnectionProperties.CatalogContent.name()))
{
return "";
}
return connectInfo.get(name);
}
public CacheControl getCacheControl(PrintWriter pw) {
return getServer().getAggregationManager().getCacheControl(this, pw);
}
/**
* Executes a Query.
*
* @param query Query parse tree
*
* @throws ResourceLimitExceededException if some resource limit specified
* in the property file was exceeded
* @throws QueryCanceledException if query was canceled during execution
* @throws QueryTimeoutException if query exceeded timeout specified in
* the property file
*
* @deprecated Use {@link #execute(mondrian.server.Execution)}; this method
* will be removed in mondrian-4.0
*/
public Result execute(Query query) {
final Statement statement = query.getStatement();
Execution execution =
new Execution(statement, statement.getQueryTimeoutMillis());
return execute(execution);
}
/**
* Executes a statement.
*
* @param execution Execution context (includes statement, query)
*
* @throws ResourceLimitExceededException if some resource limit specified
* in the property file was exceeded
* @throws QueryCanceledException if query was canceled during execution
* @throws QueryTimeoutException if query exceeded timeout specified in
* the property file
*/
public Result execute(final Execution execution) {
execution.copyMDC();
return
server.getResultShepherd()
.shepherdExecution(
execution,
new Callable<Result>() {
public Result call() throws Exception {
return executeInternal(execution);
}
});
}
private Result executeInternal(final Execution execution) {
execution.setContextMap();
final Statement statement = execution.getMondrianStatement();
// Cleanup any previous executions still running
synchronized (statement) {
final Execution previousExecution =
statement.getCurrentExecution();
if (previousExecution != null) {
statement.end(previousExecution);
}
}
final Query query = statement.getQuery();
final MemoryMonitor.Listener listener = new MemoryMonitor.Listener() {
public void memoryUsageNotification(long used, long max) {
execution.setOutOfMemory(
"OutOfMemory used="
+ used
+ ", max="
+ max
+ " for connection: "
+ getConnectString());
}
};
MemoryMonitor mm = MemoryMonitorFactory.getMemoryMonitor();
final long currId = execution.getId();
try {
mm.addListener(listener);
// Check to see if we must punt
execution.checkCancelOrTimeout();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(Util.unparse(query));
}
if (RolapUtil.MDX_LOGGER.isDebugEnabled()) {
RolapUtil.MDX_LOGGER.debug(currId + ": " + Util.unparse(query));
}
final Locus locus = new Locus(execution, null, "Loading cells");
Locus.push(locus);
Result result;
try {
statement.start(execution);
((RolapCube) query.getCube()).clearCachedAggregations(true);
result = new RolapResult(execution, true);
int i = 0;
for (QueryAxis axis : query.getAxes()) {
if (axis.isNonEmpty()) {
result = new NonEmptyResult(result, execution, i);
}
++i;
}
} finally {
Locus.pop(locus);
((RolapCube) query.getCube()).clearCachedAggregations(true);
}
statement.end(execution);
return result;
} catch (ResultLimitExceededException e) {
// query has been punted
throw e;
} catch (Exception e) {
try {
if (!execution.isCancelOrTimeout()) {
statement.end(execution);
}
} catch (Exception e1) {
// We can safely ignore that cleanup exception.
// If an error is encountered here, it means that
// one was already encountered at statement.start()
// above and the exception we will throw after the
// cleanup is the same as the original one.
}
String queryString;
try {
queryString = Util.unparse(query);
} catch (Exception e1) {
queryString = "?";
}
throw Util.newError(
e,
"Error while executing query [" + queryString + "]");
} finally {
mm.removeListener(listener);
if (RolapUtil.MDX_LOGGER.isDebugEnabled()) {
final long elapsed = execution.getElapsedMillis();
RolapUtil.MDX_LOGGER.debug(
currId + ": exec: " + elapsed + " ms");
}
}
}
public void setRole(Role role) {
assert role != null;
this.role = role;
this.schemaReader = new RolapSchemaReader(role, schema);
}
public Role getRole() {
Util.assertPostcondition(role != null, "role != null");
return role;
}
public void setScenario(Scenario scenario) {
this.scenario = scenario;
}
public Scenario getScenario() {
return scenario;
}
/**
* Returns the server (mondrian instance) that this connection belongs to.
* Usually there is only one server instance in a given JVM.
*
* @return Server instance; never null
*/
public MondrianServer getServer() {
return server;
}
public QueryPart parseStatement(String query) {
Statement statement = createInternalStatement(false);
final Locus locus =
new Locus(
new Execution(statement, 0),
"Parse/validate MDX statement",
null);
Locus.push(locus);
try {
QueryPart queryPart =
parseStatement(statement, query, null, false);
if (queryPart instanceof Query) {
((Query) queryPart).setOwnStatement(true);
statement = null;
}
return queryPart;
} finally {
Locus.pop(locus);
if (statement != null) {
statement.close();
}
}
}
public Exp parseExpression(String expr) {
boolean debug = false;
if (getLogger().isDebugEnabled()) {
//debug = true;
getLogger().debug(
Util.nl
+ expr);
}
final Statement statement = getInternalStatement();
try {
MdxParserValidator parser = createParser();
final FunTable funTable = getSchema().getFunTable();
return parser.parseExpression(statement, expr, debug, funTable);
} catch (Throwable exception) {
throw MondrianResource.instance().FailedToParseQuery.ex(
expr,
exception);
}
}
public Statement getInternalStatement() {
if (internalStatement == null) {
return schema.getInternalConnection().getInternalStatement();
} else {
return internalStatement;
}
}
private Statement createInternalStatement(boolean reentrant) {
final Statement statement =
reentrant
? new ReentrantInternalStatement()
: new InternalStatement();
server.addStatement(statement);
return statement;
}
/**
* Implementation of {@link DataSource} which calls the good ol'
* {@link java.sql.DriverManager}.
*
* <p>Overrides {@link #hashCode()} and {@link #equals(Object)} so that
* {@link Dialect} objects can be cached more effectively.
*/
private static class DriverManagerDataSource implements DataSource {
private final String jdbcConnectString;
private PrintWriter logWriter;
private int loginTimeout;
private Properties jdbcProperties;
public DriverManagerDataSource(
String jdbcConnectString,
Properties properties)
{
this.jdbcConnectString = jdbcConnectString;
this.jdbcProperties = properties;
}
@Override
public int hashCode() {
int h = loginTimeout;
h = Util.hash(h, jdbcConnectString);
h = Util.hash(h, jdbcProperties);
return h;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof DriverManagerDataSource) {
DriverManagerDataSource
that = (DriverManagerDataSource) obj;
return this.loginTimeout == that.loginTimeout
&& this.jdbcConnectString.equals(that.jdbcConnectString)
&& this.jdbcProperties.equals(that.jdbcProperties);
}
return false;
}
public Connection getConnection() throws SQLException {
return new org.apache.commons.dbcp.DelegatingConnection(
java.sql.DriverManager.getConnection(
jdbcConnectString, jdbcProperties));
}
public Connection getConnection(String username, String password)
throws SQLException
{
if (jdbcProperties == null) {
return java.sql.DriverManager.getConnection(
jdbcConnectString, username, password);
} else {
Properties temp = (Properties)jdbcProperties.clone();
temp.put("user", username);
temp.put("password", password);
return java.sql.DriverManager.getConnection(
jdbcConnectString, temp);
}
}
public PrintWriter getLogWriter() throws SQLException {
return logWriter;
}
public void setLogWriter(PrintWriter out) throws SQLException {
logWriter = out;
}
public void setLoginTimeout(int seconds) throws SQLException {
loginTimeout = seconds;
}
public int getLoginTimeout() throws SQLException {
return loginTimeout;
}
public java.util.logging.Logger getParentLogger() {
return java.util.logging.Logger.getLogger("");
}
public <T> T unwrap(Class<T> iface) throws SQLException {
throw new SQLException("not a wrapper");
}
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
}
public DataSource getDataSource() {
return dataSource;
}
/**
* Helper method to allow olap4j wrappers to implement
* {@link org.olap4j.OlapConnection#createScenario()}.
*
* @return new Scenario
*/
public ScenarioImpl createScenario() {
final ScenarioImpl scenario = new ScenarioImpl();
scenario.register(schema);
return scenario;
}
/**
* A <code>NonEmptyResult</code> filters a result by removing empty rows
* on a particular axis.
*/
static class NonEmptyResult extends ResultBase {
final Result underlying;
private final int axis;
private final Map<Integer, Integer> map;
/** workspace. Synchronized access only. */
private final int[] pos;
/**
* Creates a NonEmptyResult.
*
* @param result Result set
* @param execution Execution context
* @param axis Which axis to make non-empty
*/
NonEmptyResult(Result result, Execution execution, int axis) {
super(execution, result.getAxes().clone());
this.underlying = result;
this.axis = axis;
this.map = new HashMap<Integer, Integer>();
int axisCount = underlying.getAxes().length;
this.pos = new int[axisCount];
this.slicerAxis = underlying.getSlicerAxis();
TupleList tupleList =
((RolapAxis) underlying.getAxes()[axis]).getTupleList();
final TupleList filteredTupleList;
if (!tupleList.isEmpty()
&& tupleList.get(0).get(0).getDimension().isHighCardinality())
{
filteredTupleList =
new DelegatingTupleList(
tupleList.getArity(),
new FilteredIterableList<List<Member>>(
tupleList,
new FilteredIterableList.Filter<List<Member>>() {
public boolean accept(final List<Member> p) {
return p.get(0) != null;
}
}
));
} else {
filteredTupleList =
TupleCollections.createList(tupleList.getArity());
int i = -1;
TupleCursor tupleCursor = tupleList.tupleCursor();
while (tupleCursor.forward()) {
++i;
if (! isEmpty(i, axis)) {
map.put(filteredTupleList.size(), i);
filteredTupleList.addCurrent(tupleCursor);
}
}
}
this.axes[axis] = new RolapAxis(filteredTupleList);
}
protected Logger getLogger() {
return LOGGER;
}
/**
* Returns true if all cells at a given offset on a given axis are
* empty. For example, in a 2x2x2 dataset, <code>isEmpty(1,0)</code>
* returns true if cells <code>{(1,0,0), (1,0,1), (1,1,0),
* (1,1,1)}</code> are all empty. As you can see, we hold the 0th
* coordinate fixed at 1, and vary all other coordinates over all
* possible values.
*/
private boolean isEmpty(int offset, int fixedAxis) {
int axisCount = getAxes().length;
pos[fixedAxis] = offset;
return isEmptyRecurse(fixedAxis, axisCount - 1);
}
private boolean isEmptyRecurse(int fixedAxis, int axis) {
if (axis < 0) {
RolapCell cell = (RolapCell) underlying.getCell(pos);
return cell.isNull();
} else if (axis == fixedAxis) {
return isEmptyRecurse(fixedAxis, axis - 1);
} else {
List<Position> positions = getAxes()[axis].getPositions();
final int positionCount = positions.size();
for (int i = 0; i < positionCount; i++) {
pos[axis] = i;
if (!isEmptyRecurse(fixedAxis, axis - 1)) {
return false;
}
}
return true;
}
}
// synchronized because we use 'pos'
public synchronized Cell getCell(int[] externalPos) {
try {
System.arraycopy(
externalPos, 0, this.pos, 0, externalPos.length);
int offset = externalPos[axis];
int mappedOffset = mapOffsetToUnderlying(offset);
this.pos[axis] = mappedOffset;
return underlying.getCell(this.pos);
} catch (NullPointerException npe) {
return underlying.getCell(externalPos);
}
}
private int mapOffsetToUnderlying(int offset) {
return map.get(offset);
}
public void close() {
underlying.close();
}
}
/**
* Data source that delegates all methods to an underlying data source.
*/
private static abstract class DelegatingDataSource implements DataSource {
protected final DataSource dataSource;
public DelegatingDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
public Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
public Connection getConnection(
String username,
String password)
throws SQLException
{
return dataSource.getConnection(username, password);
}
public PrintWriter getLogWriter() throws SQLException {
return dataSource.getLogWriter();
}
public void setLogWriter(PrintWriter out) throws SQLException {
dataSource.setLogWriter(out);
}
public void setLoginTimeout(int seconds) throws SQLException {
dataSource.setLoginTimeout(seconds);
}
public int getLoginTimeout() throws SQLException {
return dataSource.getLoginTimeout();
}
// JDBC 4.0 support (JDK 1.6 and higher)
public <T> T unwrap(Class<T> iface) throws SQLException {
if (Util.JdbcVersion >= 0x0400) {
// Do
// return dataSource.unwrap(iface);
// via reflection.
try {
Method method =
DataSource.class.getMethod("unwrap", Class.class);
return iface.cast(method.invoke(dataSource, iface));
} catch (IllegalAccessException e) {
throw Util.newInternal(e, "While invoking unwrap");
} catch (InvocationTargetException e) {
throw Util.newInternal(e, "While invoking unwrap");
} catch (NoSuchMethodException e) {
throw Util.newInternal(e, "While invoking unwrap");
}
} else {
if (iface.isInstance(dataSource)) {
return iface.cast(dataSource);
} else {
return null;
}
}
}
// JDBC 4.0 support (JDK 1.6 and higher)
public boolean isWrapperFor(Class<?> iface) throws SQLException {
if (Util.JdbcVersion >= 0x0400) {
// Do
// return dataSource.isWrapperFor(iface);
// via reflection.
try {
Method method =
DataSource.class.getMethod(
"isWrapperFor", boolean.class);
return (Boolean) method.invoke(dataSource, iface);
} catch (IllegalAccessException e) {
throw Util.newInternal(e, "While invoking isWrapperFor");
} catch (InvocationTargetException e) {
throw Util.newInternal(e, "While invoking isWrapperFor");
} catch (NoSuchMethodException e) {
throw Util.newInternal(e, "While invoking isWrapperFor");
}
} else {
return iface.isInstance(dataSource);
}
}
// JDBC 4.1 support (JDK 1.7 and higher)
public java.util.logging.Logger getParentLogger() {
if (Util.JdbcVersion >= 0x0401) {
// Do
// return dataSource.getParentLogger();
// via reflection.
try {
Method method =
DataSource.class.getMethod("getParentLogger");
return (java.util.logging.Logger) method.invoke(dataSource);
} catch (IllegalAccessException e) {
throw Util.newInternal(e, "While invoking getParentLogger");
} catch (InvocationTargetException e) {
throw Util.newInternal(e, "While invoking getParentLogger");
} catch (NoSuchMethodException e) {
throw Util.newInternal(e, "While invoking getParentLogger");
}
} else {
// Can't throw SQLFeatureNotSupportedException... it doesn't
// exist before JDBC 4.1.
throw new UnsupportedOperationException();
}
}
}
/**
* Data source that gets connections from an underlying data source but
* with different user name and password.
*/
private static class UserPasswordDataSource extends DelegatingDataSource {
private final String jdbcUser;
private final String jdbcPassword;
/**
* Creates a UserPasswordDataSource
*
* @param dataSource Underlying data source
* @param jdbcUser User name
* @param jdbcPassword Password
*/
public UserPasswordDataSource(
DataSource dataSource,
String jdbcUser,
String jdbcPassword)
{
super(dataSource);
this.jdbcUser = jdbcUser;
this.jdbcPassword = jdbcPassword;
}
public Connection getConnection() throws SQLException {
return dataSource.getConnection(jdbcUser, jdbcPassword);
}
}
/**
* <p>Implementation of {@link Statement} for use when you don't have an
* olap4j connection.</p>
*/
private class InternalStatement extends StatementImpl {
private boolean closed = false;
public void close() {
if (!closed) {
closed = true;
server.removeStatement(this);
}
}
public RolapConnection getMondrianConnection() {
return RolapConnection.this;
}
}
/**
* <p>A statement that can be used for all of the various internal
* operations, such as resolving MDX identifiers, that require a
* {@link Statement} and an {@link Execution}.
*
* <p>The statement needs to be reentrant because there are many such
* operations; several of these operations might be active at one time. We
* don't want to create a new statement for each, but just one internal
* statement for each connection. The statement shouldn't have a unique
* execution. For this reason, we don't use the inherited {@link #execution}
* field.</p>
*
* <p>But there is a drawback. If we can't find the unique execution, the
* statement cannot be canceled or time out. If you want that behavior
* from an internal statement, use the base class: create a new
* {@link InternalStatement} for each operation.</p>
*/
private class ReentrantInternalStatement extends InternalStatement {
@Override
public void start(Execution execution) {
// Unlike StatementImpl, there is not a unique execution. An
// internal statement can execute several at the same time. So,
// we don't set this.execution.
execution.start();
}
@Override
public void end(Execution execution) {
execution.end();
}
@Override
public void close() {
// do not close
}
}
}
// End RolapConnection.java