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