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