001 /* 002 // $Id: //open/mondrian/src/main/mondrian/rolap/aggmatcher/AggTableManager.java#26 $ 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) 2005-2008 Julian Hyde and others 007 // All Rights Reserved. 008 // You must accept the terms of that agreement to use this software. 009 */ 010 011 package mondrian.rolap.aggmatcher; 012 013 import mondrian.olap.*; 014 import mondrian.rolap.*; 015 import mondrian.recorder.*; 016 import mondrian.resource.MondrianResource; 017 018 import org.apache.log4j.Logger; 019 import org.eigenbase.util.property.*; 020 import org.eigenbase.util.property.Property; 021 022 import javax.sql.DataSource; 023 import java.util.*; 024 import java.sql.SQLException; 025 026 /** 027 * Manages aggregate tables. 028 * 029 * <p>It is used as follows:<ul> 030 * <li>A {@link mondrian.rolap.RolapSchema} creates an {@link AggTableManager}, 031 * and stores it in a member variable to ensure that it is not 032 * garbage-collected. 033 * <li>The {@link AggTableManager} creates and registers 034 * {@link org.eigenbase.util.property.Trigger} objects, so that it is notified 035 * when properties pertinent to aggregate tables change. 036 * <li>The {@link mondrian.rolap.RolapSchema} calls {@link #initialize()}, 037 * which scans the JDBC catalog and identifies aggregate tables. 038 * <li>For each aggregate table, it creates an {@link AggStar} and calls 039 * {@link RolapStar#addAggStar(AggStar)}. 040 * 041 * @author Richard M. Emberson 042 * @version $Id: //open/mondrian/src/main/mondrian/rolap/aggmatcher/AggTableManager.java#26 $ 043 */ 044 public class AggTableManager { 045 private static final Logger LOGGER = 046 Logger.getLogger(AggTableManager.class); 047 048 private final RolapSchema schema; 049 050 private static final MondrianResource mres = MondrianResource.instance(); 051 052 /** 053 * This is used to create forward references to triggers (so they do not 054 * get reaped until the RolapSchema is reaped). 055 */ 056 private Trigger[] triggers; 057 058 public AggTableManager(final RolapSchema schema) { 059 this.schema = schema; 060 } 061 062 /** 063 * This should ONLY be called if the AggTableManager is no longer going 064 * to be used. In fact, it should only be called indirectly by its 065 * associated RolapSchema object. 066 */ 067 public void finalCleanUp() { 068 removeJdbcSchema(); 069 deregisterTriggers(MondrianProperties.instance()); 070 071 if (getLogger().isDebugEnabled()) { 072 StringBuilder buf = new StringBuilder(100); 073 buf.append("AggTableManager.finalCleanUp: schema="); 074 buf.append(schema.getName()); 075 getLogger().debug(buf.toString()); 076 } 077 } 078 079 /** 080 * Get the Logger. 081 */ 082 public Logger getLogger() { 083 return LOGGER; 084 } 085 086 /** 087 * Initializes this object, loading all aggregate tables and associating 088 * them with {@link RolapStar}s. 089 * This method should only be called once. 090 */ 091 public void initialize() { 092 if (MondrianProperties.instance().ReadAggregates.get()) { 093 try { 094 loadRolapStarAggregates(); 095 096 } catch (SQLException ex) { 097 throw mres.AggLoadingError.ex(ex); 098 } 099 } 100 registerTriggers(); 101 printResults(); 102 } 103 private void printResults() { 104 /* 105 * This was too much information at the INFO level, compared to the 106 * rest of Mondrian 107 * 108 * if (getLogger().isInfoEnabled()) { 109 // print just Star table alias and AggStar table names 110 StringBuilder buf = new StringBuilder(1024); 111 buf.append(Util.nl); 112 for (Iterator it = getStars(); it.hasNext();) { 113 RolapStar star = (RolapStar) it.next(); 114 buf.append(star.getFactTable().getAlias()); 115 buf.append(Util.nl); 116 for (Iterator ait = star.getAggStars(); ait.hasNext();) { 117 AggStar aggStar = (AggStar) ait.next(); 118 buf.append(" "); 119 buf.append(aggStar.getFactTable().getName()); 120 buf.append(Util.nl); 121 } 122 } 123 getLogger().info(buf.toString()); 124 125 } else 126 */ 127 if (getLogger().isDebugEnabled()) { 128 // print everything, Star, subTables, AggStar and subTables 129 // could be a lot 130 StringBuilder buf = new StringBuilder(4096); 131 buf.append(Util.nl); 132 for (RolapStar star : getStars()) { 133 buf.append(star.toString()); 134 buf.append(Util.nl); 135 } 136 getLogger().debug(buf.toString()); 137 } 138 } 139 private void reLoadRolapStarAggregates() { 140 if (MondrianProperties.instance().ReadAggregates.get()) { 141 try { 142 clearJdbcSchema(); 143 loadRolapStarAggregates(); 144 printResults(); 145 146 } catch (SQLException ex) { 147 throw mres.AggLoadingError.ex(ex); 148 } 149 } 150 } 151 152 private JdbcSchema getJdbcSchema() { 153 DataSource dataSource = schema.getInternalConnection().getDataSource(); 154 155 // This actually just does a lookup or simple constructor invocation, 156 // its not expected to fail 157 return JdbcSchema.makeDB(dataSource); 158 } 159 160 /** 161 * Clear the possibly already loaded snapshot of what is in the database. 162 */ 163 private void clearJdbcSchema() { 164 DataSource dataSource = schema.getInternalConnection().getDataSource(); 165 JdbcSchema.clearDB(dataSource); 166 } 167 168 /** 169 * Remove the possibly already loaded snapshot of what is in the database. 170 */ 171 private void removeJdbcSchema() { 172 DataSource dataSource = schema.getInternalConnection().getDataSource(); 173 JdbcSchema.removeDB(dataSource); 174 } 175 176 177 /** 178 * This method loads and/or reloads the aggregate tables. 179 * <p> 180 * NOTE: At this point all RolapStars have been made for this 181 * schema (except for dynamically added cubes which I am going 182 * to ignore for right now). So, All stars have their columns 183 * and their BitKeys can be generated. 184 * 185 * @throws SQLException 186 */ 187 private void loadRolapStarAggregates() throws SQLException { 188 ListRecorder msgRecorder = new ListRecorder(); 189 try { 190 191 DefaultRules rules = DefaultRules.getInstance(); 192 JdbcSchema db = getJdbcSchema(); 193 // loads tables, not their columns 194 db.load(); 195 196 loop: 197 for (RolapStar star : getStars()) { 198 // This removes any AggStars from any previous invocation of this 199 // method (if any) 200 star.prepareToLoadAggregates(); 201 202 List<ExplicitRules.Group> aggGroups = getAggGroups(star); 203 for (ExplicitRules.Group group : aggGroups) { 204 group.validate(msgRecorder); 205 } 206 207 208 String factTableName = star.getFactTable().getAlias(); 209 210 JdbcSchema.Table dbFactTable = db.getTable(factTableName); 211 if (dbFactTable == null) { 212 msgRecorder.reportWarning("No Table found for fact name=" 213 +factTableName); 214 215 continue loop; 216 } 217 218 // For each column in the dbFactTable, figure out it they are 219 // measure or foreign key columns 220 bindToStar(dbFactTable, star, msgRecorder); 221 String schema = dbFactTable.table.schema; 222 223 // Now look at all tables in the database and per table, first see 224 // if it is a match for an aggregate table for this fact table and 225 // second see if its columns match foreign key and level columns. 226 for (JdbcSchema.Table dbTable : db.getTables()) { 227 String name = dbTable.getName(); 228 229 // Do the catalog schema aggregate excludes, exclude this 230 // table name. 231 if (ExplicitRules.excludeTable(name, aggGroups)) { 232 continue; 233 } 234 235 // 236 // First see if there is an ExplicitRules match. If so, then if all 237 // of the columns match up, then make an AggStar. 238 // On the other hand, if there is no ExplicitRules match, see if 239 // there is a Default match. If so and if all the columns 240 // match up, then also make an AggStar. 241 // 242 ExplicitRules.TableDef tableDef = 243 ExplicitRules.getIncludeByTableDef(name, aggGroups); 244 245 boolean makeAggStar = false; 246 // Is it handled by the ExplicitRules 247 if (tableDef != null) { 248 // load columns 249 dbTable.load(); 250 makeAggStar = tableDef.columnsOK(star, 251 dbFactTable, 252 dbTable, 253 msgRecorder); 254 } 255 if (! makeAggStar) { 256 // Is it handled by the DefaultRules 257 if (rules.matchesTableName(factTableName, name)) { 258 // load columns 259 dbTable.load(); 260 makeAggStar = rules.columnsOK(star, 261 dbFactTable, 262 dbTable, 263 msgRecorder); 264 } 265 } 266 267 268 if (makeAggStar) { 269 dbTable.setTableUsageType(JdbcSchema.TableUsageType.AGG); 270 String alias = null; 271 dbTable.table = new MondrianDef.Table(schema, 272 name, 273 alias); 274 AggStar aggStar = AggStar.makeAggStar(star, 275 dbTable, 276 msgRecorder); 277 if (aggStar.getSize() > 0) { 278 star.addAggStar(aggStar); 279 } else { 280 String msg = mres.AggTableZeroSize.str( 281 aggStar.getFactTable().getName(), 282 factTableName); 283 getLogger().warn(msg); 284 } 285 } 286 // Note: if the dbTable name matches but the columnsOK does 287 // not, then this is an error and the aggregate tables 288 // can not be loaded. 289 // We do not "reset" the column usages in the dbTable allowing 290 // it maybe to match another rule. 291 } 292 } 293 294 } catch (RecorderException ex) { 295 throw new MondrianException(ex); 296 297 } finally { 298 msgRecorder.logInfoMessage(getLogger()); 299 msgRecorder.logWarningMessage(getLogger()); 300 msgRecorder.logErrorMessage(getLogger()); 301 if (msgRecorder.hasErrors()) { 302 throw mres.AggLoadingExceededErrorCount.ex( 303 msgRecorder.getErrorCount()); 304 } 305 } 306 } 307 private boolean runTrigger() { 308 if (RolapSchema.cacheContains(schema)) { 309 return true; 310 } else { 311 // must remove triggers 312 deregisterTriggers(MondrianProperties.instance()); 313 314 return false; 315 } 316 317 } 318 319 /** 320 * Registers triggers for the following properties: 321 * <ul> 322 * <li>{@link MondrianProperties#ChooseAggregateByVolume} 323 * <li>{@link MondrianProperties#AggregateRules} 324 * <li>{@link MondrianProperties#AggregateRuleTag} 325 * <li>{@link MondrianProperties#ReadAggregates} 326 * </ul> 327 */ 328 private void registerTriggers() { 329 final MondrianProperties properties = MondrianProperties.instance(); 330 triggers = new Trigger[] { 331 332 // When the ordering AggStars property is changed, we must 333 // reorder them, so we create a trigger. 334 // There is no need to provide equals/hashCode methods for this 335 // Trigger since it is never explicitly removed. 336 new Trigger() { 337 public boolean isPersistent() { 338 return false; 339 } 340 public int phase() { 341 return Trigger.SECONDARY_PHASE; 342 } 343 public void execute(Property property, String value) { 344 if (AggTableManager.this.runTrigger()) { 345 reOrderAggStarList(); 346 } 347 } 348 }, 349 350 // Register to know when the Default resource/url has changed 351 // so that the default aggregate table recognition rules can 352 // be re-loaded. 353 // There is no need to provide equals/hashCode methods for this 354 // Trigger since it is never explicitly removed. 355 new Trigger() { 356 public boolean isPersistent() { 357 return false; 358 } 359 public int phase() { 360 return Trigger.SECONDARY_PHASE; 361 } 362 public void execute(Property property, String value) { 363 if (AggTableManager.this.runTrigger()) { 364 reLoadRolapStarAggregates(); 365 } 366 } 367 }, 368 369 // If the system started not using aggregates, i.e., the aggregate 370 // tables were not loaded, but then the property 371 // was changed to use aggregates, we must then load the aggregates 372 // if they were never loaded. 373 new Trigger() { 374 public boolean isPersistent() { 375 return false; 376 } 377 public int phase() { 378 return Trigger.SECONDARY_PHASE; 379 } 380 public void execute(Property property, String value) { 381 if (AggTableManager.this.runTrigger()) { 382 reLoadRolapStarAggregates(); 383 } 384 } 385 } 386 }; 387 388 // Note that for each AggTableManager theses triggers are 389 // added to the properties object. Each trigger has just 390 // been created and "knows" its AggTableManager instance. 391 // The triggers' hashCode and equals methods (those provided 392 // by the Object class) are used when removing the trigger. 393 properties.ChooseAggregateByVolume.addTrigger(triggers[0]); 394 properties.AggregateRules.addTrigger(triggers[1]); 395 properties.AggregateRuleTag.addTrigger(triggers[1]); 396 properties.ReadAggregates.addTrigger(triggers[2]); 397 } 398 399 private void deregisterTriggers(final MondrianProperties properties) { 400 // Remove this AggTableManager's instance's triggers. 401 properties.ChooseAggregateByVolume.removeTrigger(triggers[0]); 402 properties.AggregateRules.removeTrigger(triggers[1]); 403 properties.AggregateRuleTag.removeTrigger(triggers[1]); 404 properties.ReadAggregates.removeTrigger(triggers[2]); 405 } 406 407 private Collection<RolapStar> getStars() { 408 return schema.getStars(); 409 } 410 411 private void reOrderAggStarList() { 412 for (RolapStar star : getStars()) { 413 star.reOrderAggStarList(); 414 } 415 } 416 417 /** 418 * Returns a list containing every 419 * {@link mondrian.rolap.aggmatcher.ExplicitRules.Group} in every 420 * cubes in a given {@link RolapStar}. 421 */ 422 protected List<ExplicitRules.Group> getAggGroups(RolapStar star) { 423 List<ExplicitRules.Group> aggGroups = 424 new ArrayList<ExplicitRules.Group>(); 425 for (RolapCube cube : schema.getCubesWithStar(star)) { 426 if (cube.hasAggGroup() && cube.getAggGroup().hasRules()) { 427 aggGroups.add(cube.getAggGroup()); 428 } 429 } 430 return aggGroups; 431 } 432 433 /** 434 * This method mines the RolapStar and annotes the JdbcSchema.Table 435 * dbFactTable by creating JdbcSchema.Table.Column.Usage instances. For 436 * example, a measure in the RolapStar becomes a measure usage for the 437 * column with the same name and a RolapStar foreign key column becomes a 438 * foreign key usage for the column with the same name. 439 * 440 * @param dbFactTable 441 * @param star 442 * @param msgRecorder 443 */ 444 void bindToStar(final JdbcSchema.Table dbFactTable, 445 final RolapStar star, 446 final MessageRecorder msgRecorder) throws SQLException { 447 msgRecorder.pushContextName("AggTableManager.bindToStar"); 448 try { 449 // load columns 450 dbFactTable.load(); 451 452 dbFactTable.setTableUsageType(JdbcSchema.TableUsageType.FACT); 453 454 MondrianDef.RelationOrJoin relation = star.getFactTable().getRelation(); 455 String schema = null; 456 if (relation instanceof MondrianDef.Table) { 457 schema = ((MondrianDef.Table) relation).schema; 458 } 459 String tableName = dbFactTable.getName(); 460 String alias = null; 461 dbFactTable.table = new MondrianDef.Table(schema, tableName, alias); 462 463 for (JdbcSchema.Table.Column factColumn : dbFactTable.getColumns()) { 464 String cname = factColumn.getName(); 465 RolapStar.Column[] rcs = 466 star.getFactTable().lookupColumns(cname); 467 468 for (RolapStar.Column rc : rcs) { 469 // its a measure 470 if (rc instanceof RolapStar.Measure) { 471 RolapStar.Measure rm = (RolapStar.Measure) rc; 472 JdbcSchema.Table.Column.Usage usage = 473 factColumn.newUsage(JdbcSchema.UsageType.MEASURE); 474 usage.setSymbolicName(rm.getName()); 475 476 usage.setAggregator(rm.getAggregator()); 477 usage.rMeasure = rm; 478 } 479 } 480 481 // it still might be a foreign key 482 RolapStar.Table rTable = 483 star.getFactTable().findTableWithLeftJoinCondition(cname); 484 if (rTable != null) { 485 JdbcSchema.Table.Column.Usage usage = 486 factColumn.newUsage(JdbcSchema.UsageType.FOREIGN_KEY); 487 usage.setSymbolicName("FOREIGN_KEY"); 488 usage.rTable = rTable; 489 } else { 490 RolapStar.Column rColumn = 491 star.getFactTable().lookupColumn(cname); 492 if ((rColumn != null) && 493 !(rColumn instanceof RolapStar.Measure)) { 494 // Ok, maybe its used in a non-shared dimension 495 // This is a column in the fact table which is 496 // (not necessarily) a measure but is also not 497 // a foreign key to an external dimension table. 498 JdbcSchema.Table.Column.Usage usage = 499 factColumn.newUsage(JdbcSchema.UsageType.FOREIGN_KEY); 500 usage.setSymbolicName("FOREIGN_KEY"); 501 usage.rColumn = rColumn; 502 } 503 } 504 505 // warn if it has not been identified 506 if (!factColumn.hasUsage() && getLogger().isDebugEnabled()) { 507 String msg = mres.UnknownFactTableColumn.str( 508 msgRecorder.getContext(), 509 dbFactTable.getName(), 510 factColumn.getName()); 511 getLogger().debug(msg); 512 } 513 } 514 } finally { 515 msgRecorder.popContextName(); 516 } 517 } 518 } 519 520 // End AggTableManager.java