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