001 /* 002 // $Id: //open/mondrian/src/main/mondrian/rolap/aggmatcher/ExplicitRules.java#19 $ 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.MessageRecorder; 016 import mondrian.resource.MondrianResource; 017 018 import org.apache.log4j.Logger; 019 020 import java.io.PrintWriter; 021 import java.io.StringWriter; 022 import java.util.*; 023 import java.util.regex.Pattern; 024 025 /** 026 * A class containing a RolapCube's Aggregate tables exclude/include 027 * criteria. 028 * 029 * @author Richard M. Emberson 030 * @version $Id: //open/mondrian/src/main/mondrian/rolap/aggmatcher/ExplicitRules.java#19 $ 031 */ 032 public class ExplicitRules { 033 private static final Logger LOGGER = Logger.getLogger(ExplicitRules.class); 034 035 private static final MondrianResource mres = MondrianResource.instance(); 036 037 /** 038 * Returns whether the given is tableName explicitly excluded from 039 * consideration as a candidate aggregate table. 040 */ 041 public static boolean excludeTable( 042 final String tableName, 043 final List<Group> aggGroups) 044 { 045 for (Group group : aggGroups) { 046 if (group.excludeTable(tableName)) { 047 return true; 048 } 049 } 050 return false; 051 } 052 053 /** 054 * Returns the {@link TableDef} for a tableName that is a candidate 055 * aggregate table. If null is returned, then the default rules are used 056 * otherwise if not null, then the ExplicitRules.TableDef is used. 057 */ 058 public static ExplicitRules.TableDef getIncludeByTableDef( 059 final String tableName, 060 final List<Group> aggGroups) { 061 for (Group group : aggGroups) { 062 TableDef tableDef = group.getIncludeByTableDef(tableName); 063 if (tableDef != null) { 064 return tableDef; 065 } 066 } 067 return null; 068 } 069 070 /** 071 * This class forms a collection of aggregate table explicit rules for a 072 * given cube. 073 * 074 */ 075 public static class Group { 076 077 /** 078 * Make an ExplicitRules.Group for a given RolapCube given the 079 * MondrianDef.Cube associated with that cube. 080 */ 081 public static ExplicitRules.Group make( 082 final RolapCube cube, 083 final MondrianDef.Cube xmlCube) { 084 Group group = new Group(cube); 085 086 MondrianDef.Relation relation = xmlCube.fact; 087 088 if (relation instanceof MondrianDef.Table) { 089 MondrianDef.AggExclude[] aggExcludes = 090 ((MondrianDef.Table) relation).getAggExcludes(); 091 if (aggExcludes != null) { 092 for (MondrianDef.AggExclude aggExclude : aggExcludes) { 093 Exclude exclude = 094 ExplicitRules.make(aggExclude); 095 group.addExclude(exclude); 096 } 097 } 098 MondrianDef.AggTable[] aggTables = 099 ((MondrianDef.Table) relation).getAggTables(); 100 if (aggTables != null) { 101 for (MondrianDef.AggTable aggTable : aggTables) { 102 TableDef tableDef = TableDef.make(aggTable, group); 103 group.addTableDef(tableDef); 104 } 105 } 106 } else { 107 String msg = mres.CubeRelationNotTable.str( 108 cube.getName(), 109 relation.getClass().getName()); 110 LOGGER.warn(msg); 111 } 112 113 if (LOGGER.isDebugEnabled()) { 114 LOGGER.debug(Util.nl + group); 115 } 116 return group; 117 } 118 119 private final RolapCube cube; 120 private List<TableDef> tableDefs; 121 private List<Exclude> excludes; 122 123 public Group(final RolapCube cube) { 124 this.cube = cube; 125 this.excludes = Collections.emptyList(); 126 this.tableDefs = Collections.emptyList(); 127 } 128 129 /** 130 * Get the RolapCube associated with this Group. 131 */ 132 public RolapCube getCube() { 133 return cube; 134 } 135 136 /** 137 * Get the RolapStar associated with this Group's RolapCube. 138 */ 139 public RolapStar getStar() { 140 return getCube().getStar(); 141 } 142 143 /** 144 * Get the name of this Group (its the name of its RolapCube). 145 */ 146 public String getName() { 147 return getCube().getName(); 148 } 149 150 /** 151 * Are there any rules associated with this Group. 152 */ 153 public boolean hasRules() { 154 return (excludes != Collections.EMPTY_LIST) || 155 (tableDefs != Collections.EMPTY_LIST); 156 } 157 158 /** 159 * Add an exclude rule. 160 * 161 * @param exclude 162 */ 163 public void addExclude(final ExplicitRules.Exclude exclude) { 164 if (excludes == Collections.EMPTY_LIST) { 165 excludes = new ArrayList<Exclude>(); 166 } 167 excludes.add(exclude); 168 } 169 170 /** 171 * Add a name or pattern (table) rule. 172 * 173 * @param tableDef 174 */ 175 public void addTableDef(final ExplicitRules.TableDef tableDef) { 176 if (tableDefs == Collections.EMPTY_LIST) { 177 tableDefs = new ArrayList<TableDef>(); 178 } 179 tableDefs.add(tableDef); 180 } 181 182 /** 183 * Returns whether the given tableName is excluded. 184 */ 185 public boolean excludeTable(final String tableName) { 186 // See if the table is explicitly, by name, excluded 187 for (Exclude exclude : excludes) { 188 if (exclude.isExcluded(tableName)) { 189 return true; 190 } 191 } 192 return false; 193 } 194 195 /** 196 * Is the given tableName included either by exact name or by pattern. 197 */ 198 public ExplicitRules.TableDef getIncludeByTableDef( 199 final String tableName) { 200 // An exact match on a NameTableDef takes precedences over a 201 // fuzzy match on a PatternTableDef, so 202 // first look throught NameTableDef then PatternTableDef 203 for (ExplicitRules.TableDef tableDef : tableDefs) { 204 if (tableDef instanceof NameTableDef) { 205 if (tableDef.matches(tableName)) { 206 return tableDef; 207 } 208 } 209 } 210 for (ExplicitRules.TableDef tableDef : tableDefs) { 211 if (tableDef instanceof PatternTableDef) { 212 if (tableDef.matches(tableName)) { 213 return tableDef; 214 } 215 } 216 } 217 return null; 218 } 219 220 /** 221 * Get the database table name associated with this Group's RolapStar's 222 * fact table. 223 */ 224 public String getTableName() { 225 RolapStar.Table table = getStar().getFactTable(); 226 MondrianDef.Relation relation = table.getRelation(); 227 return relation.getAlias(); 228 } 229 230 /** 231 * Get the database schema name associated with this Group's RolapStar's 232 * fact table. 233 */ 234 public String getSchemaName() { 235 String schema = null; 236 237 RolapStar.Table table = getStar().getFactTable(); 238 MondrianDef.Relation relation = table.getRelation(); 239 240 if (relation instanceof MondrianDef.Table) { 241 MondrianDef.Table mtable = (MondrianDef.Table) relation; 242 schema = mtable.schema; 243 } 244 return schema; 245 } 246 /** 247 * Get the database catalog name associated with this Group's 248 * RolapStar's fact table. 249 * Note: this currently this always returns null. 250 */ 251 public String getCatalogName() { 252 return null; 253 } 254 255 /** 256 * Validate the content and structure of this Group. 257 */ 258 public void validate(final MessageRecorder msgRecorder) { 259 msgRecorder.pushContextName(getName()); 260 try { 261 for (ExplicitRules.TableDef tableDef : tableDefs) { 262 tableDef.validate(msgRecorder); 263 } 264 } finally { 265 msgRecorder.popContextName(); 266 } 267 } 268 269 public String toString() { 270 StringWriter sw = new StringWriter(256); 271 PrintWriter pw = new PrintWriter(sw); 272 print(pw, ""); 273 pw.flush(); 274 return sw.toString(); 275 } 276 277 public void print(final PrintWriter pw, final String prefix) { 278 pw.print(prefix); 279 pw.println("ExplicitRules.Group:"); 280 String subprefix = prefix + " "; 281 String subsubprefix = subprefix + " "; 282 283 pw.print(subprefix); 284 pw.print("name="); 285 pw.println(getStar().getFactTable().getRelation()); 286 287 pw.print(subprefix); 288 pw.println("TableDefs: ["); 289 for (ExplicitRules.TableDef tableDef : tableDefs) { 290 tableDef.print(pw, subsubprefix); 291 } 292 pw.print(subprefix); 293 pw.println("]"); 294 } 295 } 296 297 private static Exclude make(final MondrianDef.AggExclude aggExclude) { 298 return (aggExclude.getNameAttribute() != null) ? 299 new ExcludeName( 300 aggExclude.getNameAttribute(), 301 aggExclude.isIgnoreCase()) : 302 (Exclude) new ExcludePattern( 303 aggExclude.getPattern(), 304 aggExclude.isIgnoreCase()); 305 } 306 307 /** 308 * Interface of an Exclude type. There are two implementations, one that 309 * excludes by exact name match (as an option, ignore case) and the second 310 * that matches a regular expression. 311 */ 312 private interface Exclude { 313 /** 314 * Return true if the tableName is excluded. 315 * 316 * @param tableName Table name 317 * @return whether table name is excluded 318 */ 319 boolean isExcluded(final String tableName); 320 321 /** 322 * Validate that the exclude name matches the table pattern. 323 * 324 * @param msgRecorder Message recorder 325 */ 326 void validate(final MessageRecorder msgRecorder); 327 328 /** 329 * Prints this rule to a PrintWriter. 330 * 331 * @param pw PrintWriter 332 * @param prefix Line prefix, for indentation 333 */ 334 void print(final PrintWriter pw, final String prefix); 335 } 336 337 /** 338 * Implementation of Exclude which matches names exactly. 339 */ 340 private static class ExcludeName implements Exclude { 341 private final String name; 342 private final boolean ignoreCase; 343 344 private ExcludeName(final String name, final boolean ignoreCase) { 345 this.name = name; 346 this.ignoreCase = ignoreCase; 347 } 348 349 /** 350 * Returns the name to be matched. 351 */ 352 public String getName() { 353 return name; 354 } 355 356 /** 357 * Returns true if the matching can ignore case. 358 */ 359 public boolean isIgnoreCase() { 360 return ignoreCase; 361 } 362 363 public boolean isExcluded(final String tableName) { 364 return (this.ignoreCase) 365 ? this.name.equals(tableName) 366 : this.name.equalsIgnoreCase(tableName); 367 } 368 369 public void validate(final MessageRecorder msgRecorder) { 370 msgRecorder.pushContextName("ExcludeName"); 371 try { 372 String name = getName(); 373 checkAttributeString(msgRecorder, name, "name"); 374 375 /* 376 RME TODO 377 // If name does not match the PatternTableDef pattern, 378 // then issue warning. 379 // Why, because no table with the exclude's name will 380 // ever match the pattern, so the exclude is superfluous. 381 // This is best effort. 382 Pattern pattern = ExplicitRules.PatternTableDef.this.getPattern(); 383 boolean patternIgnoreCase = 384 ExplicitRules.PatternTableDef.this.isIgnoreCase(); 385 boolean ignoreCase = isIgnoreCase(); 386 387 // If pattern is ignoreCase and name is any case or pattern 388 // is not ignoreCase and name is not ignoreCase, then simply 389 // see if name matches. 390 // Else pattern in not ignoreCase and name is ignoreCase, 391 // then pattern could be "AB.*" and name "abc". 392 // Here "abc" would name, but not pattern - but who cares 393 if (patternIgnoreCase || ! ignoreCase) { 394 if (! pattern.matcher(name).matches()) { 395 msgRecorder.reportWarning( 396 mres.getSuperfluousExludeName( 397 msgRecorder.getContext(), 398 name, 399 pattern.pattern())); 400 } 401 } 402 */ 403 } finally { 404 msgRecorder.popContextName(); 405 } 406 } 407 408 public void print(final PrintWriter pw, final String prefix) { 409 pw.print(prefix); 410 pw.println("ExplicitRules.PatternTableDef.ExcludeName:"); 411 412 String subprefix = prefix + " "; 413 414 pw.print(subprefix); 415 pw.print("name="); 416 pw.println(this.name); 417 418 pw.print(subprefix); 419 pw.print("ignoreCase="); 420 pw.println(this.ignoreCase); 421 } 422 } 423 424 /** 425 * This class is a regular expression base name matching Exclude 426 * implementation. 427 */ 428 private static class ExcludePattern implements Exclude { 429 private final Pattern pattern; 430 431 private ExcludePattern( 432 final String pattern, 433 final boolean ignoreCase) { 434 this.pattern = (ignoreCase) ? 435 Pattern.compile(pattern, Pattern.CASE_INSENSITIVE) : 436 Pattern.compile(pattern); 437 } 438 439 public boolean isExcluded(final String tableName) { 440 return pattern.matcher(tableName).matches(); 441 } 442 443 public void validate(final MessageRecorder msgRecorder) { 444 msgRecorder.pushContextName("ExcludePattern"); 445 try { 446 checkAttributeString( 447 msgRecorder, 448 pattern.pattern(), 449 "pattern"); 450 //String context = msgRecorder.getContext(); 451 // Is there any way to determine if the exclude pattern 452 // is never a sub-set of the table pattern. 453 // I will have to think about this. 454 // Until then, this method is empty. 455 } finally { 456 msgRecorder.popContextName(); 457 } 458 } 459 460 public void print(final PrintWriter pw, final String prefix) { 461 pw.print(prefix); 462 pw.println("ExplicitRules.PatternTableDef.ExcludePattern:"); 463 464 String subprefix = prefix + " "; 465 466 pw.print(subprefix); 467 pw.print("pattern="); 468 pw.print(this.pattern.pattern()); 469 pw.print(":"); 470 pw.println(this.pattern.flags()); 471 } 472 } 473 474 /** 475 * This is the base class for the exact name based and name pattern based 476 * aggregate table mapping definitions. It contains the mappings for the 477 * fact count column, optional ignore columns, foreign key mappings, 478 * measure column mappings and level column mappings. 479 */ 480 public static abstract class TableDef { 481 482 /** 483 * Given a MondrianDef.AggTable instance create a TableDef instance 484 * which is either a NameTableDef or PatternTableDef. 485 */ 486 static ExplicitRules.TableDef make( 487 final MondrianDef.AggTable aggTable, 488 final ExplicitRules.Group group) { 489 return (aggTable instanceof MondrianDef.AggName) ? 490 ExplicitRules.NameTableDef.make( 491 (MondrianDef.AggName) aggTable, group) : 492 (ExplicitRules.TableDef) 493 ExplicitRules.PatternTableDef.make( 494 (MondrianDef.AggPattern) aggTable, group); 495 } 496 497 /** 498 * This method extracts information from the MondrianDef.AggTable and 499 * places it in the ExplicitRules.TableDef. This code is used for both 500 * the NameTableDef and PatternTableDef subclasses of TableDef (it 501 * extracts information common to both). 502 * 503 * @param tableDef 504 * @param aggTable 505 */ 506 private static void add( 507 final ExplicitRules.TableDef tableDef, 508 final MondrianDef.AggTable aggTable) { 509 510 if (aggTable.getAggFactCount() != null) { 511 tableDef.setFactCountName(aggTable.getAggFactCount().getColumnName()); 512 } 513 514 MondrianDef.AggIgnoreColumn[] ignores = 515 aggTable.getAggIgnoreColumns(); 516 517 if (ignores != null) { 518 for (MondrianDef.AggIgnoreColumn ignore : ignores) { 519 tableDef.addIgnoreColumnName(ignore.getColumnName()); 520 } 521 } 522 523 MondrianDef.AggForeignKey[] fks = aggTable.getAggForeignKeys(); 524 if (fks != null) { 525 for (MondrianDef.AggForeignKey fk : fks) { 526 tableDef.addFK(fk); 527 } 528 } 529 MondrianDef.AggMeasure[] measures = aggTable.getAggMeasures(); 530 if (measures != null) { 531 for (MondrianDef.AggMeasure measure : measures) { 532 addTo(tableDef, measure); 533 } 534 } 535 536 MondrianDef.AggLevel[] levels = aggTable.getAggLevels(); 537 if (levels != null) { 538 for (MondrianDef.AggLevel level : levels) { 539 addTo(tableDef, level); 540 } 541 } 542 } 543 private static void addTo( 544 final ExplicitRules.TableDef tableDef, 545 final MondrianDef.AggLevel aggLevel) { 546 addLevelTo(tableDef, 547 aggLevel.getNameAttribute(), 548 aggLevel.getColumnName()); 549 } 550 551 private static void addTo( 552 final ExplicitRules.TableDef tableDef, 553 final MondrianDef.AggMeasure aggMeasure) { 554 addMeasureTo(tableDef, 555 aggMeasure.getNameAttribute(), 556 aggMeasure.getColumn()); 557 } 558 559 public static void addLevelTo( 560 final ExplicitRules.TableDef tableDef, 561 final String name, 562 final String columnName) { 563 Level level = tableDef.new Level(name, columnName); 564 tableDef.add(level); 565 } 566 567 public static void addMeasureTo( 568 final ExplicitRules.TableDef tableDef, 569 final String name, 570 final String column) { 571 Measure measure = tableDef.new Measure(name, column); 572 tableDef.add(measure); 573 } 574 575 /** 576 * This class is used to map from a Level's symbolic name, 577 * [Time].[Year] to the aggregate table's column name, TIME_YEAR. 578 */ 579 class Level { 580 private final String name; 581 private final String columnName; 582 private RolapLevel rlevel; 583 584 Level(final String name, final String columnName) { 585 this.name = name; 586 this.columnName = columnName; 587 } 588 589 /** 590 * Get the symbolic name, the level name. 591 */ 592 public String getName() { 593 return name; 594 } 595 596 /** 597 * Get the foreign key column name of the aggregate table. 598 */ 599 public String getColumnName() { 600 return columnName; 601 } 602 603 /** 604 * Get the RolapLevel associated with level name. 605 */ 606 public RolapLevel getRolapLevel() { 607 return rlevel; 608 } 609 610 /** 611 * Validates a level's name. 612 * 613 * <p>The level name must be of the form 614 * <blockquote><code>[hierarchy usage name].[level name]</code></blockquote> 615 * 616 * This method checks that is of length 2, starts with a hierarchy and 617 * the "level name" exists. 618 */ 619 public void validate(final MessageRecorder msgRecorder) { 620 msgRecorder.pushContextName("Level"); 621 try { 622 String name = getName(); 623 String columnName = getColumnName(); 624 checkAttributeString(msgRecorder, name, "name"); 625 checkAttributeString(msgRecorder, columnName, "column"); 626 627 List<Id.Segment> names = Util.parseIdentifier(name); 628 // must be [hierarchy usage name].[level name] 629 if (names.size() != 2) { 630 msgRecorder.reportError( 631 mres.BadLevelNameFormat.str( 632 msgRecorder.getContext(), 633 name)); 634 } else { 635 636 RolapCube cube = ExplicitRules.TableDef.this.getCube(); 637 SchemaReader schemaReader = cube.getSchemaReader(); 638 RolapLevel level = 639 (RolapLevel) schemaReader.lookupCompound( 640 cube, 641 names, 642 false, 643 Category.Level); 644 if (level == null) { 645 Hierarchy hierarchy = (Hierarchy) 646 schemaReader.lookupCompound( 647 cube, 648 names.subList(0, 1), 649 false, 650 Category.Hierarchy); 651 if (hierarchy == null) { 652 msgRecorder.reportError( 653 mres.UnknownHierarchyName.str( 654 msgRecorder.getContext(), 655 names.get(0).name)); 656 } else { 657 msgRecorder.reportError( 658 mres.UnknownLevelName.str( 659 msgRecorder.getContext(), 660 names.get(0).name, 661 names.get(1).name)); 662 } 663 } 664 rlevel = level; 665 } 666 } finally { 667 msgRecorder.popContextName(); 668 } 669 } 670 671 public String toString() { 672 StringWriter sw = new StringWriter(256); 673 PrintWriter pw = new PrintWriter(sw); 674 print(pw, ""); 675 pw.flush(); 676 return sw.toString(); 677 } 678 679 public void print(final PrintWriter pw, final String prefix) { 680 pw.print(prefix); 681 pw.println("Level:"); 682 String subprefix = prefix + " "; 683 684 pw.print(subprefix); 685 pw.print("name="); 686 pw.println(this.name); 687 688 pw.print(subprefix); 689 pw.print("columnName="); 690 pw.println(this.columnName); 691 } 692 } 693 694 /** 695 * This class is used to map from a measure's symbolic name, 696 * [Measures].[Unit Sales] to the aggregate table's column 697 * name, UNIT_SALES_SUM. 698 */ 699 class Measure { 700 private final String name; 701 private String symbolicName; 702 private final String columnName; 703 private RolapStar.Measure rolapMeasure; 704 705 Measure(final String name, final String columnName) { 706 this.name = name; 707 this.columnName = columnName; 708 } 709 710 /** 711 * Get the symbolic name, the measure name, i.e., 712 * [Measures].[Unit Sales]. 713 */ 714 public String getName() { 715 return name; 716 } 717 718 /** 719 * Get the symbolic name, the measure name, i.e., [Unit Sales]. 720 */ 721 public String getSymbolicName() { 722 return symbolicName; 723 } 724 725 /** 726 * Get the aggregate table column name. 727 */ 728 public String getColumnName() { 729 return columnName; 730 } 731 732 /** 733 * Get the RolapStar.Measure associated with this symbolic name. 734 */ 735 public RolapStar.Measure getRolapStarMeasure() { 736 return rolapMeasure; 737 } 738 739 /** 740 * Validates a measure's name. 741 * 742 * <p>The measure name must be of the form 743 * <blockquote><code>[Measures].[measure name]</code></blockquote> 744 * 745 * <p>This method checks that is of length 2, starts 746 * with "Measures" and the "measure name" exists. 747 */ 748 public void validate(final MessageRecorder msgRecorder) { 749 msgRecorder.pushContextName("Measure"); 750 try { 751 String name = getName(); 752 String column = getColumnName(); 753 checkAttributeString(msgRecorder, name, "name"); 754 checkAttributeString(msgRecorder, column, "column"); 755 756 List<Id.Segment> names = Util.parseIdentifier(name); 757 if (names.size() != 2) { 758 msgRecorder.reportError( 759 mres.BadMeasureNameFormat.str( 760 msgRecorder.getContext(), 761 name)); 762 } else { 763 RolapCube cube = ExplicitRules.TableDef.this.getCube(); 764 SchemaReader schemaReader = cube.getSchemaReader(); 765 Member member = (Member) schemaReader.lookupCompound( 766 cube, 767 names, 768 false, 769 Category.Member); 770 if (member == null) { 771 if (! names.get(0).name.equals("Measures")) { 772 msgRecorder.reportError( 773 mres.BadMeasures.str( 774 msgRecorder.getContext(), 775 names.get(0).name)); 776 } else { 777 msgRecorder.reportError( 778 mres.UnknownMeasureName.str( 779 msgRecorder.getContext(), 780 names.get(1).name)); 781 } 782 } 783 RolapStar star = cube.getStar(); 784 rolapMeasure = 785 star.getFactTable().lookupMeasureByName( 786 cube.getName(), names.get(1).name); 787 if (rolapMeasure == null) { 788 msgRecorder.reportError( 789 mres.BadMeasureName.str( 790 msgRecorder.getContext(), 791 names.get(1).name, 792 cube.getName())); 793 } 794 symbolicName = names.get(1).name; 795 } 796 } finally { 797 msgRecorder.popContextName(); 798 } 799 } 800 801 public String toString() { 802 StringWriter sw = new StringWriter(256); 803 PrintWriter pw = new PrintWriter(sw); 804 print(pw, ""); 805 pw.flush(); 806 return sw.toString(); 807 } 808 809 public void print(final PrintWriter pw, final String prefix) { 810 pw.print(prefix); 811 pw.println("Measure:"); 812 String subprefix = prefix + " "; 813 814 pw.print(subprefix); 815 pw.print("name="); 816 pw.println(this.name); 817 818 pw.print(subprefix); 819 pw.print("column="); 820 pw.println(this.columnName); 821 } 822 } 823 824 private static int idCount = 0; 825 private static int nextId() { 826 return idCount++; 827 } 828 829 protected final int id; 830 protected final boolean ignoreCase; 831 protected final ExplicitRules.Group aggGroup; 832 protected String factCountName; 833 protected List<String> ignoreColumnNames; 834 private Map<String, String> foreignKeyMap; 835 private List<Level> levels; 836 private List<Measure> measures; 837 838 protected TableDef( 839 final boolean ignoreCase, 840 final ExplicitRules.Group aggGroup) { 841 this.id = nextId(); 842 this.ignoreCase = ignoreCase; 843 this.aggGroup = aggGroup; 844 this.foreignKeyMap = Collections.emptyMap(); 845 this.levels = Collections.emptyList(); 846 this.measures = Collections.emptyList(); 847 this.ignoreColumnNames = Collections.emptyList(); 848 } 849 850 /** 851 * TODO: This does not seemed to be used anywhere??? 852 */ 853 public int getId() { 854 return this.id; 855 } 856 857 /** 858 * Return true if this name/pattern matching ignores case. 859 */ 860 public boolean isIgnoreCase() { 861 return this.ignoreCase; 862 } 863 864 /** 865 * Get the RolapStar associated with this cube. 866 */ 867 public RolapStar getStar() { 868 return getAggGroup().getStar(); 869 } 870 871 /** 872 * Get the Group with which is a part. 873 */ 874 public ExplicitRules.Group getAggGroup() { 875 return this.aggGroup; 876 } 877 878 /** 879 * Get the name of the fact count column. 880 */ 881 protected String getFactCountName() { 882 return factCountName; 883 } 884 885 /** 886 * Set the name of the fact count column. 887 * 888 * @param factCountName 889 */ 890 protected void setFactCountName(final String factCountName) { 891 this.factCountName = factCountName; 892 } 893 894 /** 895 * Get an Iterator over all ignore column name entries. 896 */ 897 protected Iterator<String> getIgnoreColumnNames() { 898 return ignoreColumnNames.iterator(); 899 } 900 901 /** 902 * Gets all level mappings. 903 */ 904 public List<Level> getLevels() { 905 return levels; 906 } 907 908 /** 909 * Gets all level mappings. 910 */ 911 public List<Measure> getMeasures() { 912 return measures; 913 } 914 915 /** 916 * Get Matcher for ignore columns. 917 */ 918 protected Recognizer.Matcher getIgnoreMatcher() { 919 return new Recognizer.Matcher() { 920 public boolean matches(final String name) { 921 for (Iterator<String> it = 922 ExplicitRules.TableDef.this.getIgnoreColumnNames(); 923 it.hasNext();) { 924 String ignoreName = it.next(); 925 if (ignoreName.equals(name)) { 926 return true; 927 } 928 } 929 return false; 930 } 931 }; 932 } 933 934 /** 935 * Get Matcher for the fact count column. 936 */ 937 protected Recognizer.Matcher getFactCountMatcher() { 938 return new Recognizer.Matcher() { 939 public boolean matches(String name) { 940 // Match is case insensitive 941 final String factCountName = TableDef.this.factCountName; 942 return factCountName != null && 943 factCountName.equalsIgnoreCase(name); 944 } 945 }; 946 } 947 948 /** 949 * Get the RolapCube associated with this mapping. 950 */ 951 RolapCube getCube() { 952 return aggGroup.getCube(); 953 } 954 955 /** 956 * Checks that ALL of the columns in the dbTable have a mapping in the 957 * tableDef. 958 * 959 * <p>It is an error if there is a column that does not have a mapping. 960 */ 961 public boolean columnsOK( 962 final RolapStar star, 963 final JdbcSchema.Table dbFactTable, 964 final JdbcSchema.Table dbTable, 965 final MessageRecorder msgRecorder) 966 { 967 Recognizer cb = 968 new ExplicitRecognizer( 969 this, star, getCube(), dbFactTable, dbTable, msgRecorder); 970 return cb.check(); 971 } 972 973 /** 974 * Adds the name of an aggregate table column that is to be ignored. 975 */ 976 protected void addIgnoreColumnName(final String ignoreName) { 977 if (this.ignoreColumnNames == Collections.EMPTY_LIST) { 978 this.ignoreColumnNames = new ArrayList<String>(); 979 } 980 this.ignoreColumnNames.add(ignoreName); 981 } 982 983 /** 984 * Add foreign key mapping entry (maps from fact table foreign key 985 * column name to aggregate table foreign key column name). 986 */ 987 protected void addFK(final MondrianDef.AggForeignKey fk) { 988 if (this.foreignKeyMap == Collections.EMPTY_MAP) { 989 this.foreignKeyMap = new HashMap<String, String>(); 990 } 991 this.foreignKeyMap.put(fk.getFactFKColumnName(), 992 fk.getAggregateFKColumnName()); 993 } 994 995 /** 996 * Get the name of the aggregate table's foreign key column that matches 997 * the base fact table's foreign key column or return null. 998 */ 999 protected String getAggregateFK(final String baseFK) { 1000 return this.foreignKeyMap.get(baseFK); 1001 } 1002 1003 /** 1004 * Adds a Level. 1005 */ 1006 protected void add(final Level level) { 1007 if (this.levels == Collections.EMPTY_LIST) { 1008 this.levels = new ArrayList<Level>(); 1009 } 1010 this.levels.add(level); 1011 } 1012 1013 /** 1014 * Adds a Measure. 1015 */ 1016 protected void add(final Measure measure) { 1017 if (this.measures == Collections.EMPTY_LIST) { 1018 this.measures = new ArrayList<Measure>(); 1019 } 1020 this.measures.add(measure); 1021 } 1022 1023 /** 1024 * Does the TableDef match a table with name tableName. 1025 */ 1026 public abstract boolean matches(final String tableName); 1027 1028 /** 1029 * Validate the Levels and Measures, also make sure each definition 1030 * is different, both name and column. 1031 */ 1032 public void validate(final MessageRecorder msgRecorder) { 1033 msgRecorder.pushContextName("TableDef"); 1034 try { 1035 // used to detect duplicates 1036 Map<String, Object> namesToObjects = 1037 new HashMap<String, Object>(); 1038 // used to detect duplicates 1039 Map<String, Object> columnsToObjects = 1040 new HashMap<String, Object>(); 1041 1042 for (Level level : levels) { 1043 level.validate(msgRecorder); 1044 1045 // Is the level name a duplicate 1046 if (namesToObjects.containsKey(level.getName())) { 1047 msgRecorder.reportError( 1048 mres.DuplicateLevelNames.str( 1049 msgRecorder.getContext(), 1050 level.getName())); 1051 } else { 1052 namesToObjects.put(level.getName(), level); 1053 } 1054 1055 // Is the level foreign key name a duplicate 1056 if (columnsToObjects.containsKey(level.getColumnName())) { 1057 Level l = (Level) 1058 columnsToObjects.get(level.getColumnName()); 1059 msgRecorder.reportError( 1060 mres.DuplicateLevelColumnNames.str( 1061 msgRecorder.getContext(), 1062 level.getName(), 1063 l.getName(), 1064 level.getColumnName())); 1065 } else { 1066 columnsToObjects.put(level.getColumnName(), level); 1067 } 1068 } 1069 1070 // reset names map, but keep the columns from levels 1071 namesToObjects.clear(); 1072 for (Measure measure : measures) { 1073 measure.validate(msgRecorder); 1074 1075 if (namesToObjects.containsKey(measure.getName())) { 1076 msgRecorder.reportError( 1077 mres.DuplicateMeasureNames.str( 1078 msgRecorder.getContext(), 1079 measure.getName())); 1080 continue; 1081 } else { 1082 namesToObjects.put(measure.getName(), measure); 1083 } 1084 1085 if (columnsToObjects.containsKey(measure.getColumnName())) { 1086 Object o = 1087 columnsToObjects.get(measure.getColumnName()); 1088 if (o instanceof Measure) { 1089 Measure m = (Measure) o; 1090 msgRecorder.reportError( 1091 mres.DuplicateMeasureColumnNames.str( 1092 msgRecorder.getContext(), 1093 measure.getName(), 1094 m.getName(), 1095 measure.getColumnName())); 1096 } else { 1097 Level l = (Level) o; 1098 msgRecorder.reportError( 1099 mres.DuplicateLevelMeasureColumnNames.str( 1100 msgRecorder.getContext(), 1101 l.getName(), 1102 measure.getName(), 1103 measure.getColumnName())); 1104 } 1105 1106 } else { 1107 columnsToObjects.put(measure.getColumnName(), measure); 1108 } 1109 } 1110 1111 // reset both 1112 namesToObjects.clear(); 1113 columnsToObjects.clear(); 1114 1115 // Make sure that the base fact table foreign key names match 1116 // real columns 1117 RolapStar star = getStar(); 1118 RolapStar.Table factTable = star.getFactTable(); 1119 String tableName = factTable.getAlias(); 1120 for (Map.Entry<String, String> e : foreignKeyMap.entrySet()) { 1121 String baseFKName = e.getKey(); 1122 String aggFKName = e.getValue(); 1123 1124 if (namesToObjects.containsKey(baseFKName)) { 1125 msgRecorder.reportError( 1126 mres.DuplicateFactForeignKey.str( 1127 msgRecorder.getContext(), 1128 baseFKName, 1129 aggFKName)); 1130 } else { 1131 namesToObjects.put(baseFKName, aggFKName); 1132 } 1133 if (columnsToObjects.containsKey(aggFKName)) { 1134 msgRecorder.reportError( 1135 mres.DuplicateFactForeignKey.str( 1136 msgRecorder.getContext(), 1137 baseFKName, 1138 aggFKName)); 1139 } else { 1140 columnsToObjects.put(aggFKName, baseFKName); 1141 } 1142 1143 MondrianDef.Column c = 1144 new MondrianDef.Column(tableName, baseFKName); 1145 if (factTable.findTableWithLeftCondition(c) == null) { 1146 msgRecorder.reportError( 1147 mres.UnknownLeftJoinCondition.str( 1148 msgRecorder.getContext(), 1149 tableName, 1150 baseFKName)); 1151 } 1152 } 1153 } finally { 1154 msgRecorder.popContextName(); 1155 } 1156 } 1157 1158 public String toString() { 1159 StringWriter sw = new StringWriter(256); 1160 PrintWriter pw = new PrintWriter(sw); 1161 print(pw, ""); 1162 pw.flush(); 1163 return sw.toString(); 1164 } 1165 1166 public void print(final PrintWriter pw, final String prefix) { 1167 String subprefix = prefix + " "; 1168 String subsubprefix = subprefix + " "; 1169 1170 pw.print(subprefix); 1171 pw.print("id="); 1172 pw.println(this.id); 1173 1174 pw.print(subprefix); 1175 pw.print("ignoreCase="); 1176 pw.println(this.ignoreCase); 1177 1178 pw.print(subprefix); 1179 pw.println("Levels: ["); 1180 for (Level level : this.levels) { 1181 level.print(pw, subsubprefix); 1182 } 1183 pw.print(subprefix); 1184 pw.println("]"); 1185 1186 pw.print(subprefix); 1187 pw.println("Measures: ["); 1188 for (Measure measure : this.measures) { 1189 measure.print(pw, subsubprefix); 1190 } 1191 pw.print(subprefix); 1192 pw.println("]"); 1193 } 1194 } 1195 1196 static class NameTableDef extends ExplicitRules.TableDef { 1197 /** 1198 * Makes a NameTableDef from the catalog schema. 1199 */ 1200 static ExplicitRules.NameTableDef make( 1201 final MondrianDef.AggName aggName, 1202 final ExplicitRules.Group group) 1203 { 1204 ExplicitRules.NameTableDef name = 1205 new ExplicitRules.NameTableDef( 1206 aggName.getNameAttribute(), 1207 aggName.isIgnoreCase(), 1208 group); 1209 1210 ExplicitRules.TableDef.add(name, aggName); 1211 1212 return name; 1213 } 1214 1215 private final String name; 1216 public NameTableDef( 1217 final String name, 1218 final boolean ignoreCase, 1219 final ExplicitRules.Group group) { 1220 super(ignoreCase, group); 1221 this.name = name; 1222 } 1223 1224 /** 1225 * Does the given tableName match this NameTableDef (either exact match 1226 * or, if set, a case insensitive match). 1227 */ 1228 public boolean matches(final String tableName) { 1229 return (this.ignoreCase) ? 1230 this.name.equals(tableName) : 1231 this.name.equalsIgnoreCase(tableName); 1232 } 1233 1234 /** 1235 * Validate name and base class. 1236 * 1237 * @param msgRecorder 1238 */ 1239 public void validate(final MessageRecorder msgRecorder) { 1240 msgRecorder.pushContextName("NameTableDef"); 1241 try { 1242 checkAttributeString(msgRecorder, name, "name"); 1243 1244 super.validate(msgRecorder); 1245 } finally { 1246 msgRecorder.popContextName(); 1247 } 1248 } 1249 1250 public void print(final PrintWriter pw, final String prefix) { 1251 pw.print(prefix); 1252 pw.println("ExplicitRules.NameTableDef:"); 1253 super.print(pw, prefix); 1254 1255 String subprefix = prefix + " "; 1256 1257 pw.print(subprefix); 1258 pw.print("name="); 1259 pw.println(this.name); 1260 1261 } 1262 } 1263 1264 /** 1265 * This class matches candidate aggregate table name with a pattern. 1266 */ 1267 public static class PatternTableDef extends ExplicitRules.TableDef { 1268 1269 /** 1270 * Make a PatternTableDef from the catalog schema. 1271 */ 1272 static ExplicitRules.PatternTableDef make( 1273 final MondrianDef.AggPattern aggPattern, 1274 final ExplicitRules.Group group) { 1275 1276 ExplicitRules.PatternTableDef pattern = 1277 new ExplicitRules.PatternTableDef( 1278 aggPattern.getPattern(), 1279 aggPattern.isIgnoreCase(), 1280 group); 1281 1282 MondrianDef.AggExclude[] excludes = aggPattern.getAggExcludes(); 1283 if (excludes != null) { 1284 for (MondrianDef.AggExclude exclude1 : excludes) { 1285 Exclude exclude = ExplicitRules.make(exclude1); 1286 pattern.add(exclude); 1287 } 1288 } 1289 1290 ExplicitRules.TableDef.add(pattern, aggPattern); 1291 1292 return pattern; 1293 } 1294 1295 private final Pattern pattern; 1296 private List<Exclude> excludes; 1297 1298 public PatternTableDef( 1299 final String pattern, 1300 final boolean ignoreCase, 1301 final ExplicitRules.Group group) { 1302 super(ignoreCase, group); 1303 this.pattern = (this.ignoreCase) 1304 ? Pattern.compile(pattern, Pattern.CASE_INSENSITIVE) 1305 : Pattern.compile(pattern); 1306 this.excludes = Collections.emptyList(); 1307 } 1308 1309 /** 1310 * Get the Pattern. 1311 */ 1312 public Pattern getPattern() { 1313 return pattern; 1314 } 1315 1316 /** 1317 * Get an Iterator over the list of Excludes. 1318 */ 1319 public List<Exclude> getExcludes() { 1320 return excludes; 1321 } 1322 1323 /** 1324 * Add an Exclude. 1325 * 1326 * @param exclude 1327 */ 1328 private void add(final Exclude exclude) { 1329 if (this.excludes == Collections.EMPTY_LIST) { 1330 this.excludes = new ArrayList<Exclude>(); 1331 } 1332 this.excludes.add(exclude); 1333 } 1334 1335 /** 1336 * Return true if the tableName 1) matches the pattern and 2) is not 1337 * matched by any of the Excludes. 1338 */ 1339 public boolean matches(final String tableName) { 1340 if (! pattern.matcher(tableName).matches()) { 1341 return false; 1342 } else { 1343 for (Exclude exclude : getExcludes()) { 1344 if (exclude.isExcluded(tableName)) { 1345 return false; 1346 } 1347 } 1348 return true; 1349 } 1350 } 1351 1352 /** 1353 * Validate excludes and base class. 1354 */ 1355 public void validate(final MessageRecorder msgRecorder) { 1356 msgRecorder.pushContextName("PatternTableDef"); 1357 try { 1358 checkAttributeString(msgRecorder, pattern.pattern(), "pattern"); 1359 1360 for (Exclude exclude : getExcludes()) { 1361 exclude.validate(msgRecorder); 1362 } 1363 super.validate(msgRecorder); 1364 } finally { 1365 msgRecorder.popContextName(); 1366 } 1367 } 1368 1369 public void print(final PrintWriter pw, final String prefix) { 1370 pw.print(prefix); 1371 pw.println("ExplicitRules.PatternTableDef:"); 1372 super.print(pw, prefix); 1373 1374 String subprefix = prefix + " "; 1375 String subsubprefix = subprefix + " "; 1376 1377 pw.print(subprefix); 1378 pw.print("pattern="); 1379 pw.print(this.pattern.pattern()); 1380 pw.print(":"); 1381 pw.println(this.pattern.flags()); 1382 1383 pw.print(subprefix); 1384 pw.println("Excludes: ["); 1385 Iterator<Exclude> it = this.excludes.iterator(); 1386 while (it.hasNext()) { 1387 Exclude exclude = it.next(); 1388 exclude.print(pw, subsubprefix); 1389 } 1390 pw.print(subprefix); 1391 pw.println("]"); 1392 1393 } 1394 } 1395 1396 /** 1397 * Helper method used to determine if an attribute with name attrName has a 1398 * non-empty value. 1399 * 1400 * @param msgRecorder 1401 * @param attrValue 1402 * @param attrName 1403 */ 1404 private static void checkAttributeString( 1405 final MessageRecorder msgRecorder, 1406 final String attrValue, 1407 final String attrName) { 1408 if (attrValue == null) { 1409 msgRecorder.reportError(mres.NullAttributeString.str( 1410 msgRecorder.getContext(), 1411 attrName)); 1412 } else if (attrValue.length() == 0) { 1413 msgRecorder.reportError(mres.EmptyAttributeString.str( 1414 msgRecorder.getContext(), 1415 attrName)); 1416 } 1417 } 1418 1419 1420 private ExplicitRules() { 1421 } 1422 } 1423 1424 // End ExplicitRules.java