001 /* 002 // $Id: //open/mondrian/src/main/mondrian/rolap/RolapCube.java#133 $ 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, 10 August, 2001 012 */ 013 014 package mondrian.rolap; 015 016 import mondrian.mdx.MdxVisitorImpl; 017 import mondrian.mdx.MemberExpr; 018 import mondrian.olap.*; 019 import mondrian.resource.MondrianResource; 020 import mondrian.rolap.aggmatcher.ExplicitRules; 021 import mondrian.rolap.cache.SoftSmartCache; 022 import org.apache.log4j.Logger; 023 import org.eigenbase.xom.*; 024 import org.eigenbase.xom.Parser; 025 026 import java.lang.reflect.Constructor; 027 import java.util.*; 028 029 /** 030 * <code>RolapCube</code> implements {@link Cube} for a ROLAP database. 031 * 032 * @author jhyde 033 * @since 10 August, 2001 034 * @version $Id: //open/mondrian/src/main/mondrian/rolap/RolapCube.java#133 $ 035 */ 036 public class RolapCube extends CubeBase { 037 038 private static final Logger LOGGER = Logger.getLogger(RolapCube.class); 039 040 private final RolapSchema schema; 041 private final RolapHierarchy measuresHierarchy; 042 043 /** For SQL generator. Fact table. */ 044 final MondrianDef.Relation fact; 045 046 /** Schema reader which can see this cube and nothing else. */ 047 private SchemaReader schemaReader; 048 049 /** 050 * List of calculated members. 051 */ 052 private Formula[] calculatedMembers; 053 054 /** 055 * Role based cache of calculated members 056 */ 057 private final SoftSmartCache<Role, List<Member>> roleToAccessibleCalculatedMembers = 058 new SoftSmartCache<Role, List<Member>>(); 059 060 /** 061 * List of named sets. 062 */ 063 private Formula[] namedSets; 064 065 /** Contains {@link HierarchyUsage}s for this cube */ 066 private final List<HierarchyUsage> hierarchyUsages; 067 068 private RolapStar star; 069 private ExplicitRules.Group aggGroup; 070 071 /** 072 * True if the cube is being created while loading the schema 073 */ 074 private boolean load; 075 076 private final Map<Hierarchy, HierarchyUsage> firstUsageMap = 077 new HashMap<Hierarchy, HierarchyUsage>(); 078 079 /** 080 * Refers {@link RolapCubeUsages} if this is a virtual cube 081 */ 082 private RolapCubeUsages cubeUsages; 083 084 /** 085 * Private constructor used by both normal cubes and virtual cubes. 086 * 087 * @param schema Schema cube belongs to 088 * @param name Name of cube 089 * @param caption Caption 090 * @param fact Definition of fact table 091 */ 092 private RolapCube( 093 RolapSchema schema, 094 MondrianDef.Schema xmlSchema, 095 String name, 096 String caption, 097 boolean isCache, 098 MondrianDef.Relation fact, 099 MondrianDef.CubeDimension[] dimensions, 100 boolean load) 101 { 102 super(name, new RolapDimension[dimensions.length + 1]); 103 104 this.schema = schema; 105 this.caption = caption; 106 this.fact = fact; 107 this.hierarchyUsages = new ArrayList<HierarchyUsage>(); 108 this.calculatedMembers = new Formula[0]; 109 this.namedSets = new Formula[0]; 110 this.load = load; 111 112 if (! isVirtual()) { 113 this.star = schema.getRolapStarRegistry().getOrCreateStar(fact); 114 // only set if different from default (so that if two cubes share 115 // the same fact table, either can turn off caching and both are 116 // effected). 117 if (! isCache) { 118 star.setCacheAggregations(isCache); 119 } 120 } 121 122 if (getLogger().isDebugEnabled()) { 123 if (isVirtual()) { 124 getLogger().debug("RolapCube<init>: virtual cube=" + this.name); 125 } else { 126 getLogger().debug("RolapCube<init>: cube=" + this.name); 127 } 128 } 129 130 RolapDimension measuresDimension = new RolapDimension( 131 schema, 132 Dimension.MEASURES_NAME, 133 DimensionType.MeasuresDimension, 134 false); 135 136 this.dimensions[0] = measuresDimension; 137 138 this.measuresHierarchy = measuresDimension.newHierarchy(null, false); 139 140 if (!Util.isEmpty(xmlSchema.measuresCaption)) { 141 measuresDimension.setCaption(xmlSchema.measuresCaption); 142 this.measuresHierarchy.setCaption(xmlSchema.measuresCaption); 143 } 144 145 for (int i = 0; i < dimensions.length; i++) { 146 MondrianDef.CubeDimension xmlCubeDimension = dimensions[i]; 147 // Look up usages of shared dimensions in the schema before 148 // consulting the XML schema (which may be null). 149 RolapCubeDimension dimension = 150 getOrCreateDimension(xmlCubeDimension, schema, xmlSchema, i + 1); 151 if (getLogger().isDebugEnabled()) { 152 getLogger().debug("RolapCube<init>: dimension=" 153 + dimension.getName()); 154 } 155 this.dimensions[i + 1] = dimension; 156 157 if (! isVirtual()) { 158 createUsages(dimension, xmlCubeDimension); 159 } 160 161 // the register Dimension call was moved here 162 // to keep the RolapStar in sync with the realiasing 163 // within the RolapCubeHierarchy objects. 164 registerDimension(dimension); 165 } 166 167 schema.addCube(this); 168 } 169 170 /** 171 * Creates a <code>RolapCube</code> from a regular cube. 172 */ 173 RolapCube( 174 RolapSchema schema, 175 MondrianDef.Schema xmlSchema, 176 MondrianDef.Cube xmlCube, 177 boolean load) 178 { 179 this( 180 schema, xmlSchema, xmlCube.name, xmlCube.caption, xmlCube.cache, 181 xmlCube.fact, xmlCube.dimensions, load); 182 183 if (fact == null) { 184 throw Util.newError( 185 "Must specify fact table of cube '" + 186 getName() + "'"); 187 } 188 189 if (fact.getAlias() == null) { 190 throw Util.newError( 191 "Must specify alias for fact table of cube '" + 192 getName() + "'"); 193 } 194 195 // since MondrianDef.Measure and MondrianDef.VirtualCubeMeasure 196 // can not be treated as the same, measure creation can not be 197 // done in a common constructor. 198 RolapLevel measuresLevel = this.measuresHierarchy.newMeasuresLevel(); 199 200 List<RolapMember> measureList = 201 new ArrayList<RolapMember>(xmlCube.measures.length); 202 Member defaultMeasure = null; 203 for (int i = 0; i < xmlCube.measures.length; i++) { 204 MondrianDef.Measure xmlMeasure = xmlCube.measures[i]; 205 MondrianDef.Expression measureExp; 206 if (xmlMeasure.column != null) { 207 if (xmlMeasure.measureExp != null) { 208 throw MondrianResource.instance(). 209 BadMeasureSource.ex( 210 xmlCube.name, xmlMeasure.name); 211 } 212 measureExp = new MondrianDef.Column( 213 fact.getAlias(), xmlMeasure.column); 214 } else if (xmlMeasure.measureExp != null) { 215 measureExp = xmlMeasure.measureExp; 216 } else { 217 throw MondrianResource.instance(). 218 BadMeasureSource.ex( 219 xmlCube.name, xmlMeasure.name); 220 } 221 222 // Validate aggregator name. Substitute deprecated "distinct count" 223 // with modern "distinct-count". 224 String aggregator = xmlMeasure.aggregator; 225 if (aggregator.equals("distinct count")) { 226 aggregator = RolapAggregator.DistinctCount.getName(); 227 } 228 final RolapBaseCubeMeasure measure = new RolapBaseCubeMeasure( 229 this, null, measuresLevel, xmlMeasure.name, 230 xmlMeasure.formatString, measureExp, 231 aggregator, xmlMeasure.datatype); 232 measureList.add(measure); 233 if (Util.equalName(measure.getName(),xmlCube.defaultMeasure)) { 234 defaultMeasure = measure; 235 } 236 237 try { 238 CellFormatter cellFormatter = 239 getCellFormatter(xmlMeasure.formatter); 240 if (cellFormatter != null) { 241 measure.setFormatter(cellFormatter); 242 } 243 } catch (Exception e) { 244 throw MondrianResource.instance().CellFormatterLoadFailed.ex( 245 xmlMeasure.formatter, measure.getUniqueName(), e); 246 } 247 248 // Set member's caption, if present. 249 if (!Util.isEmpty(xmlMeasure.caption)) { 250 // there is a special caption string 251 measure.setProperty( 252 Property.CAPTION.name, 253 xmlMeasure.caption); 254 } 255 256 // Set member's visibility, default true. 257 Boolean visible = xmlMeasure.visible; 258 if (visible == null) { 259 visible = Boolean.TRUE; 260 } 261 measure.setProperty(Property.VISIBLE.name, visible); 262 263 List<String> propNames = new ArrayList<String>(); 264 List<String> propExprs = new ArrayList<String>(); 265 validateMemberProps( 266 xmlMeasure.memberProperties, propNames, propExprs, 267 xmlMeasure.name); 268 int ordinal = i; 269 for (int j = 0; j < propNames.size(); j++) { 270 String propName = propNames.get(j); 271 final Object propExpr = propExprs.get(j); 272 measure.setProperty(propName, propExpr); 273 if (propName.equals(Property.MEMBER_ORDINAL.name) 274 && propExpr instanceof String) { 275 final String expr = (String) propExpr; 276 if (expr.startsWith("\"") 277 && expr.endsWith("\"")) { 278 try { 279 ordinal = 280 Integer.valueOf( 281 expr.substring(1, expr.length() - 1)); 282 } catch (NumberFormatException e) { 283 Util.discard(e); 284 } 285 } 286 } 287 } 288 measure.setOrdinal(ordinal); 289 } 290 291 setMeasuresHierarchyMemberReader( 292 new CacheMemberReader( 293 new MeasureMemberSource(this.measuresHierarchy, measureList))); 294 295 this.measuresHierarchy.setDefaultMember(defaultMeasure); 296 init(xmlCube.dimensions); 297 init(xmlCube, measureList); 298 299 setMeasuresHierarchyMemberReader( 300 new CacheMemberReader( 301 new MeasureMemberSource(this.measuresHierarchy, measureList))); 302 303 checkOrdinals(xmlCube.name, measureList); 304 loadAggGroup(xmlCube); 305 } 306 307 /** 308 * this method makes sure that the schemaReader cache is invalidated. 309 * problems can occur if the measure hierarchy member reader is out 310 * of sync with the cache. 311 * 312 * @param memberReader new member reader for measures hierarchy 313 */ 314 private void setMeasuresHierarchyMemberReader(MemberReader memberReader) { 315 this.measuresHierarchy.setMemberReader(memberReader); 316 // this invalidates any cached schema reader 317 this.schemaReader = null; 318 } 319 320 /** 321 * Given the name of a cell formatter class, returns a cell formatter. 322 * If class name is null, returns null. 323 * 324 * @param cellFormatterClassName Name of cell formatter class 325 * @return Cell formatter or null 326 * @throws Exception if class cannot be instantiated 327 */ 328 @SuppressWarnings({"unchecked"}) 329 static CellFormatter getCellFormatter( 330 String cellFormatterClassName) 331 throws Exception 332 { 333 if (Util.isEmpty(cellFormatterClassName)) { 334 return null; 335 } 336 Class<CellFormatter> clazz = 337 (Class<CellFormatter>) 338 Class.forName(cellFormatterClassName); 339 Constructor<CellFormatter> ctor = clazz.getConstructor(); 340 return ctor.newInstance(); 341 } 342 343 /** 344 * Creates a <code>RolapCube</code> from a virtual cube. 345 */ 346 RolapCube( 347 RolapSchema schema, 348 MondrianDef.Schema xmlSchema, 349 MondrianDef.VirtualCube xmlVirtualCube, 350 boolean load) 351 { 352 this(schema, xmlSchema, xmlVirtualCube.name, xmlVirtualCube.caption, 353 true, null, xmlVirtualCube.dimensions, load); 354 355 356 // Since MondrianDef.Measure and MondrianDef.VirtualCubeMeasure cannot 357 // be treated as the same, measure creation cannot be done in a common 358 // constructor. 359 RolapLevel measuresLevel = this.measuresHierarchy.newMeasuresLevel(); 360 361 // Recreate CalculatedMembers, as the original members point to 362 // incorrect dimensional ordinals for the virtual cube. 363 List<RolapVirtualCubeMeasure> origMeasureList = 364 new ArrayList<RolapVirtualCubeMeasure>(); 365 List<MondrianDef.CalculatedMember> origCalcMeasureList = 366 new ArrayList<MondrianDef.CalculatedMember>(); 367 CubeComparator cubeComparator = new CubeComparator(); 368 Map<RolapCube, List<MondrianDef.CalculatedMember>> calculatedMembersMap = 369 new TreeMap<RolapCube, List<MondrianDef.CalculatedMember>>( 370 cubeComparator); 371 Member defaultMeasure = null; 372 373 this.cubeUsages = new RolapCubeUsages(xmlVirtualCube.cubeUsage); 374 375 for (MondrianDef.VirtualCubeMeasure xmlMeasure : xmlVirtualCube.measures) { 376 // Lookup a measure in an existing cube. 377 RolapCube cube = schema.lookupCube(xmlMeasure.cubeName); 378 List<Member> cubeMeasures = cube.getMeasures(); 379 boolean found = false; 380 for (Member cubeMeasure : cubeMeasures) { 381 if (cubeMeasure.getUniqueName().equals(xmlMeasure.name)) { 382 if (cubeMeasure.getName().equalsIgnoreCase(xmlVirtualCube.defaultMeasure)) { 383 defaultMeasure = cubeMeasure; 384 } 385 found = true; 386 if (cubeMeasure instanceof RolapCalculatedMember) { 387 // We have a calulated member! Keep track of which 388 // base cube each calculated member is associated 389 // with, so we can resolve the calculated member 390 // relative to its base cube. We're using a treeMap 391 // to store the mapping to ensure a deterministic 392 // order for the members. 393 MondrianDef.CalculatedMember calcMember = 394 schema.lookupXmlCalculatedMember( 395 xmlMeasure.name, xmlMeasure.cubeName); 396 if (calcMember == null) { 397 throw Util.newInternal( 398 "Could not find XML Calculated Member '" + 399 xmlMeasure.name + "' in XML cube '" + 400 xmlMeasure.cubeName + "'"); 401 } 402 List<MondrianDef.CalculatedMember> memberList = 403 calculatedMembersMap.get(cube); 404 if (memberList == null) { 405 memberList = 406 new ArrayList<MondrianDef.CalculatedMember>(); 407 } 408 memberList.add(calcMember); 409 origCalcMeasureList.add(calcMember); 410 calculatedMembersMap.put(cube, memberList); 411 } else { 412 // This is the a standard measure. (Don't know 413 // whether it will confuse things that this 414 // measure still points to its 'real' cube.) 415 RolapVirtualCubeMeasure virtualCubeMeasure = 416 new RolapVirtualCubeMeasure( 417 null, 418 measuresLevel, 419 (RolapStoredMeasure) cubeMeasure); 420 421 // Set member's visibility, default true. 422 Boolean visible = xmlMeasure.visible; 423 if (visible == null) { 424 visible = Boolean.TRUE; 425 } 426 virtualCubeMeasure.setProperty(Property.VISIBLE.name, 427 visible); 428 // Inherit caption from the "real" measure 429 virtualCubeMeasure.setProperty(Property.CAPTION.name, 430 cubeMeasure.getCaption()); 431 origMeasureList.add(virtualCubeMeasure); 432 } 433 break; 434 } 435 } 436 if (!found) { 437 throw Util.newInternal( 438 "could not find measure '" + xmlMeasure.name + 439 "' in cube '" + xmlMeasure.cubeName + "'"); 440 } 441 } 442 443 // Must init the dimensions before dealing with calculated members 444 init(xmlVirtualCube.dimensions); 445 446 // Loop through the base cubes containing calculated members 447 // referenced by this virtual cube. Resolve those members relative 448 // to their base cubes first, then resolve them relative to this 449 // cube so the correct dimension ordinals are used 450 List<RolapVirtualCubeMeasure> modifiedMeasureList = 451 new ArrayList<RolapVirtualCubeMeasure>(origMeasureList); 452 for (Object o : calculatedMembersMap.keySet()) { 453 RolapCube baseCube = (RolapCube) o; 454 List<MondrianDef.CalculatedMember> calculatedMemberList = 455 calculatedMembersMap.get(baseCube); 456 Query queryExp = 457 resolveCalcMembers( 458 calculatedMemberList, 459 Collections.<MondrianDef.NamedSet>emptyList(), 460 baseCube, 461 false); 462 MeasureFinder measureFinder = 463 new MeasureFinder(this, baseCube, measuresLevel); 464 queryExp.accept(measureFinder); 465 modifiedMeasureList.addAll(measureFinder.getMeasuresFound()); 466 } 467 468 // Add the original calculated members from the base cubes to our 469 // list of calculated members 470 List<MondrianDef.CalculatedMember> calculatedMemberList = 471 new ArrayList<MondrianDef.CalculatedMember>(); 472 for (Object o : calculatedMembersMap.keySet()) { 473 RolapCube baseCube = (RolapCube) o; 474 calculatedMemberList.addAll( 475 calculatedMembersMap.get(baseCube)); 476 } 477 calculatedMemberList.addAll( 478 Arrays.asList(xmlVirtualCube.calculatedMembers)); 479 480 481 // Resolve all calculated members relative to this virtual cube, 482 // whose measureHierarchy member reader now contains all base 483 // measures referenced in those calculated members 484 setMeasuresHierarchyMemberReader( 485 new CacheMemberReader( 486 new MeasureMemberSource( 487 this.measuresHierarchy, 488 Util.<RolapMember>cast(modifiedMeasureList)))); 489 490 createCalcMembersAndNamedSets( 491 calculatedMemberList, 492 Arrays.asList(xmlVirtualCube.namedSets), 493 new ArrayList<RolapMember>(), 494 new ArrayList<Formula>(), 495 this, 496 false); 497 498 // reset the measureHierarchy member reader back to the list of 499 // measures that are only defined on this virtual cube 500 setMeasuresHierarchyMemberReader( 501 new CacheMemberReader( 502 new MeasureMemberSource( 503 this.measuresHierarchy, 504 Util.<RolapMember>cast(origMeasureList)))); 505 506 this.measuresHierarchy.setDefaultMember(defaultMeasure); 507 508 509 // remove from the calculated members array those members that weren't 510 // originally defined on this virtual cube 511 List<Formula> finalCalcMemberList = new ArrayList<Formula>(); 512 for (Formula calculatedMember : calculatedMembers) { 513 if (findOriginalMembers( 514 calculatedMember, 515 origCalcMeasureList, 516 finalCalcMemberList)) { 517 continue; 518 } 519 findOriginalMembers( 520 calculatedMember, 521 Arrays.asList(xmlVirtualCube.calculatedMembers), 522 finalCalcMemberList); 523 } 524 calculatedMembers = 525 finalCalcMemberList.toArray( 526 new Formula[finalCalcMemberList.size()]); 527 528 for (Formula calcMember : finalCalcMemberList) { 529 if (calcMember.getName(). 530 equalsIgnoreCase(xmlVirtualCube.defaultMeasure)) { 531 this.measuresHierarchy.setDefaultMember(calcMember.getMdxMember()); 532 break; 533 } 534 } 535 536 // Note: virtual cubes do not get aggregate 537 } 538 539 private boolean findOriginalMembers( 540 Formula formula, 541 List<MondrianDef.CalculatedMember> calcMemberList, 542 List<Formula> finalCalcMemberList) 543 { 544 for (MondrianDef.CalculatedMember xmlCalcMember : calcMemberList) { 545 Dimension dimension = 546 (Dimension) lookupDimension( 547 new Id.Segment( 548 xmlCalcMember.dimension, 549 Id.Quoting.UNQUOTED)); 550 if (formula.getName().equals(xmlCalcMember.name) && 551 formula.getMdxMember().getDimension().getName().equals( 552 dimension.getName())) { 553 finalCalcMemberList.add(formula); 554 return true; 555 } 556 } 557 return false; 558 } 559 560 protected Logger getLogger() { 561 return LOGGER; 562 } 563 564 public boolean hasAggGroup() { 565 return (aggGroup != null); 566 } 567 public ExplicitRules.Group getAggGroup() { 568 return aggGroup; 569 } 570 void loadAggGroup(MondrianDef.Cube xmlCube) { 571 aggGroup = ExplicitRules.Group.make(this, xmlCube); 572 } 573 574 /** 575 * Creates a dimension from its XML definition. If the XML definition is 576 * a <DimensionUsage>, and the shared dimension is cached in the 577 * schema, returns that. 578 * 579 * @param xmlCubeDimension XML Dimension or DimensionUsage 580 * @param schema Schema 581 * @param xmlSchema XML Schema 582 * @param dimensionOrdinal Ordinal of dimension 583 * @return A dimension 584 */ 585 private RolapCubeDimension getOrCreateDimension( 586 MondrianDef.CubeDimension xmlCubeDimension, 587 RolapSchema schema, 588 MondrianDef.Schema xmlSchema, 589 int dimensionOrdinal) 590 { 591 RolapDimension dimension = null; 592 if (xmlCubeDimension instanceof MondrianDef.DimensionUsage) { 593 MondrianDef.DimensionUsage usage = 594 (MondrianDef.DimensionUsage) xmlCubeDimension; 595 final RolapHierarchy sharedHierarchy = 596 schema.getSharedHierarchy(usage.source); 597 if (sharedHierarchy != null) { 598 dimension = 599 (RolapDimension) sharedHierarchy.getDimension(); 600 } 601 } 602 603 if (dimension == null) { 604 MondrianDef.Dimension xmlDimension = 605 xmlCubeDimension.getDimension(xmlSchema); 606 dimension = 607 new RolapDimension( 608 schema, this, xmlDimension, xmlCubeDimension); 609 } 610 611 // wrap the shared or regular dimension with a 612 // rolap cube dimension object 613 return new RolapCubeDimension( 614 this, dimension, xmlCubeDimension, 615 xmlCubeDimension.name, dimensionOrdinal, 616 xmlCubeDimension.highCardinality); 617 } 618 619 /** 620 * Post-initialization, doing things which cannot be done in the 621 * constructor. 622 */ 623 private void init( 624 MondrianDef.Cube xmlCube, 625 final List<RolapMember> memberList) 626 { 627 // Load calculated members and named sets. 628 // (We cannot do this in the constructor, because 629 // cannot parse the generated query, because the schema has not been 630 // set in the cube at this point.) 631 List<Formula> formulaList = new ArrayList<Formula>(); 632 createCalcMembersAndNamedSets( 633 Arrays.asList(xmlCube.calculatedMembers), 634 Arrays.asList(xmlCube.namedSets), 635 memberList, 636 formulaList, 637 this, 638 true); 639 } 640 641 /** 642 * Checks that the ordinals of measures (including calculated measures) 643 * are unique. 644 * 645 * @param cubeName name of the cube (required for error messages) 646 * @param measures measure list 647 */ 648 private void checkOrdinals( 649 String cubeName, 650 List<RolapMember> measures) 651 { 652 Map<Integer, String> ordinals = new HashMap<Integer, String>(); 653 for (RolapMember measure : measures) { 654 Integer ordinal = measure.getOrdinal(); 655 if (!ordinals.containsKey(ordinal)) { 656 ordinals.put(ordinal, measure.getUniqueName()); 657 } else { 658 throw MondrianResource.instance().MeasureOrdinalsNotUnique.ex( 659 cubeName, 660 ordinal.toString(), 661 ordinals.get(ordinal), 662 measure.getUniqueName()); 663 } 664 } 665 } 666 667 /** 668 * Adds a collection of calculated members and named sets to this cube. 669 * The members and sets can refer to each other. 670 * 671 * @param xmlCalcMembers XML objects representing members 672 * @param xmlNamedSets Array of XML definition of named set 673 * @param memberList Output list of {@link mondrian.olap.Member} objects 674 * @param formulaList Output list of {@link mondrian.olap.Formula} objects 675 * @param cube the cube that the calculated members originate from 676 * @param errOnDups throws an error if a duplicate member is found 677 */ 678 private void createCalcMembersAndNamedSets( 679 List<MondrianDef.CalculatedMember> xmlCalcMembers, 680 List<MondrianDef.NamedSet> xmlNamedSets, 681 List<RolapMember> memberList, 682 List<Formula> formulaList, 683 RolapCube cube, 684 boolean errOnDups) 685 { 686 final Query queryExp = 687 resolveCalcMembers( 688 xmlCalcMembers, 689 xmlNamedSets, 690 cube, 691 errOnDups); 692 if (queryExp == null) { 693 return; 694 } 695 696 // Now pick through the formulas. 697 Util.assertTrue( 698 queryExp.formulas.length == 699 xmlCalcMembers.size() + xmlNamedSets.size()); 700 for (int i = 0; i < xmlCalcMembers.size(); i++) { 701 postCalcMember(xmlCalcMembers, i, queryExp, memberList); 702 } 703 for (int i = 0; i < xmlNamedSets.size(); i++) { 704 postNamedSet( 705 xmlNamedSets, xmlCalcMembers.size(), i, queryExp, formulaList); 706 } 707 } 708 709 private Query resolveCalcMembers( 710 List<MondrianDef.CalculatedMember> xmlCalcMembers, 711 List<MondrianDef.NamedSet> xmlNamedSets, 712 RolapCube cube, 713 boolean errOnDups) 714 { 715 // If there are no objects to create, our generated SQL will be so 716 // silly, the parser will laugh. 717 if (xmlCalcMembers.size() == 0 && xmlNamedSets.size() == 0) { 718 return null; 719 } 720 721 StringBuilder buf = new StringBuilder(256); 722 buf.append("WITH").append(Util.nl); 723 724 // Check the members individually, and generate SQL. 725 for (int i = 0; i < xmlCalcMembers.size(); i++) { 726 preCalcMember(xmlCalcMembers, i, buf, cube, errOnDups); 727 } 728 729 // Check the named sets individually (for uniqueness) and generate SQL. 730 Set<String> nameSet = new HashSet<String>(); 731 for (Formula namedSet : namedSets) { 732 nameSet.add(namedSet.getName()); 733 } 734 for (MondrianDef.NamedSet xmlNamedSet : xmlNamedSets) { 735 preNamedSet(xmlNamedSet, nameSet, buf); 736 } 737 738 buf.append("SELECT FROM ").append(cube.getUniqueName()); 739 740 // Parse and validate this huge MDX query we've created. 741 final String queryString = buf.toString(); 742 final Query queryExp; 743 try { 744 RolapConnection conn = schema.getInternalConnection(); 745 queryExp = conn.parseQuery(queryString, load); 746 } catch (Exception e) { 747 throw MondrianResource.instance().UnknownNamedSetHasBadFormula.ex( 748 getName(), e); 749 } 750 queryExp.resolve(); 751 return queryExp; 752 } 753 754 private void postNamedSet( 755 List<MondrianDef.NamedSet> xmlNamedSets, 756 final int offset, int i, 757 final Query queryExp, 758 List<Formula> formulaList) { 759 MondrianDef.NamedSet xmlNamedSet = xmlNamedSets.get(i); 760 Util.discard(xmlNamedSet); 761 Formula formula = queryExp.formulas[offset + i]; 762 namedSets = RolapUtil.addElement(namedSets, formula); 763 formulaList.add(formula); 764 } 765 766 private void preNamedSet( 767 MondrianDef.NamedSet xmlNamedSet, 768 Set<String> nameSet, 769 StringBuilder buf) { 770 if (!nameSet.add(xmlNamedSet.name)) { 771 throw MondrianResource.instance().NamedSetNotUnique.ex( 772 xmlNamedSet.name, getName()); 773 } 774 775 buf.append("SET ") 776 .append(Util.makeFqName(xmlNamedSet.name)) 777 .append(Util.nl) 778 .append(" AS "); 779 Util.singleQuoteString(xmlNamedSet.getFormula(), buf); 780 buf.append(Util.nl); 781 } 782 783 private void postCalcMember( 784 List<MondrianDef.CalculatedMember> xmlCalcMembers, 785 int i, 786 final Query queryExp, 787 List<RolapMember> memberList) 788 { 789 MondrianDef.CalculatedMember xmlCalcMember = xmlCalcMembers.get(i); 790 final Formula formula = queryExp.formulas[i]; 791 792 calculatedMembers = RolapUtil.addElement(calculatedMembers, formula); 793 794 Member member = formula.getMdxMember(); 795 796 Boolean visible = xmlCalcMember.visible; 797 if (visible == null) { 798 visible = Boolean.TRUE; 799 } 800 member.setProperty(Property.VISIBLE.name, visible); 801 802 if ((xmlCalcMember.caption != null) && 803 xmlCalcMember.caption.length() > 0) { 804 member.setProperty( 805 Property.CAPTION.name, 806 xmlCalcMember.caption); 807 } 808 809 memberList.add((RolapMember) formula.getMdxMember()); 810 } 811 812 private void preCalcMember( 813 List<MondrianDef.CalculatedMember> xmlCalcMembers, 814 int j, 815 StringBuilder buf, 816 RolapCube cube, 817 boolean errOnDup) 818 { 819 MondrianDef.CalculatedMember xmlCalcMember = xmlCalcMembers.get(j); 820 821 // Lookup dimension 822 final Dimension dimension = 823 (Dimension) lookupDimension( 824 new Id.Segment( 825 xmlCalcMember.dimension, 826 Id.Quoting.UNQUOTED)); 827 if (dimension == null) { 828 throw MondrianResource.instance().CalcMemberHasBadDimension.ex( 829 xmlCalcMember.dimension, xmlCalcMember.name, getName()); 830 } 831 832 // If we're processing a virtual cube, it's possible that we've 833 // already processed this calculated member because it's 834 // referenced in another measure; in that case, remove it from the 835 // list, since we'll add it back in later; otherwise, in the 836 // non-virtual cube case, throw an exception 837 List<Formula> newCalcMemberList = new ArrayList<Formula>(); 838 for (Formula formula : calculatedMembers) { 839 if (formula.getName().equals(xmlCalcMember.name) && 840 formula.getMdxMember().getDimension().getName().equals( 841 dimension.getName())) { 842 if (errOnDup) { 843 throw MondrianResource.instance().CalcMemberNotUnique.ex( 844 Util.makeFqName(dimension, xmlCalcMember.name), 845 getName()); 846 } 847 continue; 848 } else { 849 newCalcMemberList.add(formula); 850 } 851 } 852 calculatedMembers = 853 newCalcMemberList.toArray(new Formula[newCalcMemberList.size()]); 854 855 // Check this calc member doesn't clash with one earlier in this 856 // batch. 857 for (int k = 0; k < j; k++) { 858 MondrianDef.CalculatedMember xmlCalcMember2 = xmlCalcMembers.get(k); 859 if (xmlCalcMember2.name.equals(xmlCalcMember.name) && 860 xmlCalcMember2.dimension.equals(xmlCalcMember.dimension)) { 861 throw MondrianResource.instance().CalcMemberNotUnique.ex( 862 Util.makeFqName(dimension, xmlCalcMember.name), 863 getName()); 864 } 865 } 866 867 final String memberUniqueName = Util.makeFqName( 868 dimension.getUniqueName(), xmlCalcMember.name); 869 final MondrianDef.CalculatedMemberProperty[] xmlProperties = 870 xmlCalcMember.memberProperties; 871 List<String> propNames = new ArrayList<String>(); 872 List<String> propExprs = new ArrayList<String>(); 873 validateMemberProps( 874 xmlProperties, propNames, propExprs, xmlCalcMember.name); 875 876 final int measureCount = 877 cube.measuresHierarchy.getMemberReader().getMemberCount(); 878 879 // Generate SQL. 880 assert memberUniqueName.startsWith("["); 881 buf.append("MEMBER ").append(memberUniqueName) 882 .append(Util.nl) 883 .append(" AS "); 884 Util.singleQuoteString(xmlCalcMember.getFormula(), buf); 885 886 assert propNames.size() == propExprs.size(); 887 processFormatStringAttribute(xmlCalcMember, buf); 888 889 for (int i = 0; i < propNames.size(); i++) { 890 String name = propNames.get(i); 891 String expr = propExprs.get(i); 892 buf.append(",").append(Util.nl); 893 expr = removeSurroundingQuotesIfNumericProperty(name, expr); 894 buf.append(name).append(" = ").append(expr); 895 } 896 // Flag that the calc members are defined against a cube; will 897 // determine the value of Member.isCalculatedInQuery 898 buf.append(",").append(Util.nl). 899 append(Util.quoteMdxIdentifier(Property.MEMBER_SCOPE.name)). 900 append(" = 'CUBE'"); 901 902 // Assign the member an ordinal higher than all of the stored measures. 903 if (!propNames.contains(Property.MEMBER_ORDINAL.getName())) { 904 buf.append(",").append(Util.nl). 905 append(Property.MEMBER_ORDINAL).append(" = "). 906 append(measureCount + j); 907 } 908 buf.append(Util.nl); 909 } 910 911 private String removeSurroundingQuotesIfNumericProperty(String name, String expr) { 912 Property prop = Property.lookup(name, false); 913 if (prop != null && prop.getType() == Property.Datatype.TYPE_NUMERIC && 914 isSurroundedWithQuotes(expr) && expr.length() > 2) { 915 return expr.substring(1, expr.length() - 1); 916 } 917 return expr; 918 } 919 920 private boolean isSurroundedWithQuotes(String expr) { 921 return expr.startsWith("\"") && expr.endsWith("\""); 922 } 923 924 void processFormatStringAttribute(MondrianDef.CalculatedMember xmlCalcMember, StringBuilder buf) { 925 if (xmlCalcMember.formatString != null) { 926 buf.append(",").append(Util.nl) 927 .append(Property.FORMAT_STRING.name).append(" = ").append(Util.quoteForMdx(xmlCalcMember.formatString)); 928 } 929 } 930 931 /** 932 * Validates an array of member properties, and populates a list of names 933 * and expressions, one for each property. 934 * 935 * @param xmlProperties Array of property definitions. 936 * @param propNames Output array of property names. 937 * @param propExprs Output array of property expressions. 938 * @param memberName Name of member which the properties belong to. 939 */ 940 private void validateMemberProps( 941 final MondrianDef.CalculatedMemberProperty[] xmlProperties, 942 List<String> propNames, 943 List<String> propExprs, 944 String memberName) { 945 946 MemberProperty[] properties = new MemberProperty[xmlProperties.length]; 947 for (int i = 0; i < properties.length; i++) { 948 final MondrianDef.CalculatedMemberProperty xmlProperty = 949 xmlProperties[i]; 950 if (xmlProperty.expression == null && 951 xmlProperty.value == null) 952 { 953 throw MondrianResource.instance() 954 .NeitherExprNorValueForCalcMemberProperty.ex( 955 xmlProperty.name, 956 memberName, 957 getName()); 958 } 959 if (xmlProperty.expression != null && 960 xmlProperty.value != null) 961 { 962 throw MondrianResource.instance() 963 .ExprAndValueForMemberProperty.ex( 964 xmlProperty.name, 965 memberName, 966 getName()); 967 } 968 propNames.add(xmlProperty.name); 969 if (xmlProperty.expression != null) { 970 propExprs.add(xmlProperty.expression); 971 } else { 972 propExprs.add(Util.quoteForMdx(xmlProperty.value)); 973 } 974 } 975 } 976 977 public RolapSchema getSchema() { 978 return schema; 979 } 980 981 /** 982 * Returns the named sets of this cube. 983 */ 984 public NamedSet[] getNamedSets() { 985 NamedSet[] namedSetsArray = new NamedSet[namedSets.length]; 986 for (int i = 0; i < namedSets.length; i++) { 987 namedSetsArray[i] = namedSets[i].getNamedSet(); 988 } 989 return namedSetsArray; 990 } 991 992 /** 993 * Returns the schema reader which enforces the appropriate access-control 994 * context. schemaReader is cached, and needs to stay in sync with 995 * any changes to the cube. 996 * 997 * @post return != null 998 * @see #getSchemaReader(Role) 999 */ 1000 public synchronized SchemaReader getSchemaReader() { 1001 if (schemaReader == null) { 1002 RoleImpl schemaDefaultRoleImpl = schema.getDefaultRole(); 1003 RoleImpl roleImpl = schemaDefaultRoleImpl.makeMutableClone(); 1004 roleImpl.grant(this, Access.ALL); 1005 Role role = roleImpl; 1006 schemaReader = new RolapCubeSchemaReader(role); 1007 } 1008 return schemaReader; 1009 } 1010 1011 public SchemaReader getSchemaReader(Role role) { 1012 if (role == null) { 1013 return getSchemaReader(); 1014 } else { 1015 return new RolapCubeSchemaReader(role); 1016 } 1017 } 1018 1019 MondrianDef.CubeDimension lookup( 1020 MondrianDef.CubeDimension[] xmlDimensions, 1021 String name) { 1022 for (MondrianDef.CubeDimension cd : xmlDimensions) { 1023 if (name.equals(cd.name)) { 1024 return cd; 1025 } 1026 } 1027 // TODO: this ought to be a fatal error. 1028 return null; 1029 } 1030 1031 private void init(MondrianDef.CubeDimension[] xmlDimensions) { 1032 for (Dimension dimension1 : dimensions) { 1033 final RolapDimension dimension = (RolapDimension) dimension1; 1034 dimension.init(lookup(xmlDimensions, dimension.getName())); 1035 } 1036 register(); 1037 } 1038 1039 private void register() { 1040 if (isVirtual()) { 1041 return; 1042 } 1043 List<Member> list = new ArrayList<Member>(); 1044 List<Member> measures = getMeasures(); 1045 for (Member measure : measures) { 1046 if (measure instanceof RolapBaseCubeMeasure) { 1047 list.add(measure); 1048 } 1049 } 1050 RolapBaseCubeMeasure[] storedMeasures = 1051 list.toArray(new RolapBaseCubeMeasure[list.size()]); 1052 1053 RolapStar star = getStar(); 1054 RolapStar.Table table = star.getFactTable(); 1055 1056 // create measures (and stars for them, if necessary) 1057 for (RolapBaseCubeMeasure storedMeasure : storedMeasures) { 1058 table.makeMeasure(storedMeasure); 1059 } 1060 } 1061 1062 /** 1063 * Returns true if this Cube is either virtual or if the Cube's 1064 * RolapStar is caching aggregates. 1065 * 1066 * @return Whether this Cube's RolapStar should cache aggregations 1067 */ 1068 public boolean isCacheAggregations() { 1069 return isVirtual() || star.isCacheAggregations(); 1070 } 1071 1072 /** 1073 * Set if this (non-virtual) Cube's RolapStar should cache 1074 * aggregations. 1075 * 1076 * @param cache Whether this Cube's RolapStar should cache aggregations 1077 */ 1078 public void setCacheAggregations(boolean cache) { 1079 if (! isVirtual()) { 1080 star.setCacheAggregations(cache); 1081 } 1082 } 1083 1084 /** 1085 * Clear the in memory aggregate cache associated with this Cube, but 1086 * only if Disabling Caching has been enabled. 1087 */ 1088 public void clearCachedAggregations() { 1089 clearCachedAggregations(false); 1090 } 1091 1092 /** 1093 * Clear the in memory aggregate cache associated with this Cube. 1094 */ 1095 public void clearCachedAggregations(boolean forced) { 1096 if (isVirtual()) { 1097 // TODO: 1098 // Currently a virtual cube does not keep a list of all of its 1099 // base cubes, so we need to iterate through each and flush 1100 // the ones that should be flushed. Could use a CacheControl 1101 // method here. 1102 for (RolapStar star1 : schema.getStars()) { 1103 // this will only flush the star's aggregate cache if 1104 // 1) DisableCaching is true or 2) the star's cube has 1105 // cacheAggregations set to false in the schema. 1106 star1.clearCachedAggregations(forced); 1107 } 1108 } else { 1109 star.clearCachedAggregations(forced); 1110 } 1111 } 1112 1113 /** 1114 * Check if there are modifications in the aggregations cache 1115 */ 1116 public void checkAggregateModifications() { 1117 if (isVirtual()) { 1118 // TODO: 1119 // Currently a virtual cube does not keep a list of all of its 1120 // base cubes, so we need to iterate through each and flush 1121 // the ones that should be flushed 1122 schema.checkAggregateModifications(); 1123 } else { 1124 star.checkAggregateModifications(); 1125 } 1126 } 1127 /** 1128 * Push all modifications of the aggregations to global cache, 1129 * so other queries can start using the new cache 1130 */ 1131 public void pushAggregateModificationsToGlobalCache() { 1132 if (isVirtual()) { 1133 // TODO: 1134 // Currently a virtual cube does not keep a list of all of its 1135 // base cubes, so we need to iterate through each and flush 1136 // the ones that should be flushed 1137 schema.pushAggregateModificationsToGlobalCache(); 1138 } else { 1139 star.pushAggregateModificationsToGlobalCache(); 1140 } 1141 } 1142 1143 1144 1145 /** 1146 * Returns this cube's underlying star schema. 1147 */ 1148 public RolapStar getStar() { 1149 return star; 1150 } 1151 1152 private void createUsages(RolapCubeDimension dimension, 1153 MondrianDef.CubeDimension xmlCubeDimension) { 1154 // RME level may not be in all hierarchies 1155 // If one uses the DimensionUsage attribute "level", which level 1156 // in a hierarchy to join on, and there is more than one hierarchy, 1157 // then a HierarchyUsage can not be created for the hierarchies 1158 // that do not have the level defined. 1159 RolapCubeHierarchy[] hierarchies = 1160 (RolapCubeHierarchy[]) dimension.getHierarchies(); 1161 1162 if (hierarchies.length == 1) { 1163 // Only one, so let lower level error checking handle problems 1164 createUsage(hierarchies[0], xmlCubeDimension); 1165 1166 } else if ((xmlCubeDimension instanceof MondrianDef.DimensionUsage) && 1167 (((MondrianDef.DimensionUsage) xmlCubeDimension).level != null)) { 1168 // More than one, make sure if we are joining by level, that 1169 // at least one hierarchy can and those that can not are 1170 // not registered 1171 MondrianDef.DimensionUsage du = 1172 (MondrianDef.DimensionUsage) xmlCubeDimension; 1173 1174 int cnt = 0; 1175 1176 for (RolapCubeHierarchy hierarchy : hierarchies) { 1177 if (getLogger().isDebugEnabled()) { 1178 getLogger().debug("RolapCube<init>: hierarchy=" 1179 + hierarchy.getName()); 1180 } 1181 RolapLevel joinLevel = (RolapLevel) 1182 Util.lookupHierarchyLevel(hierarchy, du.level); 1183 if (joinLevel == null) { 1184 continue; 1185 } 1186 createUsage(hierarchy, xmlCubeDimension); 1187 cnt++; 1188 } 1189 1190 if (cnt == 0) { 1191 // None of the hierarchies had the level, let lower level 1192 // detect and throw error 1193 createUsage(hierarchies[0], xmlCubeDimension); 1194 } 1195 1196 } else { 1197 // just do it 1198 for (RolapCubeHierarchy hierarchy : hierarchies) { 1199 if (getLogger().isDebugEnabled()) { 1200 getLogger().debug("RolapCube<init>: hierarchy=" 1201 + hierarchy.getName()); 1202 } 1203 createUsage(hierarchy, xmlCubeDimension); 1204 } 1205 } 1206 } 1207 1208 synchronized void createUsage( 1209 RolapCubeHierarchy hierarchy, 1210 MondrianDef.CubeDimension cubeDim) { 1211 1212 HierarchyUsage usage = new HierarchyUsage(this, hierarchy, cubeDim); 1213 if (LOGGER.isDebugEnabled()) { 1214 LOGGER.debug("RolapCube.createUsage: "+ 1215 "cube=" + getName()+ 1216 ", hierarchy=" + hierarchy.getName() + 1217 ", usage=" + usage); 1218 } 1219 for (HierarchyUsage hierUsage : hierarchyUsages) { 1220 if (hierUsage.equals(usage)) { 1221 getLogger().warn( 1222 "RolapCube.createUsage: duplicate " + hierUsage); 1223 return; 1224 } 1225 } 1226 if (getLogger().isDebugEnabled()) { 1227 getLogger().debug("RolapCube.createUsage: register " + usage); 1228 } 1229 this.hierarchyUsages.add(usage); 1230 } 1231 1232 private synchronized HierarchyUsage getUsageByName(String name) { 1233 for (HierarchyUsage hierUsage : hierarchyUsages) { 1234 if (hierUsage.getFullName().equals(name)) { 1235 return hierUsage; 1236 } 1237 } 1238 return null; 1239 } 1240 1241 /** 1242 * A Hierarchy may have one or more HierarchyUsages. This method returns 1243 * an array holding the one or more usages associated with a Hierarchy. 1244 * The HierarchyUsages hierarchyName attribute always equals the name 1245 * attribute of the Hierarchy. 1246 * 1247 * @param hierarchy Hierarchy 1248 * @return an HierarchyUsages array with 0 or more members. 1249 */ 1250 public synchronized HierarchyUsage[] getUsages(Hierarchy hierarchy) { 1251 String name = hierarchy.getName(); 1252 if (getLogger().isDebugEnabled()) { 1253 getLogger().debug("RolapCube.getUsages: name=" + name); 1254 } 1255 1256 HierarchyUsage hierUsage = null; 1257 List<HierarchyUsage> list = null; 1258 1259 for (HierarchyUsage hu : hierarchyUsages) { 1260 if (hu.getHierarchyName().equals(name)) { 1261 if (list != null) { 1262 if (getLogger().isDebugEnabled()) { 1263 getLogger().debug("RolapCube.getUsages: " 1264 + "add list HierarchyUsage.name=" + hu.getName()); 1265 } 1266 list.add(hu); 1267 } else if (hierUsage == null) { 1268 hierUsage = hu; 1269 } else { 1270 list = new ArrayList<HierarchyUsage>(); 1271 if (getLogger().isDebugEnabled()) { 1272 getLogger().debug("RolapCube.getUsages: " 1273 + "add list hierUsage.name=" 1274 + hierUsage.getName() 1275 + ", hu.name=" 1276 + hu.getName()); 1277 } 1278 list.add(hierUsage); 1279 list.add(hu); 1280 hierUsage = null; 1281 } 1282 } 1283 } 1284 if (hierUsage != null) { 1285 return new HierarchyUsage[] { hierUsage }; 1286 } else if (list != null) { 1287 if (getLogger().isDebugEnabled()) { 1288 getLogger().debug("RolapCube.getUsages: return list"); 1289 } 1290 return list.toArray(new HierarchyUsage[list.size()]); 1291 } else { 1292 return new HierarchyUsage[0]; 1293 } 1294 } 1295 1296 synchronized HierarchyUsage getFirstUsage(Hierarchy hier) { 1297 HierarchyUsage hierarchyUsage = firstUsageMap.get(hier); 1298 if (hierarchyUsage == null) { 1299 HierarchyUsage[] hierarchyUsages = getUsages(hier); 1300 if (hierarchyUsages.length != 0) { 1301 hierarchyUsage = hierarchyUsages[0]; 1302 firstUsageMap.put(hier, hierarchyUsage); 1303 } 1304 } 1305 return hierarchyUsage; 1306 } 1307 1308 /** 1309 * Looks up all of the HierarchyUsages with the same "source" returning 1310 * an array of HierarchyUsage of length 0 or more. 1311 * 1312 * This method is currently only called if an error occurs in lookupChild(), 1313 * so that more information can be displayed in the error log. 1314 * 1315 * @param source Name of shared dimension 1316 * @return array of HierarchyUsage (HierarchyUsage[]) - never null. 1317 */ 1318 private synchronized HierarchyUsage[] getUsagesBySource(String source) { 1319 if (getLogger().isDebugEnabled()) { 1320 getLogger().debug("RolapCube.getUsagesBySource: source=" + source); 1321 } 1322 1323 HierarchyUsage hierUsage = null; 1324 List<HierarchyUsage> list = null; 1325 1326 for (HierarchyUsage hu : hierarchyUsages) { 1327 String s = hu.getSource(); 1328 if ((s != null) && s.equals(source)) { 1329 if (list != null) { 1330 if (getLogger().isDebugEnabled()) { 1331 getLogger().debug("RolapCube.getUsagesBySource: " 1332 + "add list HierarchyUsage.name=" 1333 + hu.getName()); 1334 } 1335 list.add(hu); 1336 } else if (hierUsage == null) { 1337 hierUsage = hu; 1338 } else { 1339 list = new ArrayList<HierarchyUsage>(); 1340 if (getLogger().isDebugEnabled()) { 1341 getLogger().debug("RolapCube.getUsagesBySource: " 1342 + "add list hierUsage.name=" 1343 + hierUsage.getName() 1344 + ", hu.name=" 1345 + hu.getName()); 1346 } 1347 list.add(hierUsage); 1348 list.add(hu); 1349 hierUsage = null; 1350 } 1351 } 1352 } 1353 if (hierUsage != null) { 1354 return new HierarchyUsage[] { hierUsage }; 1355 } else if (list != null) { 1356 if (getLogger().isDebugEnabled()) { 1357 getLogger().debug("RolapCube.getUsagesBySource: return list"); 1358 } 1359 return list.toArray(new HierarchyUsage[list.size()]); 1360 } else { 1361 return new HierarchyUsage[0]; 1362 } 1363 } 1364 1365 1366 /** 1367 * Understand this and you are no longer a novice. 1368 * 1369 * @param dimension Dimension 1370 */ 1371 void registerDimension(RolapCubeDimension dimension) { 1372 RolapStar star = getStar(); 1373 1374 Hierarchy[] hierarchies = dimension.getHierarchies(); 1375 1376 for (Hierarchy hierarchy1 : hierarchies) { 1377 RolapHierarchy hierarchy = (RolapHierarchy) hierarchy1; 1378 1379 MondrianDef.RelationOrJoin relation = hierarchy.getRelation(); 1380 if (relation == null) { 1381 continue; // e.g. [Measures] hierarchy 1382 } 1383 RolapCubeLevel[] levels = (RolapCubeLevel[]) hierarchy.getLevels(); 1384 1385 HierarchyUsage[] hierarchyUsages = getUsages(hierarchy); 1386 if (hierarchyUsages.length == 0) { 1387 if (getLogger().isDebugEnabled()) { 1388 StringBuilder buf = new StringBuilder(64); 1389 buf.append("RolapCube.registerDimension: "); 1390 buf.append("hierarchyUsages == null for cube=\""); 1391 buf.append(this.name); 1392 buf.append("\", hierarchy=\""); 1393 buf.append(hierarchy.getName()); 1394 buf.append("\""); 1395 getLogger().debug(buf.toString()); 1396 } 1397 continue; 1398 } 1399 1400 for (HierarchyUsage hierarchyUsage : hierarchyUsages) { 1401 String usagePrefix = hierarchyUsage.getUsagePrefix(); 1402 RolapStar.Table table = star.getFactTable(); 1403 1404 String levelName = hierarchyUsage.getLevelName(); 1405 1406 // RME 1407 // If a DimensionUsage has its level attribute set, then 1408 // one wants joins to occur at that level and not below (not 1409 // at a finer level), i.e., if you have levels: Year, Quarter, 1410 // Month, and Day, and the level attribute is set to Month, the 1411 // you do not want aggregate joins to include the Day level. 1412 // By default, it is the lowest level that the fact table 1413 // joins to, the Day level. 1414 // To accomplish this, we reorganize the relation and then 1415 // copy it (so that elsewhere the original relation can 1416 // still be used), and finally, clip off those levels below 1417 // the DimensionUsage level attribute. 1418 // Note also, if the relation (MondrianDef.Relation) is not 1419 // a MondrianDef.Join, i.e., the dimension is not a snowflake, 1420 // there is a single dimension table, then this is currently 1421 // an unsupported configuation and all bets are off. 1422 if (relation instanceof MondrianDef.Join) { 1423 1424 // RME 1425 // take out after things seem to be working 1426 MondrianDef.RelationOrJoin relationTmp1 = relation; 1427 1428 relation = reorder(relation, levels); 1429 1430 if (relation == null && getLogger().isDebugEnabled()) { 1431 getLogger().debug( 1432 "RolapCube.registerDimension: after reorder relation==null"); 1433 getLogger().debug( 1434 "RolapCube.registerDimension: reorder relationTmp1=" 1435 + format(relationTmp1)); 1436 } 1437 } 1438 1439 MondrianDef.RelationOrJoin relationTmp2 = relation; 1440 1441 if (levelName != null) { 1442 //System.out.println("RolapCube.registerDimension: levelName=" +levelName); 1443 // When relation is a table, this does nothing. Otherwise 1444 // it tries to arrange the joins so that the fact table 1445 // in the RolapStar will be joining at the lowest level. 1446 // 1447 1448 // Make sure the level exists 1449 RolapLevel level = 1450 RolapLevel.lookupLevel(levels, levelName); 1451 if (level == null) { 1452 StringBuilder buf = new StringBuilder(64); 1453 buf.append("For cube \""); 1454 buf.append(getName()); 1455 buf.append("\" and HierarchyUsage ["); 1456 buf.append(hierarchyUsage); 1457 buf.append("], there is no level with given"); 1458 buf.append(" level name \""); 1459 buf.append(levelName); 1460 buf.append("\""); 1461 throw Util.newInternal(buf.toString()); 1462 } 1463 1464 // If level has child, not the lowest level, then snip 1465 // relation between level and its child so that 1466 // joins do not include the lower levels. 1467 // If the child level is null, then the DimensionUsage 1468 // level attribute was simply set to the default, lowest 1469 // level and we do nothing. 1470 if (relation instanceof MondrianDef.Join) { 1471 RolapLevel childLevel = 1472 (RolapLevel) level.getChildLevel(); 1473 if (childLevel != null) { 1474 String tableName = childLevel.getTableName(); 1475 if (tableName != null) { 1476 relation = snip(relation, tableName); 1477 1478 if (relation == null && 1479 getLogger().isDebugEnabled()) { 1480 getLogger().debug( 1481 "RolapCube.registerDimension: after snip relation==null"); 1482 getLogger().debug( 1483 "RolapCube.registerDimension: snip relationTmp2=" 1484 + format(relationTmp2)); 1485 } 1486 } 1487 } 1488 } 1489 1490 } 1491 1492 // cube and dimension usage are in different tables 1493 if (!relation.equals(table.getRelation())) { 1494 // HierarchyUsage should have checked this. 1495 if (hierarchyUsage.getForeignKey() == null) { 1496 throw MondrianResource.instance() 1497 .HierarchyMustHaveForeignKey.ex( 1498 hierarchy.getName(), getName()); 1499 } 1500 // jhyde: check is disabled until we handle <View> correctly 1501 if (false && 1502 !star.getFactTable() 1503 .containsColumn(hierarchyUsage.getForeignKey())) { 1504 throw MondrianResource.instance() 1505 .HierarchyInvalidForeignKey.ex( 1506 hierarchyUsage.getForeignKey(), 1507 hierarchy.getName(), 1508 getName()); 1509 } 1510 // parameters: 1511 // fact table, 1512 // fact table foreign key, 1513 MondrianDef.Column column = 1514 new MondrianDef.Column(table.getAlias(), 1515 hierarchyUsage.getForeignKey()); 1516 // parameters: 1517 // left column 1518 // right column 1519 RolapStar.Condition joinCondition = 1520 new RolapStar.Condition(column, 1521 hierarchyUsage.getJoinExp()); 1522 1523 // (rchen) potential bug?: 1524 // FACT table joins with tables in a hierarchy in the 1525 // order they appear in the schema definition, even though 1526 // the primary key for this hierarchy can be on a table 1527 // which is not the leftmost. 1528 // e.g. 1529 // <Dimension name="Product"> 1530 // <Hierarchy hasAll="true" primaryKey="product_id" primaryKeyTable="product"> 1531 // <Join leftKey="product_class_id" rightKey="product_class_id"> 1532 // <Table name="product_class"/> 1533 // <Table name="product"/> 1534 // </Join> 1535 // </Hierarchy> 1536 // </Dimension> 1537 // 1538 // When this hierarchy is referenced in a cube, the fact 1539 // table is joined with the dimension tables using this 1540 // incorrect join condition which assumes the leftmost 1541 // table produces the primaryKey: 1542 // "fact"."foreignKey" = "product_class"."product_id" 1543 1544 table = table.addJoin(this, relation, joinCondition); 1545 } 1546 1547 // The parent Column is used so that non-shared dimensions 1548 // which use the fact table (not a separate dimension table) 1549 // can keep a record of what other columns are in the 1550 // same set of levels. 1551 RolapStar.Column parentColumn = null; 1552 1553 //RME 1554 // If the level name is not null, then we need only register 1555 // those columns for that level and above. 1556 if (levelName != null) { 1557 for (RolapCubeLevel level : levels) { 1558 if (level.getKeyExp() != null) { 1559 parentColumn = makeColumns(table, 1560 level, parentColumn, usagePrefix); 1561 } 1562 if (levelName.equals(level.getName())) { 1563 break; 1564 } 1565 } 1566 } else { 1567 // This is the normal case, no level attribute so register 1568 // all columns. 1569 for (RolapCubeLevel level : levels) { 1570 if (level.getKeyExp() != null) { 1571 parentColumn = makeColumns(table, 1572 level, parentColumn, usagePrefix); 1573 } 1574 } 1575 } 1576 } 1577 } 1578 } 1579 1580 /** 1581 * Adds a column to the appropriate table in the {@link RolapStar}. 1582 * Note that if the RolapLevel has a table attribute, then the associated 1583 * column needs to be associated with that table. 1584 */ 1585 protected RolapStar.Column makeColumns( 1586 RolapStar.Table table, 1587 RolapCubeLevel level, 1588 RolapStar.Column parentColumn, 1589 String usagePrefix) { 1590 1591 // If there is a table name, then first see if the table name is the 1592 // table parameter's name or alias and, if so, simply add the column 1593 // to that table. On the other hand, find the ancestor of the table 1594 // parameter and if found, then associate the new column with 1595 // that table. 1596 // Lastly, if the ancestor can not be found, i.e., there is no table 1597 // with the level's table name, what to do. Here we simply punt and 1598 // associated the new column with the table parameter which might 1599 // be an error. We do issue a warning in any case. 1600 String tableName = level.getTableName(); 1601 if (tableName != null) { 1602 if (table.getAlias().equals(tableName)) { 1603 parentColumn = table.makeColumns(this, level, 1604 parentColumn, usagePrefix); 1605 } else if (table.equalsTableName(tableName)) { 1606 parentColumn = table.makeColumns(this, level, 1607 parentColumn, usagePrefix); 1608 } else { 1609 RolapStar.Table t = table.findAncestor(tableName); 1610 if (t != null) { 1611 parentColumn = t.makeColumns(this, level, 1612 parentColumn, usagePrefix); 1613 } else { 1614 // Issue warning and keep going. 1615 StringBuilder buf = new StringBuilder(64); 1616 buf.append("RolapCube.makeColumns: for cube \""); 1617 buf.append(getName()); 1618 buf.append("\" the Level \""); 1619 buf.append(level.getName()); 1620 buf.append("\" has a table name attribute \""); 1621 buf.append(tableName); 1622 buf.append("\" but the associated RolapStar does not"); 1623 buf.append(" have a table with that name."); 1624 getLogger().warn(buf.toString()); 1625 1626 parentColumn = table.makeColumns(this, level, 1627 parentColumn, usagePrefix); 1628 } 1629 } 1630 } else { 1631 // level's expr is not a MondrianDef.Column (this is used by tests) 1632 // or there is no table name defined 1633 parentColumn = table.makeColumns(this, level, 1634 parentColumn, usagePrefix); 1635 } 1636 1637 return parentColumn; 1638 } 1639 1640 /////////////////////////////////////////////////////////////////////////// 1641 // 1642 // The following code deals with handling the DimensionUsage level attribute 1643 // and snowflake dimensions only. 1644 // 1645 1646 /** 1647 * Formats a {@link mondrian.olap.MondrianDef.RelationOrJoin}, indenting joins for 1648 * readability. 1649 * 1650 * @param relation 1651 */ 1652 private static String format(MondrianDef.RelationOrJoin relation) { 1653 StringBuilder buf = new StringBuilder(); 1654 format(relation, buf, ""); 1655 return buf.toString(); 1656 } 1657 1658 private static void format( 1659 MondrianDef.RelationOrJoin relation, 1660 StringBuilder buf, String indent) { 1661 if (relation instanceof MondrianDef.Table) { 1662 MondrianDef.Table table = (MondrianDef.Table) relation; 1663 1664 buf.append(indent); 1665 buf.append(table.name); 1666 if (table.alias != null) { 1667 buf.append('('); 1668 buf.append(table.alias); 1669 buf.append(')'); 1670 } 1671 buf.append(Util.nl); 1672 } else { 1673 MondrianDef.Join join = (MondrianDef.Join) relation; 1674 String subindent = indent + " "; 1675 1676 buf.append(indent); 1677 //buf.append(join.leftAlias); 1678 buf.append(join.getLeftAlias()); 1679 buf.append('.'); 1680 buf.append(join.leftKey); 1681 buf.append('='); 1682 buf.append(join.getRightAlias()); 1683 //buf.append(join.rightAlias); 1684 buf.append('.'); 1685 buf.append(join.rightKey); 1686 buf.append(Util.nl); 1687 format(join.left, buf, subindent); 1688 format(join.right, buf, indent); 1689 } 1690 } 1691 1692 /** 1693 * This method tells us if unrelated dimensions to measures from 1694 * the input base cube should be pushed to default member or not 1695 * during aggregation. 1696 * @param baseCubeName name of the base cube for which we want 1697 * to check this property 1698 * @return boolean 1699 */ 1700 public boolean shouldIgnoreUnrelatedDimensions(String baseCubeName) { 1701 return cubeUsages != null 1702 && cubeUsages.shouldIgnoreUnrelatedDimensions(baseCubeName); 1703 } 1704 1705 /** 1706 * This class is used to associate a MondrianDef.Table with its associated 1707 * level's depth. This is used to rank tables in a snowflake so that 1708 * the table with the lowest rank, level depth, is furthest from 1709 * the base fact table in the RolapStar. 1710 * 1711 */ 1712 private static class RelNode { 1713 1714 /** 1715 * Finds a RelNode by table name or, if that fails, by table alias 1716 * from a map of RelNodes. 1717 * 1718 * @param table 1719 * @param map 1720 */ 1721 private static RelNode lookup( 1722 MondrianDef.Relation table, 1723 Map<String, RelNode> map) 1724 { 1725 RelNode relNode; 1726 if (table instanceof MondrianDef.Table) { 1727 relNode = map.get(((MondrianDef.Table) table).name); 1728 if (relNode != null) { 1729 return relNode; 1730 } 1731 } 1732 return map.get(table.getAlias()); 1733 } 1734 1735 private int depth; 1736 private String alias; 1737 private MondrianDef.Relation table; 1738 RelNode(String alias, int depth) { 1739 this.alias = alias; 1740 this.depth = depth; 1741 } 1742 1743 } 1744 1745 /** 1746 * Attempts to transform a {@link mondrian.olap.MondrianDef.RelationOrJoin} 1747 * into the "canonical" form. 1748 * 1749 * <p>What is the canonical form? It is only relevant 1750 * when the relation is a snowflake (nested joins), not simply a table. 1751 * The canonical form has lower levels to the left of higher levels (Day 1752 * before Month before Quarter before Year) and the nested joins are always 1753 * on the right side of the parent join. 1754 * 1755 * <p>The canonical form is (using a Time dimension example): 1756 * <pre> 1757 * | 1758 * ---------------- 1759 * | | 1760 * Day -------------- 1761 * | | 1762 * Month --------- 1763 * | | 1764 * Quarter Year 1765 * </pre> 1766 * <p> 1767 * When the relation looks like the above, then the fact table joins to the 1768 * lowest level table (the Day table) which joins to the next level (the 1769 * Month table) which joins to the next (the Quarter table) which joins to 1770 * the top level table (the Year table). 1771 * <p> 1772 * This method supports the transformation of a subset of all possible 1773 * join/table relation trees (and anyone who whats to generalize it is 1774 * welcome to). It will take any of the following and convert them to 1775 * the canonical. 1776 * <pre> 1777 * | 1778 * ---------------- 1779 * | | 1780 * Year -------------- 1781 * | | 1782 * Quarter --------- 1783 * | | 1784 * Month Day 1785 * 1786 * | 1787 * ---------------- 1788 * | | 1789 * -------------- Year 1790 * | | 1791 * --------- Quarter 1792 * | | 1793 * Day Month 1794 * 1795 * | 1796 * ---------------- 1797 * | | 1798 * -------------- Day 1799 * | | 1800 * --------- Month 1801 * | | 1802 * Year Quarter 1803 * 1804 * | 1805 * ---------------- 1806 * | | 1807 * Day -------------- 1808 * | | 1809 * Month --------- 1810 * | | 1811 * Quarter Year 1812 * 1813 * </pre> 1814 * <p> 1815 * In addition, at any join node, it can exchange the left and right 1816 * child relations so that the lower level depth is to the left. 1817 * For example, it can also transform the following: 1818 * <pre> 1819 * | 1820 * ---------------- 1821 * | | 1822 * -------------- Day 1823 * | | 1824 * Month --------- 1825 * | | 1826 * Year Quarter 1827 * </pre> 1828 * <p> 1829 * What it can not handle are cases where on both the left and right side of 1830 * a join there are child joins: 1831 * <pre> 1832 * | 1833 * ---------------- 1834 * | | 1835 * --------- ---------- 1836 * | | | | 1837 * Month Day Year Quarter 1838 * 1839 * | 1840 * ---------------- 1841 * | | 1842 * --------- ---------- 1843 * | | | | 1844 * Year Day Month Quarter 1845 * </pre> 1846 * <p> 1847 * When does this method do nothing? 1) when there are less than 2 levels, 1848 * 2) when any level does not have a table name, and 3) when for every table 1849 * in the relation there is not a level. In these cases, this method simply 1850 * return the original relation. 1851 * 1852 * @param relation 1853 * @param levels 1854 */ 1855 private static MondrianDef.RelationOrJoin reorder( 1856 MondrianDef.RelationOrJoin relation, 1857 RolapLevel[] levels) { 1858 // Need at least two levels, with only one level theres nothing to do. 1859 if (levels.length < 2) { 1860 return relation; 1861 } 1862 1863 Map<String, RelNode> nodeMap = new HashMap<String, RelNode>(); 1864 1865 // Create RelNode in top down order (year -> day) 1866 for (int i = 0; i < levels.length; i++) { 1867 RolapLevel level = levels[i]; 1868 1869 if (level.isAll()) { 1870 continue; 1871 } 1872 1873 // this is the table alias 1874 String tableName = level.getTableName(); 1875 if (tableName == null) { 1876 // punt, no table name 1877 return relation; 1878 } 1879 RelNode rnode = new RelNode(tableName, i); 1880 nodeMap.put(tableName, rnode); 1881 } 1882 if (! validateNodes(relation, nodeMap)) { 1883 return relation; 1884 } 1885 relation = copy(relation); 1886 1887 // Put lower levels to the left of upper levels 1888 leftToRight(relation, nodeMap); 1889 1890 // Move joins to the right side 1891 topToBottom(relation); 1892 1893 return relation; 1894 } 1895 1896 /** 1897 * The map has to be validated against the relation because there are 1898 * certain cases where we do not want to (read: can not) do reordering, for 1899 * instance, when closures are involved. 1900 * 1901 * @param relation 1902 * @param map 1903 */ 1904 private static boolean validateNodes( 1905 MondrianDef.RelationOrJoin relation, 1906 Map<String, RelNode> map) 1907 { 1908 if (relation instanceof MondrianDef.Relation) { 1909 MondrianDef.Relation table = 1910 (MondrianDef.Relation) relation; 1911 1912 RelNode relNode = RelNode.lookup(table, map); 1913 return (relNode != null); 1914 1915 } else if (relation instanceof MondrianDef.Join) { 1916 MondrianDef.Join join = (MondrianDef.Join) relation; 1917 1918 return validateNodes(join.left, map) && 1919 validateNodes(join.right, map); 1920 1921 } else { 1922 throw Util.newInternal("bad relation type " + relation); 1923 } 1924 1925 } 1926 1927 /** 1928 * Transforms the Relation moving the tables associated with 1929 * lower levels (greater level depth, i.e., Day is lower than Month) to the 1930 * left of tables with high levels. 1931 * 1932 * @param relation 1933 * @param map 1934 */ 1935 private static int leftToRight( 1936 MondrianDef.RelationOrJoin relation, 1937 Map<String, RelNode> map) 1938 { 1939 if (relation instanceof MondrianDef.Relation) { 1940 MondrianDef.Relation table = 1941 (MondrianDef.Relation) relation; 1942 1943 RelNode relNode = RelNode.lookup(table, map); 1944 // Associate the table with its RelNode!!!! This is where this 1945 // happens. 1946 relNode.table = table; 1947 1948 return relNode.depth; 1949 1950 } else if (relation instanceof MondrianDef.Join) { 1951 MondrianDef.Join join = (MondrianDef.Join) relation; 1952 int leftDepth = leftToRight(join.left, map); 1953 int rightDepth = leftToRight(join.right, map); 1954 1955 // we want the right side to be less than the left 1956 if (rightDepth > leftDepth) { 1957 // switch 1958 String leftAlias = join.leftAlias; 1959 String leftKey = join.leftKey; 1960 MondrianDef.RelationOrJoin left = join.left; 1961 join.leftAlias = join.rightAlias; 1962 join.leftKey = join.rightKey; 1963 join.left = join.right; 1964 join.rightAlias = leftAlias; 1965 join.rightKey = leftKey; 1966 join.right = left; 1967 } 1968 // Does not currently matter which is returned because currently we 1969 // only support structures where the left and right depth values 1970 // form an inclusive subset of depth values, that is, any 1971 // node with a depth value between the left or right values is 1972 // a child of this current join. 1973 return leftDepth; 1974 1975 } else { 1976 throw Util.newInternal("bad relation type " + relation); 1977 } 1978 1979 } 1980 1981 /** 1982 * Transforms so that all joins have a table as their left child and either 1983 * a table of child join on the right. 1984 * 1985 * @param relation 1986 */ 1987 private static void topToBottom(MondrianDef.RelationOrJoin relation) { 1988 if (relation instanceof MondrianDef.Table) { 1989 // nothing 1990 1991 } else if (relation instanceof MondrianDef.Join) { 1992 MondrianDef.Join join = (MondrianDef.Join) relation; 1993 1994 while (join.left instanceof MondrianDef.Join) { 1995 MondrianDef.Join jleft = (MondrianDef.Join) join.left; 1996 1997 join.right = new MondrianDef.Join( 1998 join.leftAlias, 1999 join.leftKey, 2000 jleft.right, 2001 join.rightAlias, 2002 join.rightKey, 2003 join.right); 2004 2005 join.left = jleft.left; 2006 2007 join.rightAlias = jleft.rightAlias; 2008 join.rightKey = jleft.rightKey; 2009 join.leftAlias = jleft.leftAlias; 2010 join.leftKey = jleft.leftKey; 2011 } 2012 } 2013 2014 } 2015 2016 /** 2017 * Copies a {@link mondrian.olap.MondrianDef.RelationOrJoin}. 2018 * 2019 * @param relation 2020 */ 2021 private static MondrianDef.RelationOrJoin copy(MondrianDef.RelationOrJoin relation) { 2022 if (relation instanceof MondrianDef.Table) { 2023 MondrianDef.Table table = (MondrianDef.Table) relation; 2024 return new MondrianDef.Table(table); 2025 2026 } else if (relation instanceof MondrianDef.InlineTable) { 2027 MondrianDef.InlineTable table = (MondrianDef.InlineTable) relation; 2028 return new MondrianDef.InlineTable(table); 2029 2030 } else if (relation instanceof MondrianDef.Join) { 2031 MondrianDef.Join join = (MondrianDef.Join) relation; 2032 2033 MondrianDef.RelationOrJoin left = copy(join.left); 2034 MondrianDef.RelationOrJoin right = copy(join.right); 2035 2036 return new MondrianDef.Join(join.leftAlias, join.leftKey, left, 2037 join.rightAlias, join.rightKey, right); 2038 2039 } else { 2040 throw Util.newInternal("bad relation type " + relation); 2041 } 2042 } 2043 2044 /** 2045 * Takes a relation in canonical form and snips off the 2046 * the tables with the given tableName (or table alias). The matching table 2047 * only appears once in the relation. 2048 * 2049 * @param relation 2050 * @param tableName 2051 */ 2052 private static MondrianDef.RelationOrJoin snip( 2053 MondrianDef.RelationOrJoin relation, 2054 String tableName) { 2055 if (relation instanceof MondrianDef.Table) { 2056 MondrianDef.Table table = (MondrianDef.Table) relation; 2057 // Return null if the table's name or alias matches tableName 2058 return ((table.alias != null) && table.alias.equals(tableName)) 2059 ? null 2060 : (table.name.equals(tableName) ? null : table); 2061 2062 } else if (relation instanceof MondrianDef.Join) { 2063 MondrianDef.Join join = (MondrianDef.Join) relation; 2064 2065 // snip left 2066 MondrianDef.RelationOrJoin left = snip(join.left, tableName); 2067 if (left == null) { 2068 // left got snipped so return the right 2069 // (the join is no longer a join). 2070 return join.right; 2071 2072 } else { 2073 // whatever happened on the left, save it 2074 join.left = left; 2075 2076 // snip right 2077 MondrianDef.RelationOrJoin right = snip(join.right, tableName); 2078 if (right == null) { 2079 // right got snipped so return the left. 2080 return join.left; 2081 2082 } else { 2083 // save the right, join still has right and left children 2084 // so return it. 2085 join.right = right; 2086 return join; 2087 } 2088 } 2089 2090 2091 } else { 2092 throw Util.newInternal("bad relation type " + relation); 2093 } 2094 2095 } 2096 // 2097 /////////////////////////////////////////////////////////////////////////// 2098 2099 2100 2101 public Member[] getMembersForQuery(String query, List<Member> calcMembers) { 2102 throw new UnsupportedOperationException(); 2103 } 2104 2105 /** 2106 * Finds out non joining dimensions for this cube. 2107 * Useful for finding out non joining dimensions for a stored measure from 2108 * a base cube. 2109 * 2110 * @param tuple array of members 2111 * @return Set of dimensions that do not exist (non joining) in this cube 2112 */ 2113 public Set<Dimension> nonJoiningDimensions(Member[] tuple) { 2114 Set<Dimension> otherDims = new HashSet<Dimension>(); 2115 for (Member member : tuple) { 2116 if (!member.isCalculated()) { 2117 otherDims.add(member.getDimension()); 2118 } 2119 } 2120 return nonJoiningDimensions(otherDims); 2121 } 2122 2123 /** 2124 * Finds out non joining dimensions for this cube. 2125 * Equality test for dimensions is done based on the unique name. Object 2126 * equality can't be used. 2127 * 2128 * @param otherDims Set of dimensions to be tested for existance in this cube 2129 * @return Set of dimensions that do not exist (non joining) in this cube 2130 */ 2131 public Set<Dimension> nonJoiningDimensions(Set<Dimension> otherDims) { 2132 Dimension[] baseCubeDimensions = getDimensions(); 2133 Set<String> baseCubeDimNames = new HashSet<String>(); 2134 for (Dimension baseCubeDimension : baseCubeDimensions) { 2135 baseCubeDimNames.add(baseCubeDimension.getUniqueName()); 2136 } 2137 Set<Dimension> nonJoiningDimensions = new HashSet<Dimension>(); 2138 for (Dimension otherDim : otherDims) { 2139 if (!baseCubeDimNames.contains(otherDim.getUniqueName())) { 2140 nonJoiningDimensions.add(otherDim); 2141 } 2142 } 2143 return nonJoiningDimensions; 2144 } 2145 2146 List<Member> getMeasures() { 2147 Level measuresLevel = dimensions[0].getHierarchies()[0].getLevels()[0]; 2148 return getSchemaReader().getLevelMembers(measuresLevel, true); 2149 } 2150 2151 /** 2152 * Returns this cube's fact table, null if the cube is virtual. 2153 */ 2154 MondrianDef.RelationOrJoin getFact() { 2155 return fact; 2156 } 2157 2158 /** 2159 * Returns whether this cube is virtual. We use the fact that virtual cubes 2160 * do not have fact tables. 2161 */ 2162 public boolean isVirtual() { 2163 return (fact == null); 2164 } 2165 2166 2167 /** 2168 * Locates the base cube hierarchy for a particular virtual hierarchy. 2169 * If not found, return null. This may be converted to a map lookup 2170 * or cached in some way in the future to increase performance 2171 * with cubes that have large numbers of hierarchies 2172 * 2173 * @param hierarchy virtual hierarchy 2174 * @return base cube hierarchy if found 2175 */ 2176 RolapHierarchy findBaseCubeHierarchy(RolapHierarchy hierarchy) { 2177 for (int i = 0; i < getDimensions().length; i++) { 2178 Dimension dimension = getDimensions()[i]; 2179 if (dimension.getName().equals(hierarchy.getDimension().getName())) { 2180 for (int j = 0; j < dimension.getHierarchies().length; j++) { 2181 Hierarchy hier = dimension.getHierarchies()[j]; 2182 if (hier.getName().equals(hierarchy.getName())) { 2183 return (RolapHierarchy)hier; 2184 } 2185 } 2186 } 2187 } 2188 return null; 2189 } 2190 2191 2192 /** 2193 * Locates the base cube level for a particular virtual level. 2194 * If not found, return null. This may be converted to a map lookup 2195 * or cached in some way in the future to increase performance 2196 * with cubes that have large numbers of hierarchies and levels 2197 * 2198 * @param level virtual level 2199 * @return base cube level if found 2200 */ 2201 public RolapCubeLevel findBaseCubeLevel(RolapLevel level) { 2202 for (int i = 0; i < getDimensions().length; i++) { 2203 Dimension dimension = getDimensions()[i]; 2204 if (dimension.getName().equals(level.getDimension().getName())) { 2205 for (int j = 0; j < dimension.getHierarchies().length; j++) { 2206 Hierarchy hier = dimension.getHierarchies()[j]; 2207 if (hier.getName().equals(level.getHierarchy().getName())) { 2208 for (int k = 0; k < hier.getLevels().length; k++) { 2209 Level lvl = hier.getLevels()[k]; 2210 if (lvl.getName().equals(level.getName())) { 2211 return (RolapCubeLevel)lvl; 2212 } 2213 } 2214 } 2215 } 2216 } 2217 } 2218 return null; 2219 } 2220 2221 RolapCubeDimension createDimension( 2222 MondrianDef.CubeDimension xmlCubeDimension, 2223 MondrianDef.Schema xmlSchema) 2224 { 2225 RolapCubeDimension dimension = 2226 getOrCreateDimension( 2227 xmlCubeDimension, schema, xmlSchema, 2228 dimensions.length); 2229 2230 if (! isVirtual()) { 2231 createUsages(dimension, xmlCubeDimension); 2232 } 2233 registerDimension(dimension); 2234 2235 dimension.init(xmlCubeDimension); 2236 2237 // add to dimensions array 2238 this.dimensions = (DimensionBase[]) 2239 RolapUtil.addElement(dimensions, dimension); 2240 2241 return dimension; 2242 } 2243 2244 public OlapElement lookupChild(SchemaReader schemaReader, Id.Segment s) { 2245 return lookupChild(schemaReader, s, MatchType.EXACT); 2246 } 2247 2248 public OlapElement lookupChild( 2249 SchemaReader schemaReader, Id.Segment s, MatchType matchType) 2250 { 2251 // Note that non-exact matches aren't supported at this level, 2252 // so the matchType is ignored 2253 String status = null; 2254 OlapElement oe = super.lookupChild(schemaReader, s, MatchType.EXACT); 2255 2256 if (oe == null) { 2257 HierarchyUsage[] usages = getUsagesBySource(s.name); 2258 if (usages.length > 0) { 2259 StringBuilder buf = new StringBuilder(64); 2260 buf.append("RolapCube.lookupChild: "); 2261 buf.append("In cube \""); 2262 buf.append(getName()); 2263 buf.append("\" use of unaliased Dimension name \""); 2264 buf.append(s); 2265 if (usages.length == 1) { 2266 // ERROR: this will work but is bad coding 2267 buf.append("\" rather than the alias name "); 2268 buf.append("\""); 2269 buf.append(usages[0].getName()); 2270 buf.append("\" "); 2271 getLogger().error(buf.toString()); 2272 throw new MondrianException(buf.toString()); 2273 } else { 2274 // ERROR: this is not allowed 2275 buf.append("\" rather than one of the alias names "); 2276 for (HierarchyUsage usage : usages) { 2277 buf.append("\""); 2278 buf.append(usage.getName()); 2279 buf.append("\" "); 2280 } 2281 getLogger().error(buf.toString()); 2282 throw new MondrianException(buf.toString()); 2283 } 2284 } 2285 } 2286 2287 if (getLogger().isDebugEnabled()) { 2288 if (!s.matches("Measures")) { 2289 HierarchyUsage hierUsage = getUsageByName(s.name); 2290 if (hierUsage == null) { 2291 status = "hierUsage == null"; 2292 } else { 2293 status = "hierUsage == " + (hierUsage.isShared() ? "shared" : "not shared"); 2294 } 2295 } 2296 StringBuilder buf = new StringBuilder(64); 2297 buf.append("RolapCube.lookupChild: "); 2298 buf.append("name="); 2299 buf.append(getName()); 2300 buf.append(", childname="); 2301 buf.append(s); 2302 if (status != null) { 2303 buf.append(", status="); 2304 buf.append(status); 2305 } 2306 if (oe == null) { 2307 buf.append(" returning null"); 2308 } else { 2309 buf.append(" returning elementname=").append(oe.getName()); 2310 } 2311 getLogger().debug(buf.toString()); 2312 } 2313 2314 return oe; 2315 } 2316 2317 /** 2318 * Returns the the measures hierarchy. 2319 */ 2320 public Hierarchy getMeasuresHierarchy() { 2321 return measuresHierarchy; 2322 } 2323 2324 // RME 2325 public List<RolapMember> getMeasuresMembers() { 2326 return measuresHierarchy.getMemberReader().getMembers(); 2327 } 2328 2329 public Member createCalculatedMember(String xml) { 2330 MondrianDef.CalculatedMember xmlCalcMember; 2331 try { 2332 final Parser xmlParser = XOMUtil.createDefaultParser(); 2333 final DOMWrapper def = xmlParser.parse(xml); 2334 final String tagName = def.getTagName(); 2335 if (tagName.equals("CalculatedMember")) { 2336 xmlCalcMember = new MondrianDef.CalculatedMember(def); 2337 } else { 2338 throw new XOMException("Got <" + tagName + 2339 "> when expecting <CalculatedMember>"); 2340 } 2341 } catch (XOMException e) { 2342 throw Util.newError(e, 2343 "Error while creating calculated member from XML [" + 2344 xml + "]"); 2345 } 2346 2347 final List<RolapMember> memberList = new ArrayList<RolapMember>(); 2348 createCalcMembersAndNamedSets( 2349 Collections.singletonList(xmlCalcMember), 2350 Collections.<MondrianDef.NamedSet>emptyList(), 2351 memberList, 2352 new ArrayList<Formula>(), 2353 this, 2354 true); 2355 assert memberList.size() == 1; 2356 return memberList.get(0); 2357 } 2358 2359 /** 2360 * Schema reader which works from the perspective of a particular cube 2361 * (and hence includes calculated members defined in that cube) and also 2362 * applies the access-rights of a given role. 2363 */ 2364 private class RolapCubeSchemaReader extends RolapSchemaReader { 2365 public RolapCubeSchemaReader(Role role) { 2366 super(role, schema); 2367 assert role != null : "precondition: role != null"; 2368 } 2369 2370 public List<Member> getLevelMembers( 2371 Level level, 2372 boolean includeCalculated) 2373 { 2374 List<Member> members = super.getLevelMembers(level, false); 2375 if (includeCalculated) { 2376 members = Util.addLevelCalculatedMembers(this, level, members); 2377 } 2378 return members; 2379 } 2380 2381 public Member getCalculatedMember(List<Id.Segment> nameParts) { 2382 final String uniqueName = Util.implode(nameParts); 2383 for (Formula formula : calculatedMembers) { 2384 final String formulaUniqueName = 2385 formula.getMdxMember().getUniqueName(); 2386 if (formulaUniqueName.equals(uniqueName) && 2387 getRole().canAccess(formula.getMdxMember())) 2388 { 2389 return formula.getMdxMember(); 2390 } 2391 } 2392 return null; 2393 } 2394 2395 public NamedSet getNamedSet(List<Id.Segment> segments) { 2396 if (segments.size() == 1) { 2397 Id.Segment segment = segments.get(0); 2398 for (Formula namedSet : namedSets) { 2399 if (segment.matches(namedSet.getName())) { 2400 return namedSet.getNamedSet(); 2401 } 2402 } 2403 } 2404 return super.getNamedSet(segments); 2405 } 2406 2407 public List<Member> getCalculatedMembers(Hierarchy hierarchy) { 2408 ArrayList<Member> list = new ArrayList<Member>(); 2409 2410 if (getRole().getAccess(hierarchy) == Access.NONE) { 2411 return list; 2412 } 2413 2414 for (Member member : getCalculatedMembers()) { 2415 if (member.getHierarchy().equals(hierarchy)) { 2416 list.add(member); 2417 } 2418 } 2419 return list; 2420 } 2421 2422 public List<Member> getCalculatedMembers(Level level) { 2423 List<Member> list = new ArrayList<Member>(); 2424 2425 if (getRole().getAccess(level) == Access.NONE) { 2426 return list; 2427 } 2428 2429 for (Member member : getCalculatedMembers()) { 2430 if (member.getLevel().equals(level)) { 2431 list.add(member); 2432 } 2433 } 2434 return list; 2435 } 2436 2437 public List<Member> getCalculatedMembers() { 2438 List<Member> list = roleToAccessibleCalculatedMembers.get(getRole()); 2439 if (list == null) { 2440 list = new ArrayList<Member>(); 2441 for (Formula formula : calculatedMembers) { 2442 Member member = formula.getMdxMember(); 2443 if (getRole().canAccess(member)) { 2444 list.add(member); 2445 } 2446 } 2447 // calculatedMembers array may not have been initialized 2448 if (list.size() > 0) { 2449 roleToAccessibleCalculatedMembers.put(getRole(), list); 2450 } 2451 } 2452 return list; 2453 } 2454 2455 public Member getMemberByUniqueName( 2456 List<Id.Segment> uniqueNameParts, 2457 boolean failIfNotFound, 2458 MatchType matchType) 2459 { 2460 Member member = 2461 (Member) lookupCompound( 2462 RolapCube.this, uniqueNameParts, 2463 failIfNotFound, Category.Member, 2464 matchType); 2465 if (!failIfNotFound && member == null) { 2466 return null; 2467 } 2468 if (getRole().canAccess(member)) { 2469 return member; 2470 } else { 2471 return null; 2472 } 2473 } 2474 2475 public Cube getCube() { 2476 return RolapCube.this; 2477 } 2478 } 2479 2480 /** 2481 * Visitor that walks an MDX parse tree containing formulas 2482 * associated with calculated members defined in a base cube but 2483 * referenced from a virtual cube. When walking the tree, look 2484 * for other calculated members as well as stored measures. Keep 2485 * track of all stored measures found, and for the calculated members, 2486 * once the formula of that calculated member has been visited, resolve 2487 * the calculated member relative to the virtual cube. 2488 */ 2489 private class MeasureFinder extends MdxVisitorImpl 2490 { 2491 /** 2492 * The virtual cube where the original calculated member was 2493 * referenced from 2494 */ 2495 private RolapCube virtualCube; 2496 2497 /** 2498 * The base cube where the original calculated member is defined 2499 */ 2500 private RolapCube baseCube; 2501 2502 /** 2503 * The measures level corresponding to the virtual cube 2504 */ 2505 private RolapLevel measuresLevel; 2506 2507 /** 2508 * List of measures found 2509 */ 2510 private List<RolapVirtualCubeMeasure> measuresFound; 2511 2512 /** 2513 * List of calculated members found 2514 */ 2515 private List<RolapCalculatedMember> calcMembersSeen; 2516 2517 public MeasureFinder( 2518 RolapCube virtualCube, 2519 RolapCube baseCube, 2520 RolapLevel measuresLevel) 2521 { 2522 this.virtualCube = virtualCube; 2523 this.baseCube = baseCube; 2524 this.measuresLevel = measuresLevel; 2525 this.measuresFound = new ArrayList<RolapVirtualCubeMeasure>(); 2526 this.calcMembersSeen = new ArrayList<RolapCalculatedMember>(); 2527 } 2528 2529 public Object visit(MemberExpr memberExpr) 2530 { 2531 Member member = memberExpr.getMember(); 2532 if (member instanceof RolapCalculatedMember) { 2533 // ignore the calculated member if we've already processed 2534 // it in another reference 2535 if (calcMembersSeen.contains(member)) { 2536 return null; 2537 } 2538 RolapCalculatedMember calcMember = 2539 (RolapCalculatedMember) member; 2540 Formula formula = calcMember.getFormula(); 2541 formula.accept(this); 2542 calcMembersSeen.add(calcMember); 2543 2544 // now that we've located all measures referenced in the 2545 // calculated member's formula, resolve the calculated 2546 // member relative to the virtual cube 2547 virtualCube.setMeasuresHierarchyMemberReader( 2548 new CacheMemberReader( 2549 new MeasureMemberSource( 2550 virtualCube.measuresHierarchy, 2551 Util.<RolapMember>cast(measuresFound)))); 2552 2553 MondrianDef.CalculatedMember xmlCalcMember = 2554 schema.lookupXmlCalculatedMember( 2555 calcMember.getUniqueName(), 2556 baseCube.name); 2557 createCalcMembersAndNamedSets( 2558 Collections.singletonList(xmlCalcMember), 2559 Collections.<MondrianDef.NamedSet>emptyList(), 2560 new ArrayList<RolapMember>(), 2561 new ArrayList<Formula>(), 2562 virtualCube, 2563 false); 2564 return null; 2565 2566 } else if (member instanceof RolapBaseCubeMeasure) { 2567 RolapBaseCubeMeasure baseMeasure = 2568 (RolapBaseCubeMeasure) member; 2569 RolapVirtualCubeMeasure virtualCubeMeasure = 2570 new RolapVirtualCubeMeasure( 2571 null, 2572 measuresLevel, 2573 baseMeasure); 2574 if (!measuresFound.contains(virtualCubeMeasure)) { 2575 measuresFound.add(virtualCubeMeasure); 2576 } 2577 } 2578 2579 return null; 2580 } 2581 2582 public List<RolapVirtualCubeMeasure> getMeasuresFound() 2583 { 2584 return measuresFound; 2585 } 2586 } 2587 2588 public static class CubeComparator implements Comparator<RolapCube> 2589 { 2590 public int compare(RolapCube c1, RolapCube c2) 2591 { 2592 return c1.getName().compareTo(c2.getName()); 2593 } 2594 } 2595 } 2596 2597 // End RolapCube.java