001/* 002// This software is subject to the terms of the Eclipse Public License v1.0 003// Agreement, available at the following URL: 004// http://www.eclipse.org/legal/epl-v10.html. 005// You must accept the terms of that agreement to use this software. 006// 007// Copyright (C) 2001-2005 Julian Hyde 008// Copyright (C) 2005-2013 Pentaho and others 009// All Rights Reserved. 010*/ 011package mondrian.rolap; 012 013import mondrian.calc.*; 014import mondrian.calc.impl.DelegatingTupleList; 015import mondrian.olap.*; 016import mondrian.parser.MdxParserValidator; 017import mondrian.resource.MondrianResource; 018import mondrian.server.*; 019import mondrian.spi.*; 020import mondrian.spi.impl.JndiDataSourceResolver; 021import mondrian.util.*; 022 023import org.apache.log4j.Logger; 024 025import org.eigenbase.util.property.StringProperty; 026 027import org.olap4j.Scenario; 028 029import java.io.PrintWriter; 030import java.lang.reflect.InvocationTargetException; 031import java.lang.reflect.Method; 032import java.sql.Connection; 033import java.sql.SQLException; 034import java.util.*; 035import java.util.concurrent.Callable; 036import java.util.concurrent.atomic.AtomicInteger; 037import javax.sql.DataSource; 038 039/** 040 * A <code>RolapConnection</code> is a connection to a Mondrian OLAP Server. 041 * 042 * <p>Typically, you create a connection via 043 * {@link DriverManager#getConnection(String, mondrian.spi.CatalogLocator)}. 044 * {@link RolapConnectionProperties} describes allowable keywords.</p> 045 * 046 * @see RolapSchema 047 * @see DriverManager 048 * @author jhyde 049 * @since 2 October, 2002 050 */ 051public class RolapConnection extends ConnectionBase { 052 private static final Logger LOGGER = 053 Logger.getLogger(RolapConnection.class); 054 private static final AtomicInteger ID_GENERATOR = new AtomicInteger(); 055 056 private final MondrianServer server; 057 058 private final Util.PropertyList connectInfo; 059 060 /** 061 * Factory for JDBC connections to talk to the RDBMS. This factory will 062 * usually use a connection pool. 063 */ 064 private final DataSource dataSource; 065 private final String catalogUrl; 066 private final RolapSchema schema; 067 private SchemaReader schemaReader; 068 protected Role role; 069 private Locale locale = Locale.getDefault(); 070 private Scenario scenario; 071 private boolean closed = false; 072 073 private static DataSourceResolver dataSourceResolver; 074 private final int id; 075 private final Statement internalStatement; 076 077 /** 078 * Creates a connection. 079 * 080 * @param server Server instance this connection belongs to 081 * @param connectInfo Connection properties; keywords are described in 082 * {@link RolapConnectionProperties}. 083 * @param dataSource JDBC data source 084 */ 085 public RolapConnection( 086 MondrianServer server, 087 Util.PropertyList connectInfo, 088 DataSource dataSource) 089 { 090 this(server, connectInfo, null, dataSource); 091 } 092 093 /** 094 * Creates a RolapConnection. 095 * 096 * <p>Only {@link RolapSchemaPool#get} calls this with 097 * schema != null (to create a schema's internal connection). 098 * Other uses retrieve a schema from the cache based upon 099 * the <code>Catalog</code> property. 100 * 101 * @param server Server instance this connection belongs to 102 * @param connectInfo Connection properties; keywords are described in 103 * {@link RolapConnectionProperties}. 104 * @param schema Schema for the connection. Must be null unless this is to 105 * be an internal connection. 106 * @param dataSource If not null an external DataSource to be used 107 * by Mondrian 108 */ 109 RolapConnection( 110 MondrianServer server, 111 Util.PropertyList connectInfo, 112 RolapSchema schema, 113 DataSource dataSource) 114 { 115 super(); 116 assert server != null; 117 this.server = server; 118 this.id = ID_GENERATOR.getAndIncrement(); 119 120 assert connectInfo != null; 121 String provider = connectInfo.get( 122 RolapConnectionProperties.Provider.name(), "mondrian"); 123 Util.assertTrue(provider.equalsIgnoreCase("mondrian")); 124 this.connectInfo = connectInfo; 125 this.catalogUrl = 126 connectInfo.get(RolapConnectionProperties.Catalog.name()); 127 final String jdbcUser = 128 connectInfo.get(RolapConnectionProperties.JdbcUser.name()); 129 final String jdbcConnectString = 130 connectInfo.get(RolapConnectionProperties.Jdbc.name()); 131 final String strDataSource = 132 connectInfo.get(RolapConnectionProperties.DataSource.name()); 133 StringBuilder buf = new StringBuilder(); 134 this.dataSource = 135 createDataSource(dataSource, connectInfo, buf); 136 Role role = null; 137 138 // Register this connection before we register its internal statement. 139 server.addConnection(this); 140 141 if (schema == null) { 142 // If RolapSchema.Pool.get were to call this with schema == null, 143 // we would loop. 144 Statement bootstrapStatement = createInternalStatement(false); 145 final Locus locus = 146 new Locus( 147 new Execution(bootstrapStatement, 0), 148 null, 149 "Initializing connection"); 150 Locus.push(locus); 151 try { 152 if (dataSource == null) { 153 // If there is no external data source is passed in, we 154 // expect the properties Jdbc, JdbcUser, DataSource to be 155 // set, as they are used to generate the schema cache key. 156 final String connectionKey = 157 jdbcConnectString 158 + getJdbcProperties(connectInfo).toString(); 159 160 schema = RolapSchemaPool.instance().get( 161 catalogUrl, 162 connectionKey, 163 jdbcUser, 164 strDataSource, 165 connectInfo); 166 } else { 167 schema = RolapSchemaPool.instance().get( 168 catalogUrl, 169 dataSource, 170 connectInfo); 171 } 172 } finally { 173 Locus.pop(locus); 174 bootstrapStatement.close(); 175 } 176 internalStatement = 177 schema.getInternalConnection().getInternalStatement(); 178 String roleNameList = 179 connectInfo.get(RolapConnectionProperties.Role.name()); 180 if (roleNameList != null) { 181 List<String> roleNames = Util.parseCommaList(roleNameList); 182 List<Role> roleList = new ArrayList<Role>(); 183 for (String roleName : roleNames) { 184 final LockBox.Entry entry = 185 server.getLockBox().get(roleName); 186 Role role1; 187 if (entry != null) { 188 try { 189 role1 = (Role) entry.getValue(); 190 } catch (ClassCastException e) { 191 role1 = null; 192 } 193 } else { 194 role1 = schema.lookupRole(roleName); 195 } 196 if (role1 == null) { 197 throw Util.newError( 198 "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 this.internalStatement = createInternalStatement(true); 219 220 // We are creating an internal connection. Now is a great time to 221 // make sure that the JDBC credentials are valid, for this 222 // connection and for external connections built on top of this. 223 Connection conn = null; 224 java.sql.Statement statement = null; 225 try { 226 conn = this.dataSource.getConnection(); 227 Dialect dialect = 228 DialectManager.createDialect(this.dataSource, conn); 229 if (dialect.getDatabaseProduct() 230 == Dialect.DatabaseProduct.DERBY) 231 { 232 // Derby requires a little extra prodding to do the 233 // validation to detect an error. 234 statement = conn.createStatement(); 235 statement.executeQuery("select * from bogustable"); 236 } 237 } catch (SQLException e) { 238 if (e.getMessage().equals( 239 "Table/View 'BOGUSTABLE' does not exist.")) 240 { 241 // Ignore. This exception comes from Derby when the 242 // connection is valid. If the connection were invalid, we 243 // would receive an error such as "Schema 'BOGUSUSER' does 244 // not exist" 245 } else { 246 throw Util.newError( 247 e, 248 "Error while creating SQL connection: " + buf); 249 } 250 } finally { 251 try { 252 if (statement != null) { 253 statement.close(); 254 } 255 if (conn != null) { 256 conn.close(); 257 } 258 } catch (SQLException e) { 259 // ignore 260 } 261 } 262 } 263 264 if (role == null) { 265 role = schema.getDefaultRole(); 266 } 267 268 // Set the locale. 269 String localeString = 270 connectInfo.get(RolapConnectionProperties.Locale.name()); 271 if (localeString != null) { 272 this.locale = Util.parseLocale(localeString); 273 assert locale != null; 274 } 275 276 this.schema = schema; 277 setRole(role); 278 } 279 280 @Override 281 protected void finalize() throws Throwable { 282 try { 283 super.finalize(); 284 close(); 285 } catch (Throwable t) { 286 LOGGER.info( 287 MondrianResource.instance() 288 .FinalizerErrorRolapConnection.baseMessage, 289 t); 290 } 291 } 292 293 /** 294 * Returns the identifier of this connection. Unique within the lifetime of 295 * this JVM. 296 * 297 * @return Identifier of this connection 298 */ 299 public int getId() { 300 return id; 301 } 302 303 protected Logger getLogger() { 304 return LOGGER; 305 } 306 307 /** 308 * Creates a JDBC data source from the JDBC credentials contained within a 309 * set of mondrian connection properties. 310 * 311 * <p>This method is package-level so that it can be called from the 312 * RolapConnectionTest unit test. 313 * 314 * @param dataSource Anonymous data source from user, or null 315 * @param connectInfo Mondrian connection properties 316 * @param buf Into which method writes a description of the JDBC credentials 317 * @return Data source 318 */ 319 static DataSource createDataSource( 320 DataSource dataSource, 321 Util.PropertyList connectInfo, 322 StringBuilder buf) 323 { 324 assert buf != null; 325 final String jdbcConnectString = 326 connectInfo.get(RolapConnectionProperties.Jdbc.name()); 327 final String jdbcUser = 328 connectInfo.get(RolapConnectionProperties.JdbcUser.name()); 329 final String jdbcPassword = 330 connectInfo.get(RolapConnectionProperties.JdbcPassword.name()); 331 final String dataSourceName = 332 connectInfo.get(RolapConnectionProperties.DataSource.name()); 333 334 if (dataSource != null) { 335 appendKeyValue(buf, "Anonymous data source", dataSource); 336 appendKeyValue( 337 buf, RolapConnectionProperties.JdbcUser.name(), jdbcUser); 338 appendKeyValue( 339 buf, 340 RolapConnectionProperties.JdbcPassword.name(), 341 jdbcPassword); 342 if (jdbcUser != null || jdbcPassword != null) { 343 dataSource = 344 new UserPasswordDataSource( 345 dataSource, jdbcUser, jdbcPassword); 346 } 347 return dataSource; 348 349 } else if (jdbcConnectString != null) { 350 // Get connection through own pooling datasource 351 appendKeyValue( 352 buf, RolapConnectionProperties.Jdbc.name(), jdbcConnectString); 353 appendKeyValue( 354 buf, RolapConnectionProperties.JdbcUser.name(), jdbcUser); 355 appendKeyValue( 356 buf, 357 RolapConnectionProperties.JdbcPassword.name(), 358 jdbcPassword); 359 String jdbcDrivers = 360 connectInfo.get(RolapConnectionProperties.JdbcDrivers.name()); 361 if (jdbcDrivers != null) { 362 RolapUtil.loadDrivers(jdbcDrivers); 363 } 364 final String jdbcDriversProp = 365 MondrianProperties.instance().JdbcDrivers.get(); 366 RolapUtil.loadDrivers(jdbcDriversProp); 367 368 Properties jdbcProperties = getJdbcProperties(connectInfo); 369 final Map<String, String> map = Util.toMap(jdbcProperties); 370 for (Map.Entry<String, String> entry : map.entrySet()) { 371 // FIXME ordering is non-deterministic 372 appendKeyValue(buf, entry.getKey(), entry.getValue()); 373 } 374 375 if (jdbcUser != null) { 376 jdbcProperties.put("user", jdbcUser); 377 } 378 if (jdbcPassword != null) { 379 jdbcProperties.put("password", jdbcPassword); 380 } 381 382 // JDBC connections are dumb beasts, so we assume they're not 383 // pooled. Therefore the default is true. 384 final boolean poolNeeded = 385 connectInfo.get( 386 RolapConnectionProperties.PoolNeeded.name(), 387 "true").equalsIgnoreCase("true"); 388 389 if (!poolNeeded) { 390 // Connection is already pooled; don't pool it again. 391 return new DriverManagerDataSource( 392 jdbcConnectString, 393 jdbcProperties); 394 } 395 396 if (jdbcConnectString.toLowerCase().indexOf("mysql") > -1) { 397 // mysql driver needs this autoReconnect parameter 398 jdbcProperties.setProperty("autoReconnect", "true"); 399 } 400 return RolapConnectionPool.instance() 401 .getDriverManagerPoolingDataSource( 402 jdbcConnectString, jdbcProperties); 403 404 } else if (dataSourceName != null) { 405 appendKeyValue( 406 buf, 407 RolapConnectionProperties.DataSource.name(), 408 dataSourceName); 409 appendKeyValue( 410 buf, 411 RolapConnectionProperties.JdbcUser.name(), 412 jdbcUser); 413 appendKeyValue( 414 buf, 415 RolapConnectionProperties.JdbcPassword.name(), 416 jdbcPassword); 417 418 // Data sources are fairly smart, so we assume they look after 419 // their own pooling. Therefore the default is false. 420 final boolean poolNeeded = 421 connectInfo.get( 422 RolapConnectionProperties.PoolNeeded.name(), 423 "false").equalsIgnoreCase("true"); 424 425 // Get connection from datasource. 426 DataSourceResolver dataSourceResolver = getDataSourceResolver(); 427 try { 428 dataSource = dataSourceResolver.lookup(dataSourceName); 429 } catch (Exception e) { 430 throw Util.newInternal( 431 e, 432 "Error while looking up data source (" 433 + dataSourceName + ")"); 434 } 435 if (poolNeeded) { 436 dataSource = 437 RolapConnectionPool.instance() 438 .getDataSourcePoolingDataSource( 439 dataSource, dataSourceName, jdbcUser, jdbcPassword); 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 * Returns the instance of the {@link mondrian.spi.DataSourceResolver} 458 * plugin. 459 * 460 * @return data source resolver 461 */ 462 private static synchronized DataSourceResolver getDataSourceResolver() { 463 if (dataSourceResolver == null) { 464 final StringProperty property = 465 MondrianProperties.instance().DataSourceResolverClass; 466 final String className = 467 property.get( 468 JndiDataSourceResolver.class.getName()); 469 try { 470 dataSourceResolver = 471 ClassResolver.INSTANCE.instantiateSafe(className); 472 } catch (ClassCastException e) { 473 throw Util.newInternal( 474 e, 475 "Plugin class specified by property " 476 + property.getPath() 477 + " must implement " 478 + DataSourceResolver.class.getName()); 479 } 480 } 481 return dataSourceResolver; 482 } 483 484 /** 485 * Appends "key=value" to a buffer, if value is not null. 486 * 487 * @param buf Buffer 488 * @param key Key 489 * @param value Value 490 */ 491 private static void appendKeyValue( 492 StringBuilder buf, 493 String key, 494 Object value) 495 { 496 if (value != null) { 497 if (buf.length() > 0) { 498 buf.append("; "); 499 } 500 buf.append(key).append('=').append(value); 501 } 502 } 503 504 /** 505 * Creates a {@link Properties} object containing all of the JDBC 506 * connection properties present in the 507 * {@link mondrian.olap.Util.PropertyList connectInfo}. 508 * 509 * @param connectInfo Connection properties 510 * @return The JDBC connection properties. 511 */ 512 private static Properties getJdbcProperties(Util.PropertyList connectInfo) { 513 Properties jdbcProperties = new Properties(); 514 for (Pair<String, String> entry : connectInfo) { 515 if (entry.left.startsWith( 516 RolapConnectionProperties.JdbcPropertyPrefix)) 517 { 518 jdbcProperties.put( 519 entry.left.substring( 520 RolapConnectionProperties.JdbcPropertyPrefix.length()), 521 entry.right); 522 } 523 } 524 return jdbcProperties; 525 } 526 527 public Util.PropertyList getConnectInfo() { 528 return connectInfo; 529 } 530 531 public void close() { 532 if (!closed) { 533 closed = true; 534 server.removeConnection(this); 535 } 536 if (internalStatement != null) { 537 internalStatement.close(); 538 } 539 } 540 541 public RolapSchema getSchema() { 542 return schema; 543 } 544 545 public String getConnectString() { 546 return connectInfo.toString(); 547 } 548 549 public String getCatalogName() { 550 return catalogUrl; 551 } 552 553 public Locale getLocale() { 554 return locale; 555 } 556 557 public void setLocale(Locale locale) { 558 if (locale == null) { 559 throw new IllegalArgumentException("locale must not be null"); 560 } 561 this.locale = locale; 562 } 563 564 public SchemaReader getSchemaReader() { 565 return schemaReader; 566 } 567 568 public Object getProperty(String name) { 569 // Mask out the values of certain properties. 570 if (name.equals(RolapConnectionProperties.JdbcPassword.name()) 571 || name.equals(RolapConnectionProperties.CatalogContent.name())) 572 { 573 return ""; 574 } 575 return connectInfo.get(name); 576 } 577 578 public CacheControl getCacheControl(PrintWriter pw) { 579 return getServer().getAggregationManager().getCacheControl(this, pw); 580 } 581 582 /** 583 * Executes a Query. 584 * 585 * @param query Query parse tree 586 * 587 * @throws ResourceLimitExceededException if some resource limit specified 588 * in the property file was exceeded 589 * @throws QueryCanceledException if query was canceled during execution 590 * @throws QueryTimeoutException if query exceeded timeout specified in 591 * the property file 592 * 593 * @deprecated Use {@link #execute(mondrian.server.Execution)}; this method 594 * will be removed in mondrian-4.0 595 */ 596 public Result execute(Query query) { 597 final Statement statement = query.getStatement(); 598 Execution execution = 599 new Execution(statement, statement.getQueryTimeoutMillis()); 600 return execute(execution); 601 } 602 603 /** 604 * Executes a statement. 605 * 606 * @param execution Execution context (includes statement, query) 607 * 608 * @throws ResourceLimitExceededException if some resource limit specified 609 * in the property file was exceeded 610 * @throws QueryCanceledException if query was canceled during execution 611 * @throws QueryTimeoutException if query exceeded timeout specified in 612 * the property file 613 */ 614 public Result execute(final Execution execution) { 615 execution.copyMDC(); 616 return 617 server.getResultShepherd() 618 .shepherdExecution( 619 execution, 620 new Callable<Result>() { 621 public Result call() throws Exception { 622 return executeInternal(execution); 623 } 624 }); 625 } 626 627 private Result executeInternal(final Execution execution) { 628 execution.setContextMap(); 629 final Statement statement = execution.getMondrianStatement(); 630 // Cleanup any previous executions still running 631 synchronized (statement) { 632 final Execution previousExecution = 633 statement.getCurrentExecution(); 634 if (previousExecution != null) { 635 statement.end(previousExecution); 636 } 637 } 638 final Query query = statement.getQuery(); 639 final MemoryMonitor.Listener listener = new MemoryMonitor.Listener() { 640 public void memoryUsageNotification(long used, long max) { 641 execution.setOutOfMemory( 642 "OutOfMemory used=" 643 + used 644 + ", max=" 645 + max 646 + " for connection: " 647 + getConnectString()); 648 } 649 }; 650 MemoryMonitor mm = MemoryMonitorFactory.getMemoryMonitor(); 651 final long currId = execution.getId(); 652 try { 653 mm.addListener(listener); 654 // Check to see if we must punt 655 execution.checkCancelOrTimeout(); 656 657 if (LOGGER.isDebugEnabled()) { 658 LOGGER.debug(Util.unparse(query)); 659 } 660 661 if (RolapUtil.MDX_LOGGER.isDebugEnabled()) { 662 RolapUtil.MDX_LOGGER.debug(currId + ": " + Util.unparse(query)); 663 } 664 665 final Locus locus = new Locus(execution, null, "Loading cells"); 666 Locus.push(locus); 667 Result result; 668 try { 669 statement.start(execution); 670 ((RolapCube) query.getCube()).clearCachedAggregations(true); 671 result = new RolapResult(execution, true); 672 int i = 0; 673 for (QueryAxis axis : query.getAxes()) { 674 if (axis.isNonEmpty()) { 675 result = new NonEmptyResult(result, execution, i); 676 } 677 ++i; 678 } 679 } finally { 680 Locus.pop(locus); 681 ((RolapCube) query.getCube()).clearCachedAggregations(true); 682 } 683 statement.end(execution); 684 return result; 685 } catch (ResultLimitExceededException e) { 686 // query has been punted 687 throw e; 688 } catch (Exception e) { 689 try { 690 if (!execution.isCancelOrTimeout()) { 691 statement.end(execution); 692 } 693 } catch (Exception e1) { 694 // We can safely ignore that cleanup exception. 695 // If an error is encountered here, it means that 696 // one was already encountered at statement.start() 697 // above and the exception we will throw after the 698 // cleanup is the same as the original one. 699 } 700 String queryString; 701 try { 702 queryString = Util.unparse(query); 703 } catch (Exception e1) { 704 queryString = "?"; 705 } 706 throw Util.newError( 707 e, 708 "Error while executing query [" + queryString + "]"); 709 } finally { 710 mm.removeListener(listener); 711 if (RolapUtil.MDX_LOGGER.isDebugEnabled()) { 712 final long elapsed = execution.getElapsedMillis(); 713 RolapUtil.MDX_LOGGER.debug( 714 currId + ": exec: " + elapsed + " ms"); 715 } 716 } 717 } 718 719 public void setRole(Role role) { 720 assert role != null; 721 722 this.role = role; 723 this.schemaReader = new RolapSchemaReader(role, schema); 724 } 725 726 public Role getRole() { 727 Util.assertPostcondition(role != null, "role != null"); 728 729 return role; 730 } 731 732 public void setScenario(Scenario scenario) { 733 this.scenario = scenario; 734 } 735 736 public Scenario getScenario() { 737 return scenario; 738 } 739 740 /** 741 * Returns the server (mondrian instance) that this connection belongs to. 742 * Usually there is only one server instance in a given JVM. 743 * 744 * @return Server instance; never null 745 */ 746 public MondrianServer getServer() { 747 return server; 748 } 749 750 public QueryPart parseStatement(String query) { 751 Statement statement = createInternalStatement(false); 752 final Locus locus = 753 new Locus( 754 new Execution(statement, 0), 755 "Parse/validate MDX statement", 756 null); 757 Locus.push(locus); 758 try { 759 QueryPart queryPart = 760 parseStatement(statement, query, null, false); 761 if (queryPart instanceof Query) { 762 ((Query) queryPart).setOwnStatement(true); 763 statement = null; 764 } 765 return queryPart; 766 } finally { 767 Locus.pop(locus); 768 if (statement != null) { 769 statement.close(); 770 } 771 } 772 } 773 774 public Exp parseExpression(String expr) { 775 boolean debug = false; 776 if (getLogger().isDebugEnabled()) { 777 //debug = true; 778 getLogger().debug( 779 Util.nl 780 + expr); 781 } 782 final Statement statement = getInternalStatement(); 783 try { 784 MdxParserValidator parser = createParser(); 785 final FunTable funTable = getSchema().getFunTable(); 786 return parser.parseExpression(statement, expr, debug, funTable); 787 } catch (Throwable exception) { 788 throw MondrianResource.instance().FailedToParseQuery.ex( 789 expr, 790 exception); 791 } 792 } 793 794 public Statement getInternalStatement() { 795 if (internalStatement == null) { 796 return schema.getInternalConnection().getInternalStatement(); 797 } else { 798 return internalStatement; 799 } 800 } 801 802 private Statement createInternalStatement(boolean reentrant) { 803 final Statement statement = 804 reentrant 805 ? new ReentrantInternalStatement() 806 : new InternalStatement(); 807 server.addStatement(statement); 808 return statement; 809 } 810 811 /** 812 * Implementation of {@link DataSource} which calls the good ol' 813 * {@link java.sql.DriverManager}. 814 * 815 * <p>Overrides {@link #hashCode()} and {@link #equals(Object)} so that 816 * {@link Dialect} objects can be cached more effectively. 817 */ 818 private static class DriverManagerDataSource implements DataSource { 819 private final String jdbcConnectString; 820 private PrintWriter logWriter; 821 private int loginTimeout; 822 private Properties jdbcProperties; 823 824 public DriverManagerDataSource( 825 String jdbcConnectString, 826 Properties properties) 827 { 828 this.jdbcConnectString = jdbcConnectString; 829 this.jdbcProperties = properties; 830 } 831 832 @Override 833 public int hashCode() { 834 int h = loginTimeout; 835 h = Util.hash(h, jdbcConnectString); 836 h = Util.hash(h, jdbcProperties); 837 return h; 838 } 839 840 @Override 841 public boolean equals(Object obj) { 842 if (obj instanceof DriverManagerDataSource) { 843 DriverManagerDataSource 844 that = (DriverManagerDataSource) obj; 845 return this.loginTimeout == that.loginTimeout 846 && this.jdbcConnectString.equals(that.jdbcConnectString) 847 && this.jdbcProperties.equals(that.jdbcProperties); 848 } 849 return false; 850 } 851 852 public Connection getConnection() throws SQLException { 853 return new org.apache.commons.dbcp.DelegatingConnection( 854 java.sql.DriverManager.getConnection( 855 jdbcConnectString, jdbcProperties)); 856 } 857 858 public Connection getConnection(String username, String password) 859 throws SQLException 860 { 861 if (jdbcProperties == null) { 862 return java.sql.DriverManager.getConnection( 863 jdbcConnectString, username, password); 864 } else { 865 Properties temp = (Properties)jdbcProperties.clone(); 866 temp.put("user", username); 867 temp.put("password", password); 868 return java.sql.DriverManager.getConnection( 869 jdbcConnectString, temp); 870 } 871 } 872 873 public PrintWriter getLogWriter() throws SQLException { 874 return logWriter; 875 } 876 877 public void setLogWriter(PrintWriter out) throws SQLException { 878 logWriter = out; 879 } 880 881 public void setLoginTimeout(int seconds) throws SQLException { 882 loginTimeout = seconds; 883 } 884 885 public int getLoginTimeout() throws SQLException { 886 return loginTimeout; 887 } 888 889 public java.util.logging.Logger getParentLogger() { 890 return java.util.logging.Logger.getLogger(""); 891 } 892 893 public <T> T unwrap(Class<T> iface) throws SQLException { 894 throw new SQLException("not a wrapper"); 895 } 896 897 public boolean isWrapperFor(Class<?> iface) throws SQLException { 898 return false; 899 } 900 } 901 902 public DataSource getDataSource() { 903 return dataSource; 904 } 905 906 /** 907 * Helper method to allow olap4j wrappers to implement 908 * {@link org.olap4j.OlapConnection#createScenario()}. 909 * 910 * @return new Scenario 911 */ 912 public ScenarioImpl createScenario() { 913 final ScenarioImpl scenario = new ScenarioImpl(); 914 scenario.register(schema); 915 return scenario; 916 } 917 918 /** 919 * A <code>NonEmptyResult</code> filters a result by removing empty rows 920 * on a particular axis. 921 */ 922 static class NonEmptyResult extends ResultBase { 923 924 final Result underlying; 925 private final int axis; 926 private final Map<Integer, Integer> map; 927 /** workspace. Synchronized access only. */ 928 private final int[] pos; 929 930 /** 931 * Creates a NonEmptyResult. 932 * 933 * @param result Result set 934 * @param execution Execution context 935 * @param axis Which axis to make non-empty 936 */ 937 NonEmptyResult(Result result, Execution execution, int axis) { 938 super(execution, result.getAxes().clone()); 939 940 this.underlying = result; 941 this.axis = axis; 942 this.map = new HashMap<Integer, Integer>(); 943 int axisCount = underlying.getAxes().length; 944 this.pos = new int[axisCount]; 945 this.slicerAxis = underlying.getSlicerAxis(); 946 TupleList tupleList = 947 ((RolapAxis) underlying.getAxes()[axis]).getTupleList(); 948 949 final TupleList filteredTupleList; 950 if (!tupleList.isEmpty() 951 && tupleList.get(0).get(0).getDimension().isHighCardinality()) 952 { 953 filteredTupleList = 954 new DelegatingTupleList( 955 tupleList.getArity(), 956 new FilteredIterableList<List<Member>>( 957 tupleList, 958 new FilteredIterableList.Filter<List<Member>>() { 959 public boolean accept(final List<Member> p) { 960 return p.get(0) != null; 961 } 962 } 963 )); 964 } else { 965 filteredTupleList = 966 TupleCollections.createList(tupleList.getArity()); 967 int i = -1; 968 TupleCursor tupleCursor = tupleList.tupleCursor(); 969 while (tupleCursor.forward()) { 970 ++i; 971 if (! isEmpty(i, axis)) { 972 map.put(filteredTupleList.size(), i); 973 filteredTupleList.addCurrent(tupleCursor); 974 } 975 } 976 } 977 this.axes[axis] = new RolapAxis(filteredTupleList); 978 } 979 980 protected Logger getLogger() { 981 return LOGGER; 982 } 983 984 /** 985 * Returns true if all cells at a given offset on a given axis are 986 * empty. For example, in a 2x2x2 dataset, <code>isEmpty(1,0)</code> 987 * returns true if cells <code>{(1,0,0), (1,0,1), (1,1,0), 988 * (1,1,1)}</code> are all empty. As you can see, we hold the 0th 989 * coordinate fixed at 1, and vary all other coordinates over all 990 * possible values. 991 */ 992 private boolean isEmpty(int offset, int fixedAxis) { 993 int axisCount = getAxes().length; 994 pos[fixedAxis] = offset; 995 return isEmptyRecurse(fixedAxis, axisCount - 1); 996 } 997 998 private boolean isEmptyRecurse(int fixedAxis, int axis) { 999 if (axis < 0) { 1000 RolapCell cell = (RolapCell) underlying.getCell(pos); 1001 return cell.isNull(); 1002 } else if (axis == fixedAxis) { 1003 return isEmptyRecurse(fixedAxis, axis - 1); 1004 } else { 1005 List<Position> positions = getAxes()[axis].getPositions(); 1006 final int positionCount = positions.size(); 1007 for (int i = 0; i < positionCount; i++) { 1008 pos[axis] = i; 1009 if (!isEmptyRecurse(fixedAxis, axis - 1)) { 1010 return false; 1011 } 1012 } 1013 return true; 1014 } 1015 } 1016 1017 // synchronized because we use 'pos' 1018 public synchronized Cell getCell(int[] externalPos) { 1019 try { 1020 System.arraycopy( 1021 externalPos, 0, this.pos, 0, externalPos.length); 1022 int offset = externalPos[axis]; 1023 int mappedOffset = mapOffsetToUnderlying(offset); 1024 this.pos[axis] = mappedOffset; 1025 return underlying.getCell(this.pos); 1026 } catch (NullPointerException npe) { 1027 return underlying.getCell(externalPos); 1028 } 1029 } 1030 1031 private int mapOffsetToUnderlying(int offset) { 1032 return map.get(offset); 1033 } 1034 1035 public void close() { 1036 underlying.close(); 1037 } 1038 } 1039 1040 /** 1041 * Data source that delegates all methods to an underlying data source. 1042 */ 1043 private static abstract class DelegatingDataSource implements DataSource { 1044 protected final DataSource dataSource; 1045 1046 public DelegatingDataSource(DataSource dataSource) { 1047 this.dataSource = dataSource; 1048 } 1049 1050 public Connection getConnection() throws SQLException { 1051 return dataSource.getConnection(); 1052 } 1053 1054 public Connection getConnection( 1055 String username, 1056 String password) 1057 throws SQLException 1058 { 1059 return dataSource.getConnection(username, password); 1060 } 1061 1062 public PrintWriter getLogWriter() throws SQLException { 1063 return dataSource.getLogWriter(); 1064 } 1065 1066 public void setLogWriter(PrintWriter out) throws SQLException { 1067 dataSource.setLogWriter(out); 1068 } 1069 1070 public void setLoginTimeout(int seconds) throws SQLException { 1071 dataSource.setLoginTimeout(seconds); 1072 } 1073 1074 public int getLoginTimeout() throws SQLException { 1075 return dataSource.getLoginTimeout(); 1076 } 1077 1078 // JDBC 4.0 support (JDK 1.6 and higher) 1079 public <T> T unwrap(Class<T> iface) throws SQLException { 1080 if (Util.JdbcVersion >= 0x0400) { 1081 // Do 1082 // return dataSource.unwrap(iface); 1083 // via reflection. 1084 try { 1085 Method method = 1086 DataSource.class.getMethod("unwrap", Class.class); 1087 return iface.cast(method.invoke(dataSource, iface)); 1088 } catch (IllegalAccessException e) { 1089 throw Util.newInternal(e, "While invoking unwrap"); 1090 } catch (InvocationTargetException e) { 1091 throw Util.newInternal(e, "While invoking unwrap"); 1092 } catch (NoSuchMethodException e) { 1093 throw Util.newInternal(e, "While invoking unwrap"); 1094 } 1095 } else { 1096 if (iface.isInstance(dataSource)) { 1097 return iface.cast(dataSource); 1098 } else { 1099 return null; 1100 } 1101 } 1102 } 1103 1104 // JDBC 4.0 support (JDK 1.6 and higher) 1105 public boolean isWrapperFor(Class<?> iface) throws SQLException { 1106 if (Util.JdbcVersion >= 0x0400) { 1107 // Do 1108 // return dataSource.isWrapperFor(iface); 1109 // via reflection. 1110 try { 1111 Method method = 1112 DataSource.class.getMethod( 1113 "isWrapperFor", boolean.class); 1114 return (Boolean) method.invoke(dataSource, iface); 1115 } catch (IllegalAccessException e) { 1116 throw Util.newInternal(e, "While invoking isWrapperFor"); 1117 } catch (InvocationTargetException e) { 1118 throw Util.newInternal(e, "While invoking isWrapperFor"); 1119 } catch (NoSuchMethodException e) { 1120 throw Util.newInternal(e, "While invoking isWrapperFor"); 1121 } 1122 } else { 1123 return iface.isInstance(dataSource); 1124 } 1125 } 1126 1127 // JDBC 4.1 support (JDK 1.7 and higher) 1128 public java.util.logging.Logger getParentLogger() { 1129 if (Util.JdbcVersion >= 0x0401) { 1130 // Do 1131 // return dataSource.getParentLogger(); 1132 // via reflection. 1133 try { 1134 Method method = 1135 DataSource.class.getMethod("getParentLogger"); 1136 return (java.util.logging.Logger) method.invoke(dataSource); 1137 } catch (IllegalAccessException e) { 1138 throw Util.newInternal(e, "While invoking getParentLogger"); 1139 } catch (InvocationTargetException e) { 1140 throw Util.newInternal(e, "While invoking getParentLogger"); 1141 } catch (NoSuchMethodException e) { 1142 throw Util.newInternal(e, "While invoking getParentLogger"); 1143 } 1144 } else { 1145 // Can't throw SQLFeatureNotSupportedException... it doesn't 1146 // exist before JDBC 4.1. 1147 throw new UnsupportedOperationException(); 1148 } 1149 } 1150 } 1151 1152 /** 1153 * Data source that gets connections from an underlying data source but 1154 * with different user name and password. 1155 */ 1156 private static class UserPasswordDataSource extends DelegatingDataSource { 1157 private final String jdbcUser; 1158 private final String jdbcPassword; 1159 1160 /** 1161 * Creates a UserPasswordDataSource 1162 * 1163 * @param dataSource Underlying data source 1164 * @param jdbcUser User name 1165 * @param jdbcPassword Password 1166 */ 1167 public UserPasswordDataSource( 1168 DataSource dataSource, 1169 String jdbcUser, 1170 String jdbcPassword) 1171 { 1172 super(dataSource); 1173 this.jdbcUser = jdbcUser; 1174 this.jdbcPassword = jdbcPassword; 1175 } 1176 1177 public Connection getConnection() throws SQLException { 1178 return dataSource.getConnection(jdbcUser, jdbcPassword); 1179 } 1180 } 1181 1182 /** 1183 * <p>Implementation of {@link Statement} for use when you don't have an 1184 * olap4j connection.</p> 1185 */ 1186 private class InternalStatement extends StatementImpl { 1187 private boolean closed = false; 1188 1189 public void close() { 1190 if (!closed) { 1191 closed = true; 1192 server.removeStatement(this); 1193 } 1194 } 1195 1196 public RolapConnection getMondrianConnection() { 1197 return RolapConnection.this; 1198 } 1199 } 1200 1201 /** 1202 * <p>A statement that can be used for all of the various internal 1203 * operations, such as resolving MDX identifiers, that require a 1204 * {@link Statement} and an {@link Execution}. 1205 * 1206 * <p>The statement needs to be reentrant because there are many such 1207 * operations; several of these operations might be active at one time. We 1208 * don't want to create a new statement for each, but just one internal 1209 * statement for each connection. The statement shouldn't have a unique 1210 * execution. For this reason, we don't use the inherited {@link #execution} 1211 * field.</p> 1212 * 1213 * <p>But there is a drawback. If we can't find the unique execution, the 1214 * statement cannot be canceled or time out. If you want that behavior 1215 * from an internal statement, use the base class: create a new 1216 * {@link InternalStatement} for each operation.</p> 1217 */ 1218 private class ReentrantInternalStatement extends InternalStatement { 1219 @Override 1220 public void start(Execution execution) { 1221 // Unlike StatementImpl, there is not a unique execution. An 1222 // internal statement can execute several at the same time. So, 1223 // we don't set this.execution. 1224 execution.start(); 1225 } 1226 1227 @Override 1228 public void end(Execution execution) { 1229 execution.end(); 1230 } 1231 1232 @Override 1233 public void close() { 1234 // do not close 1235 } 1236 } 1237} 1238 1239// End RolapConnection.java