001    /*
002    // $Id: //open/mondrian/src/main/mondrian/rolap/RolapConnection.java#75 $
003    // This software is subject to the terms of the Common Public License
004    // Agreement, available at the following URL:
005    // http://www.opensource.org/licenses/cpl.html.
006    // Copyright (C) 2001-2002 Kana Software, Inc.
007    // Copyright (C) 2001-2008 Julian Hyde and others
008    // All Rights Reserved.
009    // You must accept the terms of that agreement to use this software.
010    //
011    // jhyde, 2 October, 2002
012    */
013    package mondrian.rolap;
014    
015    import java.io.PrintWriter;
016    import java.sql.Connection;
017    import java.sql.SQLException;
018    import java.sql.Statement;
019    import java.sql.ResultSet;
020    import java.util.*;
021    import java.lang.reflect.Method;
022    import java.lang.reflect.InvocationTargetException;
023    
024    import javax.naming.InitialContext;
025    import javax.naming.NamingException;
026    import javax.sql.DataSource;
027    
028    import mondrian.olap.CacheControl;
029    import mondrian.olap.Cell;
030    import mondrian.olap.ConnectionBase;
031    import mondrian.olap.Cube;
032    import mondrian.olap.DriverManager;
033    import mondrian.olap.MondrianProperties;
034    import mondrian.olap.Position;
035    import mondrian.olap.Query;
036    import mondrian.olap.QueryAxis;
037    import mondrian.olap.QueryCanceledException;
038    import mondrian.olap.QueryTimeoutException;
039    import mondrian.olap.ResourceLimitExceededException;
040    import mondrian.olap.Result;
041    import mondrian.olap.ResultBase;
042    import mondrian.olap.ResultLimitExceededException;
043    import mondrian.olap.Role;
044    import mondrian.olap.RoleImpl;
045    import mondrian.olap.Schema;
046    import mondrian.olap.SchemaReader;
047    import mondrian.olap.Util;
048    import mondrian.rolap.agg.AggregationManager;
049    import mondrian.rolap.sql.SqlQuery;
050    import mondrian.util.FilteredIterableList;
051    import mondrian.util.MemoryMonitor;
052    import mondrian.util.MemoryMonitorFactory;
053    import mondrian.util.Pair;
054    
055    import org.apache.commons.dbcp.ConnectionFactory;
056    import org.apache.commons.dbcp.DataSourceConnectionFactory;
057    import org.apache.commons.dbcp.DriverManagerConnectionFactory;
058    import org.apache.log4j.Logger;
059    
060    /**
061     * A <code>RolapConnection</code> is a connection to a Mondrian OLAP Server.
062     *
063     * <p>Typically, you create a connection via
064     * {@link DriverManager#getConnection(String, mondrian.spi.CatalogLocator)}.
065     * {@link RolapConnectionProperties} describes allowable keywords.</p>
066     *
067     * @see RolapSchema
068     * @see DriverManager
069     * @author jhyde
070     * @since 2 October, 2002
071     * @version $Id: //open/mondrian/src/main/mondrian/rolap/RolapConnection.java#75 $
072     */
073    public class RolapConnection extends ConnectionBase {
074        private static final Logger LOGGER = Logger.getLogger(RolapConnection.class);
075    
076        private final Util.PropertyList connectInfo;
077    
078        // used for MDX logging, allows for a MDX Statement UID
079        private static long executeCount = 0;
080    
081        /**
082         * Factory for JDBC connections to talk to the RDBMS. This factory will
083         * usually use a connection pool.
084         */
085        private final DataSource dataSource;
086        private final String catalogUrl;
087        private final RolapSchema schema;
088        private SchemaReader schemaReader;
089        protected Role role;
090        private Locale locale = Locale.US;
091    
092        /**
093         * Creates a connection.
094         *
095         * @param connectInfo Connection properties; keywords are described in
096         *   {@link RolapConnectionProperties}.
097         */
098        public RolapConnection(Util.PropertyList connectInfo) {
099            this(connectInfo, null, null);
100        }
101    
102        /**
103         * Creates a connection.
104         *
105         * @param connectInfo Connection properties; keywords are described in
106         *   {@link RolapConnectionProperties}.
107         */
108        public RolapConnection(Util.PropertyList connectInfo, DataSource dataSource) {
109            this(connectInfo, null, dataSource);
110        }
111    
112        /**
113         * Creates a RolapConnection.
114         *
115         * <p>Only {@link mondrian.rolap.RolapSchema.Pool#get} calls this with schema != null (to
116         * create a schema's internal connection). Other uses retrieve a schema
117         * from the cache based upon the <code>Catalog</code> property.
118         *
119         * @param connectInfo Connection properties; keywords are described in
120         *   {@link RolapConnectionProperties}.
121         * @param schema Schema for the connection. Must be null unless this is to
122         *   be an internal connection.
123         * @pre connectInfo != null
124         */
125        RolapConnection(Util.PropertyList connectInfo, RolapSchema schema) {
126            this(connectInfo, schema, null);
127        }
128    
129        /**
130         * Creates a RolapConnection.
131         *
132         * <p>Only {@link mondrian.rolap.RolapSchema.Pool#get} calls this with
133         * schema != null (to create a schema's internal connection).
134         * Other uses retrieve a schema from the cache based upon
135         * the <code>Catalog</code> property.
136         *
137         * @param connectInfo Connection properties; keywords are described in
138         *   {@link RolapConnectionProperties}.
139         * @param schema Schema for the connection. Must be null unless this is to
140         *   be an internal connection.
141         * @param dataSource If not null an external DataSource to be used
142         *        by Mondrian
143         * @pre connectInfo != null
144         */
145        RolapConnection(
146            Util.PropertyList connectInfo,
147            RolapSchema schema,
148            DataSource dataSource)
149        {
150            super();
151    
152            String provider = connectInfo.get(
153                RolapConnectionProperties.Provider.name(), "mondrian");
154            Util.assertTrue(provider.equalsIgnoreCase("mondrian"));
155            this.connectInfo = connectInfo;
156            this.catalogUrl =
157                connectInfo.get(RolapConnectionProperties.Catalog.name());
158            final String jdbcUser =
159                connectInfo.get(RolapConnectionProperties.JdbcUser.name());
160            final String jdbcConnectString =
161                connectInfo.get(RolapConnectionProperties.Jdbc.name());
162            final String strDataSource =
163                connectInfo.get(RolapConnectionProperties.DataSource.name());
164            StringBuilder buf = new StringBuilder();
165            this.dataSource =
166                createDataSource(dataSource, connectInfo, buf);
167            Role role = null;
168            if (schema == null) {
169                // If RolapSchema.Pool.get were to call this with schema == null,
170                // we would loop.
171                if (dataSource == null) {
172                    // If there is no external data source is passed in,
173                    // we expect the properties Jdbc, JdbcUser, DataSource to be set,
174                    // as they are used to generate the schema cache key.
175                    final String connectionKey = jdbcConnectString +
176                        getJdbcProperties(connectInfo).toString();
177    
178                    schema = RolapSchema.Pool.instance().get(
179                        catalogUrl,
180                        connectionKey,
181                        jdbcUser,
182                        strDataSource,
183                        connectInfo);
184                } else {
185                    schema = RolapSchema.Pool.instance().get(
186                        catalogUrl,
187                        dataSource,
188                        connectInfo);
189                }
190                String roleNameList =
191                    connectInfo.get(RolapConnectionProperties.Role.name());
192                if (roleNameList != null) {
193                    List<String> roleNames = Util.parseCommaList(roleNameList);
194                    List<Role> roleList = new ArrayList<Role>();
195                    for (String roleName : roleNames) {
196                        Role role1 = schema.lookupRole(roleName);
197                        if (role1 == null) {
198                            throw Util.newError("Role '" + roleName + "' not found");
199                        }
200                        roleList.add(role1);
201                    }
202                    switch (roleList.size()) {
203                    case 0:
204                        // If they specify 'Role=;', the list of names will be
205                        // empty, and the effect will be as if they did specify
206                        // Role at all.
207                        role = null;
208                        break;
209                    case 1:
210                        role = roleList.get(0);
211                        break;
212                    default:
213                        role = RoleImpl.union(roleList);
214                        break;
215                    }
216                }
217            } else {
218                // We are creating an internal connection. Now is a great time to
219                // make sure that the JDBC credentials are valid, for this
220                // connection and for external connections built on top of this.
221                Connection conn = null;
222                Statement statement = null;
223                try {
224                    conn = this.dataSource.getConnection();
225                    SqlQuery.Dialect dialect =
226                        SqlQuery.Dialect.create(conn.getMetaData());
227                    if (dialect.isDerby()) {
228                        // Derby requires a little extra prodding to do the
229                        // validation to detect an error.
230                        statement = conn.createStatement();
231                        statement.executeQuery("select * from bogustable");
232                    }
233                } catch (SQLException e) {
234                    if (e.getMessage().equals("Table/View 'BOGUSTABLE' does not exist.")) {
235                        // Ignore. This exception comes from Derby when the
236                        // connection is valid. If the connection were invalid, we
237                        // would receive an error such as "Schema 'BOGUSUSER' does
238                        // not exist"
239                    } else {
240                        final String message =
241                            "Error while creating SQL connection: " + buf.toString();
242                        throw Util.newError(e, message);
243                    }
244                } finally {
245                    try {
246                        if (statement != null) {
247                            statement.close();
248                        }
249                        if (conn != null) {
250                            conn.close();
251                        }
252                    } catch (SQLException e) {
253                        // ignore
254                    }
255                }
256            }
257    
258            if (role == null) {
259                role = schema.getDefaultRole();
260            }
261    
262            // Set the locale.
263            String localeString =
264                connectInfo.get(RolapConnectionProperties.Locale.name());
265            if (localeString != null) {
266                String[] strings = localeString.split("_");
267                switch (strings.length) {
268                case 1:
269                    this.locale = new Locale(strings[0]);
270                    break;
271                case 2:
272                    this.locale = new Locale(strings[0], strings[1]);
273                    break;
274                case 3:
275                    this.locale = new Locale(strings[0], strings[1], strings[2]);
276                    break;
277                default:
278                    throw Util.newInternal("bad locale string '" + localeString + "'");
279                }
280            }
281    
282            this.schema = schema;
283            setRole(role);
284        }
285    
286        protected Logger getLogger() {
287            return LOGGER;
288        }
289    
290        /**
291         * Creates a JDBC data source from the JDBC credentials contained within a
292         * set of mondrian connection properties.
293         *
294         * <p>This method is package-level so that it can be called from the
295         * RolapConnectionTest unit test.
296         *
297         * @param dataSource Anonymous data source from user, or null
298         * @param connectInfo Mondrian connection properties
299         * @param buf Into which method writes a description of the JDBC credentials
300         * @return Data source
301         */
302        static DataSource createDataSource(
303            DataSource dataSource,
304            Util.PropertyList connectInfo,
305            StringBuilder buf)
306        {
307            assert buf != null;
308            final String jdbcConnectString =
309                connectInfo.get(RolapConnectionProperties.Jdbc.name());
310            final String jdbcUser =
311                connectInfo.get(RolapConnectionProperties.JdbcUser.name());
312            final String jdbcPassword =
313                connectInfo.get(RolapConnectionProperties.JdbcPassword.name());
314            final String dataSourceName =
315                connectInfo.get(RolapConnectionProperties.DataSource.name());
316    
317            if (dataSource != null) {
318                appendKeyValue(buf, "Anonymous data source", dataSource);
319                appendKeyValue(
320                    buf, RolapConnectionProperties.JdbcUser.name(), jdbcUser);
321                appendKeyValue(
322                    buf, RolapConnectionProperties.JdbcPassword.name(), jdbcPassword);
323                if (jdbcUser != null || jdbcPassword != null) {
324                    dataSource =
325                        new UserPasswordDataSource(
326                            dataSource, jdbcUser, jdbcPassword);
327                }
328                return dataSource;
329    
330            } else if (jdbcConnectString != null) {
331                // Get connection through own pooling datasource
332                appendKeyValue(
333                    buf, RolapConnectionProperties.Jdbc.name(), jdbcConnectString);
334                appendKeyValue(
335                    buf, RolapConnectionProperties.JdbcUser.name(), jdbcUser);
336                appendKeyValue(
337                    buf, RolapConnectionProperties.JdbcPassword.name(), jdbcPassword);
338                String jdbcDrivers =
339                    connectInfo.get(RolapConnectionProperties.JdbcDrivers.name());
340                if (jdbcDrivers != null) {
341                    RolapUtil.loadDrivers(jdbcDrivers);
342                }
343                final String jdbcDriversProp =
344                        MondrianProperties.instance().JdbcDrivers.get();
345                RolapUtil.loadDrivers(jdbcDriversProp);
346    
347                Properties jdbcProperties = getJdbcProperties(connectInfo);
348                for (Map.Entry<Object, Object> entry : jdbcProperties.entrySet()) {
349                    // FIXME ordering is non-deterministic
350                    appendKeyValue(buf, (String) entry.getKey(), entry.getValue());
351                }
352                String propertyString = jdbcProperties.toString();
353                if (jdbcUser != null) {
354                    jdbcProperties.put("user", jdbcUser);
355                }
356                if (jdbcPassword != null) {
357                    jdbcProperties.put("password", jdbcPassword);
358                }
359    
360                // JDBC connections are dumb beasts, so we assume they're not
361                // pooled. Therefore the default is true.
362                final boolean poolNeeded =
363                    connectInfo.get(
364                        RolapConnectionProperties.PoolNeeded.name(),
365                        "true").equalsIgnoreCase("true");
366    
367                if (!poolNeeded) {
368                    // Connection is already pooled; don't pool it again.
369                    return new DriverManagerDataSource(
370                        jdbcConnectString,
371                        jdbcProperties);
372                }
373    
374                if (jdbcConnectString.toLowerCase().indexOf("mysql") > -1) {
375                    // mysql driver needs this autoReconnect parameter
376                    jdbcProperties.setProperty("autoReconnect", "true");
377                }
378                // use the DriverManagerConnectionFactory to create connections
379                ConnectionFactory connectionFactory =
380                    new DriverManagerConnectionFactory(
381                        jdbcConnectString,
382                        jdbcProperties);
383                try {
384                    return RolapConnectionPool.instance().getPoolingDataSource(
385                        jdbcConnectString + propertyString,
386                        connectionFactory);
387                } catch (Throwable e) {
388                    throw Util.newInternal(
389                        e,
390                        "Error while creating connection pool (with URI " +
391                            jdbcConnectString + ")");
392                }
393    
394            } else if (dataSourceName != null) {
395                appendKeyValue(
396                    buf, RolapConnectionProperties.DataSource.name(), dataSourceName);
397                appendKeyValue(
398                    buf, RolapConnectionProperties.JdbcUser.name(), jdbcUser);
399                appendKeyValue(
400                    buf, RolapConnectionProperties.JdbcPassword.name(), jdbcPassword);
401    
402                // Data sources are fairly smart, so we assume they look after
403                // their own pooling. Therefore the default is false.
404                final boolean poolNeeded =
405                    connectInfo.get(
406                        RolapConnectionProperties.PoolNeeded.name(),
407                        "false").equalsIgnoreCase("true");
408    
409                // Get connection from datasource.
410                try {
411                    dataSource =
412                        (DataSource) new InitialContext().lookup(dataSourceName);
413                } catch (NamingException e) {
414                    throw Util.newInternal(
415                        e,
416                        "Error while looking up data source (" +
417                            dataSourceName + ")");
418                }
419                if (poolNeeded) {
420                    ConnectionFactory connectionFactory;
421                    if (jdbcUser != null || jdbcPassword != null) {
422                        connectionFactory =
423                            new DataSourceConnectionFactory(
424                                dataSource, jdbcUser, jdbcPassword);
425                    } else {
426                        connectionFactory =
427                            new DataSourceConnectionFactory(dataSource);
428                    }
429                    try {
430                        dataSource =
431                            RolapConnectionPool.instance().getPoolingDataSource(
432                                dataSourceName,
433                                connectionFactory);
434                    } catch (Exception e) {
435                        throw Util.newInternal(
436                            e,
437                            "Error while creating connection pool (with URI " +
438                                dataSourceName + ")");
439                    }
440                } else {
441                    if (jdbcUser != null || jdbcPassword != null) {
442                        dataSource =
443                            new UserPasswordDataSource(
444                                dataSource, jdbcUser, jdbcPassword);
445                    }
446                }
447                return dataSource;
448            } else {
449                throw Util.newInternal(
450                    "Connect string '" + connectInfo.toString() +
451                        "' must contain either '" + RolapConnectionProperties.Jdbc +
452                        "' or '" + RolapConnectionProperties.DataSource + "'");
453            }
454        }
455    
456        /**
457         * Appends "key=value" to a buffer, if value is not null.
458         *
459         * @param buf Buffer
460         * @param key Key
461         * @param value Value
462         */
463        private static void appendKeyValue(
464            StringBuilder buf,
465            String key,
466            Object value)
467        {
468            if (value != null) {
469                if (buf.length() > 0) {
470                    buf.append("; ");
471                }
472                buf.append(key).append('=').append(value);
473            }
474        }
475    
476        /**
477         * Creates a {@link Properties} object containing all of the JDBC
478         * connection properties present in the
479         * {@link mondrian.olap.Util.PropertyList connectInfo}.
480         *
481         * @param connectInfo Connection properties
482         * @return The JDBC connection properties.
483         */
484        private static Properties getJdbcProperties(Util.PropertyList connectInfo) {
485            Properties jdbcProperties = new Properties();
486            for (Pair<String,String> entry : connectInfo) {
487                if (entry.left.startsWith(
488                    RolapConnectionProperties.JdbcPropertyPrefix))
489                {
490                    jdbcProperties.put(
491                        entry.left.substring(
492                            RolapConnectionProperties.JdbcPropertyPrefix.length()),
493                        entry.right);
494                }
495            }
496            return jdbcProperties;
497        }
498    
499        public Util.PropertyList getConnectInfo() {
500            return connectInfo;
501        }
502    
503        public void close() {
504        }
505    
506        public Schema getSchema() {
507            return schema;
508        }
509    
510        public String getConnectString() {
511            return connectInfo.toString();
512        }
513    
514        public String getCatalogName() {
515            return catalogUrl;
516        }
517    
518        public Locale getLocale() {
519            return locale;
520        }
521    
522        public void setLocale(Locale locale) {
523            this.locale = locale;
524        }
525    
526        public SchemaReader getSchemaReader() {
527            return schemaReader;
528        }
529    
530        public Object getProperty(String name) {
531            // Mask out the values of certain properties.
532            if (name.equals(RolapConnectionProperties.JdbcPassword.name()) ||
533                name.equals(RolapConnectionProperties.CatalogContent.name())) {
534                return "";
535            }
536            return connectInfo.get(name);
537        }
538    
539        public CacheControl getCacheControl(PrintWriter pw) {
540            return AggregationManager.instance().getCacheControl(pw);
541        }
542    
543        /**
544         * Executes a Query.
545         *
546         * @throws ResourceLimitExceededException if some resource limit specified in the
547         * property file was exceeded
548         * @throws QueryCanceledException if query was canceled during execution
549         * @throws QueryTimeoutException if query exceeded timeout specified in
550         * the property file
551         */
552        public Result execute(Query query) {
553            class Listener implements MemoryMonitor.Listener {
554                private final Query query;
555                Listener(final Query query) {
556                    this.query = query;
557                }
558                public void memoryUsageNotification(long used, long max) {
559                    StringBuilder buf = new StringBuilder(200);
560                    buf.append("OutOfMemory used=");
561                    buf.append(used);
562                    buf.append(", max=");
563                    buf.append(max);
564                    buf.append(" for connection: ");
565                    buf.append(getConnectString());
566                    // Call ConnectionBase method which has access to
567                    // Query methods.
568                    RolapConnection.memoryUsageNotification(query, buf.toString());
569                }
570            }
571            Listener listener = new Listener(query);
572            MemoryMonitor mm = MemoryMonitorFactory.getMemoryMonitor();
573            long currId = -1;
574            try {
575                mm.addListener(listener);
576                // Check to see if we must punt
577                query.checkCancelOrTimeout();
578    
579                if (LOGGER.isDebugEnabled()) {
580                    LOGGER.debug(Util.unparse(query));
581                }
582    
583                if (RolapUtil.MDX_LOGGER.isDebugEnabled()) {
584                    currId = executeCount++;
585                    RolapUtil.MDX_LOGGER.debug(currId + ": " + Util.unparse(query));
586                }
587    
588                query.setQueryStartTime();
589                Result result = new RolapResult(query, true);
590                for (int i = 0; i < query.axes.length; i++) {
591                    QueryAxis axis = query.axes[i];
592                    if (axis.isNonEmpty()) {
593                        result = new NonEmptyResult(result, query, i);
594                    }
595                }
596                /* It will not work with HighCardinality.
597                if (LOGGER.isDebugEnabled()) {
598                    StringWriter sw = new StringWriter();
599                    PrintWriter pw = new PrintWriter(sw);
600                    result.print(pw);
601                    pw.flush();
602                    LOGGER.debug(sw.toString());
603                }
604                */
605                query.setQueryEndExecution();
606                return result;
607    
608            } catch (ResultLimitExceededException e) {
609                // query has been punted
610                throw e;
611            } catch (Exception e) {
612                String queryString;
613                query.setQueryEndExecution();
614                try {
615                    queryString = Util.unparse(query);
616                } catch (Exception e1) {
617                    queryString = "?";
618                }
619                throw Util.newError(e, "Error while executing query [" +
620                        queryString + "]");
621            } finally {
622                mm.removeListener(listener);
623                if (RolapUtil.MDX_LOGGER.isDebugEnabled()) {
624                    RolapUtil.MDX_LOGGER.debug(currId + ": exec: " +
625                        (System.currentTimeMillis() - query.getQueryStartTime()) +
626                        " ms");
627                }
628            }
629        }
630    
631        public void setRole(Role role) {
632            assert role != null;
633    
634            this.role = role;
635            this.schemaReader = new RolapSchemaReader(role, schema) {
636                public Cube getCube() {
637                    throw new UnsupportedOperationException();
638                }
639            };
640        }
641    
642        public Role getRole() {
643            Util.assertPostcondition(role != null, "role != null");
644    
645            return role;
646        }
647    
648        /**
649         * Implementation of {@link DataSource} which calls the good ol'
650         * {@link java.sql.DriverManager}.
651         */
652        private static class DriverManagerDataSource implements DataSource {
653            private final String jdbcConnectString;
654            private PrintWriter logWriter;
655            private int loginTimeout;
656            private Properties jdbcProperties;
657    
658            public DriverManagerDataSource(
659                String jdbcConnectString,
660                Properties properties)
661            {
662                this.jdbcConnectString = jdbcConnectString;
663                this.jdbcProperties = properties;
664            }
665    
666            public Connection getConnection() throws SQLException {
667                return new org.apache.commons.dbcp.DelegatingConnection(
668                    java.sql.DriverManager.getConnection(
669                        jdbcConnectString, jdbcProperties));
670            }
671    
672            public Connection getConnection(String username, String password)
673                    throws SQLException {
674                if (jdbcProperties == null) {
675                    return java.sql.DriverManager.getConnection(jdbcConnectString,
676                            username, password);
677                } else {
678                    Properties temp = (Properties)jdbcProperties.clone();
679                    temp.put("user", username);
680                    temp.put("password", password);
681                    return java.sql.DriverManager.getConnection(jdbcConnectString, temp);
682                }
683            }
684    
685            public PrintWriter getLogWriter() throws SQLException {
686                return logWriter;
687            }
688    
689            public void setLogWriter(PrintWriter out) throws SQLException {
690                logWriter = out;
691            }
692    
693            public void setLoginTimeout(int seconds) throws SQLException {
694                loginTimeout = seconds;
695            }
696    
697            public int getLoginTimeout() throws SQLException {
698                return loginTimeout;
699            }
700    
701            public <T> T unwrap(Class<T> iface) throws SQLException {
702                throw new SQLException("not a wrapper");
703            }
704    
705            public boolean isWrapperFor(Class<?> iface) throws SQLException {
706                return false;
707            }
708        }
709    
710        public DataSource getDataSource() {
711            return dataSource;
712        }
713    
714        /**
715         * A <code>NonEmptyResult</code> filters a result by removing empty rows
716         * on a particular axis.
717         */
718        static class NonEmptyResult extends ResultBase {
719    
720            final Result underlying;
721            private final int axis;
722            private final Map<Integer, Integer> map;
723            /** workspace. Synchronized access only. */
724            private final int[] pos;
725    
726            NonEmptyResult(Result result, Query query, int axis) {
727                super(query, result.getAxes().clone());
728    
729                this.underlying = result;
730                this.axis = axis;
731                this.map = new HashMap<Integer, Integer>();
732                int axisCount = underlying.getAxes().length;
733                this.pos = new int[axisCount];
734                this.slicerAxis = underlying.getSlicerAxis();
735                List<Position> positions = underlying.getAxes()[axis].getPositions();
736    
737                final List<Position> positionsList;
738                try {
739                    if (positions.get(0).get(0).getDimension().isHighCardinality()) {
740                        positionsList =
741                            new FilteredIterableList<Position>(
742                                positions,
743                                new FilteredIterableList.Filter<Position>() {
744                                    public boolean accept(final Position p) {
745                                        return p.get(0) != null;
746                                    }
747                                }
748                        );
749                    } else {
750                        positionsList = new ArrayList<Position>();
751                        int i = -1;
752                        for (Position position : positions) {
753                            ++i;
754                            if (! isEmpty(i, axis)) {
755                                map.put(positionsList.size(), i);
756                                positionsList.add(position);
757                            }
758                        }
759                    }
760                    this.axes[axis] = new RolapAxis.PositionList(positionsList);
761                } catch (IndexOutOfBoundsException ioobe) {
762                    // No elements.
763                    this.axes[axis] =
764                        new RolapAxis.PositionList(
765                                new ArrayList<Position>());
766                }
767            }
768    
769            protected Logger getLogger() {
770                return LOGGER;
771            }
772    
773            /**
774             * Returns true if all cells at a given offset on a given axis are
775             * empty. For example, in a 2x2x2 dataset, <code>isEmpty(1,0)</code>
776             * returns true if cells <code>{(1,0,0), (1,0,1), (1,1,0),
777             * (1,1,1)}</code> are all empty. As you can see, we hold the 0th
778             * coordinate fixed at 1, and vary all other coordinates over all
779             * possible values.
780             */
781            private boolean isEmpty(int offset, int fixedAxis) {
782                int axisCount = getAxes().length;
783                pos[fixedAxis] = offset;
784                return isEmptyRecurse(fixedAxis, axisCount - 1);
785            }
786    
787            private boolean isEmptyRecurse(int fixedAxis, int axis) {
788                if (axis < 0) {
789                    RolapCell cell = (RolapCell) underlying.getCell(pos);
790                    return cell.isNull();
791                } else if (axis == fixedAxis) {
792                    return isEmptyRecurse(fixedAxis, axis - 1);
793                } else {
794                    List<Position> positions = getAxes()[axis].getPositions();
795                    int i = 0;
796                    for (Position position : positions) {
797                        pos[axis] = i;
798                        if (!isEmptyRecurse(fixedAxis, axis - 1)) {
799                            return false;
800                        }
801                        i++;
802                    }
803                    return true;
804                }
805            }
806    
807            // synchronized because we use 'pos'
808            public synchronized Cell getCell(int[] externalPos) {
809                try {
810                    System.arraycopy(externalPos, 0, this.pos, 0, externalPos.length);
811                    int offset = externalPos[axis];
812                    int mappedOffset = mapOffsetToUnderlying(offset);
813                    this.pos[axis] = mappedOffset;
814                    return underlying.getCell(this.pos);
815                } catch (NullPointerException npe) {
816                    System.out.println("RolapConnection:619 please, review for "
817                            + "high cardinality...");
818                    return underlying.getCell(externalPos);
819                }
820            }
821    
822            private int mapOffsetToUnderlying(int offset) {
823                return map.get(offset);
824            }
825    
826            public void close() {
827                underlying.close();
828            }
829        }
830    
831        /**
832         * Data source that delegates all methods to an underlying data source.
833         */
834        private static abstract class DelegatingDataSource implements DataSource {
835            protected final DataSource dataSource;
836    
837            public DelegatingDataSource(DataSource dataSource) {
838                this.dataSource = dataSource;
839            }
840    
841            public Connection getConnection() throws SQLException {
842                return dataSource.getConnection();
843            }
844    
845            public Connection getConnection(String username, String password) throws SQLException {
846                return dataSource.getConnection(username, password);
847            }
848    
849            public PrintWriter getLogWriter() throws SQLException {
850                return dataSource.getLogWriter();
851            }
852    
853            public void setLogWriter(PrintWriter out) throws SQLException {
854                dataSource.setLogWriter(out);
855            }
856    
857            public void setLoginTimeout(int seconds) throws SQLException {
858                dataSource.setLoginTimeout(seconds);
859            }
860    
861            public int getLoginTimeout() throws SQLException {
862                return dataSource.getLoginTimeout();
863            }
864    
865            public <T> T unwrap(Class<T> iface) throws SQLException {
866                if (Util.JdbcVersion >= 4) {
867                    // Do
868                    //              return dataSource.unwrap(iface);
869                    // via reflection.
870                    try {
871                        Method method =
872                            DataSource.class.getMethod("unwrap", Class.class);
873                        return iface.cast(method.invoke(dataSource, iface));
874                    } catch (IllegalAccessException e) {
875                        throw Util.newInternal(e, "While invokin unwrap");
876                    } catch (InvocationTargetException e) {
877                        throw Util.newInternal(e, "While invokin unwrap");
878                    } catch (NoSuchMethodException e) {
879                        throw Util.newInternal(e, "While invokin unwrap");
880                    }
881                } else {
882                    if (iface.isInstance(dataSource)) {
883                        return iface.cast(dataSource);
884                    } else {
885                        return null;
886                    }
887                }
888            }
889    
890            public boolean isWrapperFor(Class<?> iface) throws SQLException {
891                if (Util.JdbcVersion >= 4) {
892                    // Do
893                    //              return dataSource.isWrapperFor(iface);
894                    // via reflection.
895                    try {
896                        Method method =
897                            DataSource.class.getMethod("isWrapperFor", boolean.class);
898                        return (Boolean) method.invoke(dataSource, iface);
899                    } catch (IllegalAccessException e) {
900                        throw Util.newInternal(e, "While invoking isWrapperFor");
901                    } catch (InvocationTargetException e) {
902                        throw Util.newInternal(e, "While invoking isWrapperFor");
903                    } catch (NoSuchMethodException e) {
904                        throw Util.newInternal(e, "While invoking isWrapperFor");
905                    }
906                } else {
907                    return iface.isInstance(dataSource);
908                }
909            }
910        }
911    
912        /**
913         * Data source that gets connections from an underlying data source but
914         * with different user name and password.
915         */
916        private static class UserPasswordDataSource extends DelegatingDataSource {
917            private final String jdbcUser;
918            private final String jdbcPassword;
919    
920            /**
921             * Creates a UserPasswordDataSource
922             *
923             * @param dataSource Underlying data source
924             * @param jdbcUser User name
925             * @param jdbcPassword Password
926             */
927            public UserPasswordDataSource(
928                DataSource dataSource,
929                String jdbcUser,
930                String jdbcPassword)
931            {
932                super(dataSource);
933                this.jdbcUser = jdbcUser;
934                this.jdbcPassword = jdbcPassword;
935            }
936    
937            public Connection getConnection() throws SQLException {
938                return dataSource.getConnection(jdbcUser, jdbcPassword);
939            }
940        }
941    }
942    
943    // End RolapConnection.java