001/* 002// This software is subject to the terms of the Eclipse Public License v1.0 003// Agreement, available at the following URL: 004// http://www.eclipse.org/legal/epl-v10.html. 005// You must accept the terms of that agreement to use this software. 006// 007// Copyright (C) 2001-2005 Julian Hyde 008// Copyright (C) 2005-2013 Pentaho and others 009// All Rights Reserved. 010*/ 011package mondrian.rolap; 012 013import mondrian.olap.*; 014import mondrian.olap.fun.VisualTotalsFunDef; 015import mondrian.rolap.TupleReader.MemberBuilder; 016import mondrian.rolap.sql.MemberChildrenConstraint; 017import mondrian.rolap.sql.TupleConstraint; 018import mondrian.util.UnsupportedList; 019 020import java.sql.SQLException; 021import java.util.*; 022 023/** 024 * Hierarchy that is associated with a specific Cube. 025 * 026 * @author Will Gorman, 19 October 2007 027 */ 028public class RolapCubeHierarchy extends RolapHierarchy { 029 030 private final boolean cachingEnabled = 031 MondrianProperties.instance().EnableRolapCubeMemberCache.get(); 032 private final RolapCubeDimension cubeDimension; 033 private final RolapHierarchy rolapHierarchy; 034 private final RolapCubeLevel currentNullLevel; 035 private RolapCubeMember currentNullMember; 036 private RolapCubeMember currentAllMember; 037 private final MondrianDef.RelationOrJoin currentRelation; 038 private final RolapCubeHierarchyMemberReader reader; 039 private HierarchyUsage usage; 040 private final Map<String, String> aliases = new HashMap<String, String>(); 041 private RolapCubeMember currentDefaultMember; 042 private final int ordinal; 043 044 /** 045 * True if the hierarchy is degenerate - has no dimension table of its own, 046 * just drives from the cube's fact table. 047 */ 048 protected final boolean usingCubeFact; 049 050 /** 051 * Length of prefix to be removed when translating member unique names, or 052 * 0 if no translation is necessary. 053 */ 054 private final int removePrefixLength; 055 056 // redundant copy of {@link #levels} with tigher type 057 private final RolapCubeLevel[] cubeLevels; 058 059 /** 060 * Creates a RolapCubeHierarchy. 061 * 062 * @param cubeDimension Dimension 063 * @param cubeDim XML dimension element 064 * @param rolapHierarchy Wrapped hierarchy 065 * @param subName Name of hierarchy within dimension 066 * @param ordinal Ordinal of hierarchy within cube 067 */ 068 public RolapCubeHierarchy( 069 RolapCubeDimension cubeDimension, 070 MondrianDef.CubeDimension cubeDim, 071 RolapHierarchy rolapHierarchy, 072 String subName, 073 int ordinal) 074 { 075 super( 076 cubeDimension, 077 subName, 078 applyPrefix(cubeDim, rolapHierarchy.getCaption()), 079 rolapHierarchy.isVisible(), 080 applyPrefix(cubeDim, rolapHierarchy.getDescription()), 081 rolapHierarchy.hasAll(), 082 null, 083 rolapHierarchy.getAnnotationMap()); 084 this.ordinal = ordinal; 085 if (!cubeDimension.getCube().isVirtual()) { 086 this.usage = 087 new HierarchyUsage( 088 cubeDimension.getCube(), rolapHierarchy, cubeDim); 089 } 090 091 this.rolapHierarchy = rolapHierarchy; 092 this.cubeDimension = cubeDimension; 093 this.xmlHierarchy = rolapHierarchy.getXmlHierarchy(); 094 // this relation should equal the name of the new dimension table 095 // The null member belongs to a level with very similar properties to 096 // the 'all' level. 097 this.currentNullLevel = new RolapCubeLevel(nullLevel, this); 098 099 usingCubeFact = 100 (cubeDimension.getCube().getFact() == null 101 || cubeDimension.getCube().getFact().equals( 102 rolapHierarchy.getRelation())); 103 104 // re-alias names if necessary 105 if (!usingCubeFact) { 106 // join expressions are columns only 107 assert (usage.getJoinExp() instanceof MondrianDef.Column); 108 currentRelation = 109 this.cubeDimension.getCube().getStar().getUniqueRelation( 110 rolapHierarchy.getRelation(), 111 usage.getForeignKey(), 112 ((MondrianDef.Column)usage.getJoinExp()).getColumnName(), 113 usage.getJoinTable().getAlias()); 114 } else { 115 currentRelation = rolapHierarchy.getRelation(); 116 } 117 extractNewAliases(rolapHierarchy.getRelation(), currentRelation); 118 this.relation = currentRelation; 119 this.levels = 120 this.cubeLevels = 121 new RolapCubeLevel[rolapHierarchy.getLevels().length]; 122 for (int i = 0; i < rolapHierarchy.getLevels().length; i++) { 123 this.cubeLevels[i] = 124 new RolapCubeLevel( 125 (RolapLevel) rolapHierarchy.getLevels()[i], this); 126 if (i == 0) { 127 if (rolapHierarchy.getAllMember() != null) { 128 RolapCubeLevel allLevel; 129 if (hasAll()) { 130 allLevel = this.cubeLevels[0]; 131 } else { 132 // create an all level if one doesn't normally 133 // exist in the hierarchy 134 allLevel = 135 new RolapCubeLevel( 136 rolapHierarchy.getAllMember().getLevel(), 137 this); 138 allLevel.init(cubeDimension.xmlDimension); 139 } 140 141 this.currentAllMember = 142 new RolapAllCubeMember( 143 rolapHierarchy.getAllMember(), 144 allLevel); 145 } 146 } 147 } 148 149 // Compute whether the unique names of members of this hierarchy are 150 // different from members of the underlying hierarchy. If so, compute 151 // the length of the prefix to be removed before this hierarchy's unique 152 // name is added. For example, if this.uniqueName is "[Ship Time]" and 153 // rolapHierarchy.uniqueName is "[Time]", remove prefixLength will be 154 // length("[Ship Time]") = 11. 155 if (uniqueName.equals(rolapHierarchy.getUniqueName())) { 156 this.removePrefixLength = 0; 157 } else { 158 this.removePrefixLength = rolapHierarchy.getUniqueName().length(); 159 } 160 161 if (cubeDimension.isHighCardinality() || !cachingEnabled) { 162 this.reader = new NoCacheRolapCubeHierarchyMemberReader(); 163 } else { 164 this.reader = new CacheRolapCubeHierarchyMemberReader(); 165 } 166 } 167 168 /** 169 * Applies a prefix to a caption or description of a hierarchy in a shared 170 * dimension. Ensures that if a dimension is used more than once in the same 171 * cube then the hierarchies are distinguishable. 172 * 173 * <p>For example, if the [Time] dimension is imported as [Order Time] and 174 * [Ship Time], then the [Time].[Weekly] hierarchy would have caption 175 * "Order Time.Weekly caption" and description "Order Time.Weekly 176 * description". 177 * 178 * <p>If the dimension usage has a caption, it overrides. 179 * 180 * <p>If the dimension usage has a null name, or the name is the same 181 * as the dimension, and no caption, then no prefix is applied. 182 * 183 * @param cubeDim Cube dimension (maybe a usage of a shared dimension) 184 * @param caption Caption or description 185 * @return Caption or description, possibly prefixed by dimension role name 186 */ 187 private static String applyPrefix( 188 MondrianDef.CubeDimension cubeDim, 189 String caption) 190 { 191 if (caption == null) { 192 return null; 193 } 194 if (cubeDim instanceof MondrianDef.DimensionUsage) { 195 final MondrianDef.DimensionUsage dimensionUsage = 196 (MondrianDef.DimensionUsage) cubeDim; 197 if (dimensionUsage.name != null 198 && !dimensionUsage.name.equals(dimensionUsage.source)) 199 { 200 if (dimensionUsage.caption != null) { 201 return dimensionUsage.caption + "." + caption; 202 } else { 203 return dimensionUsage.name + "." + caption; 204 } 205 } 206 } 207 return caption; 208 } 209 210 @Override 211 public RolapCubeLevel[] getLevels() { 212 return cubeLevels; 213 } 214 215 public String getAllMemberName() { 216 return rolapHierarchy.getAllMemberName(); 217 } 218 219 public String getSharedHierarchyName() { 220 return rolapHierarchy.getSharedHierarchyName(); 221 } 222 223 public String getAllLevelName() { 224 return rolapHierarchy.getAllLevelName(); 225 } 226 227 public boolean isUsingCubeFact() { 228 return usingCubeFact; 229 } 230 231 public String lookupAlias(String origTable) { 232 return aliases.get(origTable); 233 } 234 235 public final RolapHierarchy getRolapHierarchy() { 236 return rolapHierarchy; 237 } 238 239 public final int getOrdinalInCube() { 240 return ordinal; 241 } 242 243 /** 244 * Populates the alias map for the old and new relations. 245 * 246 * <p>This method may be simplified when we obsolete 247 * {@link mondrian.rolap.HierarchyUsage}. 248 * 249 * @param oldrel Original relation, as defined in the schema 250 * @param newrel New star relation, generated by RolapStar, canonical, and 251 * shared between all cubes with similar structure 252 */ 253 protected void extractNewAliases( 254 MondrianDef.RelationOrJoin oldrel, 255 MondrianDef.RelationOrJoin newrel) 256 { 257 if (oldrel == null && newrel == null) { 258 return; 259 } else if (oldrel instanceof MondrianDef.Relation 260 && newrel instanceof MondrianDef.Relation) 261 { 262 aliases.put( 263 ((MondrianDef.Relation) oldrel).getAlias(), 264 ((MondrianDef.Relation) newrel).getAlias()); 265 } else if (oldrel instanceof MondrianDef.Join 266 && newrel instanceof MondrianDef.Join) 267 { 268 MondrianDef.Join oldjoin = (MondrianDef.Join)oldrel; 269 MondrianDef.Join newjoin = (MondrianDef.Join)newrel; 270 extractNewAliases(oldjoin.left, newjoin.left); 271 extractNewAliases(oldjoin.right, newjoin.right); 272 } else { 273 throw new UnsupportedOperationException(); 274 } 275 } 276 277 public boolean equals(Object o) { 278 if (this == o) { 279 return true; 280 } 281 if (!(o instanceof RolapCubeHierarchy)) { 282 return false; 283 } 284 285 RolapCubeHierarchy that = (RolapCubeHierarchy)o; 286 return cubeDimension.equals(that.cubeDimension) 287 && getUniqueName().equals(that.getUniqueName()); 288 } 289 290 protected int computeHashCode() { 291 return Util.hash(super.computeHashCode(), this.cubeDimension.cube); 292 } 293 294 public Member createMember( 295 Member parent, 296 Level level, 297 String name, 298 Formula formula) 299 { 300 RolapLevel rolapLevel = ((RolapCubeLevel)level).getRolapLevel(); 301 if (formula == null) { 302 RolapMember rolapParent = null; 303 if (parent != null) { 304 rolapParent = ((RolapCubeMember)parent).getRolapMember(); 305 } 306 RolapMember member = 307 new RolapMemberBase(rolapParent, rolapLevel, name); 308 return new RolapCubeMember( 309 (RolapCubeMember) parent, member, 310 (RolapCubeLevel) level); 311 } else if (level.getDimension().isMeasures()) { 312 RolapCalculatedMeasure member = 313 new RolapCalculatedMeasure( 314 (RolapMember) parent, rolapLevel, name, formula); 315 return new RolapCubeMember( 316 (RolapCubeMember) parent, member, 317 (RolapCubeLevel) level); 318 } else { 319 RolapCalculatedMember member = 320 new RolapCalculatedMember( 321 (RolapMember) parent, rolapLevel, name, formula); 322 return new RolapCubeMember( 323 (RolapCubeMember) parent, member, 324 (RolapCubeLevel) level); 325 } 326 } 327 328 329 boolean tableExists(String tableName) { 330 return rolapHierarchy.tableExists(tableName); 331 } 332 333 /** 334 * The currentRelation object is derived from the shared relation object 335 * it is generated via the RolapStar object, and contains unique aliases 336 * for it's particular join path 337 * 338 * @return rolap cube hierarchy relation 339 */ 340 public MondrianDef.RelationOrJoin getRelation() { 341 return currentRelation; 342 } 343 344 // override with stricter return type; make final, important for performance 345 public final RolapCubeMember getDefaultMember() { 346 if (currentDefaultMember == null) { 347 reader.getRootMembers(); 348 currentDefaultMember = 349 bootstrapLookup( 350 (RolapMember) rolapHierarchy.getDefaultMember()); 351 } 352 return currentDefaultMember; 353 } 354 355 /** 356 * Looks up a {@link RolapCubeMember} corresponding to a {@link RolapMember} 357 * of the underlying hierarchy. Safe to be called while the hierarchy is 358 * initializing. 359 * 360 * @param rolapMember Member of underlying hierarchy 361 * @return Member of this hierarchy 362 */ 363 private RolapCubeMember bootstrapLookup(RolapMember rolapMember) { 364 RolapCubeMember parent = 365 rolapMember.getParentMember() == null 366 ? null 367 : rolapMember.getParentMember().isAll() 368 ? currentAllMember 369 : bootstrapLookup(rolapMember.getParentMember()); 370 RolapCubeLevel level = cubeLevels[rolapMember.getLevel().getDepth()]; 371 return reader.lookupCubeMember(parent, rolapMember, level); 372 } 373 374 public Member getNullMember() { 375 // use lazy initialization to get around bootstrap issues 376 if (currentNullMember == null) { 377 currentNullMember = 378 new RolapCubeMember( 379 null, 380 (RolapMember) rolapHierarchy.getNullMember(), 381 currentNullLevel); 382 } 383 return currentNullMember; 384 } 385 386 /** 387 * Returns the 'all' member. 388 */ 389 public RolapCubeMember getAllMember() { 390 return currentAllMember; 391 } 392 393 void setMemberReader(MemberReader memberReader) { 394 rolapHierarchy.setMemberReader(memberReader); 395 } 396 397 MemberReader getMemberReader() { 398 return reader; 399 } 400 401 public void setDefaultMember(Member defaultMeasure) { 402 // refactor this! 403 rolapHierarchy.setDefaultMember(defaultMeasure); 404 405 RolapCubeLevel level = 406 new RolapCubeLevel( 407 (RolapLevel)rolapHierarchy.getDefaultMember().getLevel(), 408 this); 409 currentDefaultMember = 410 new RolapCubeMember( 411 null, 412 (RolapMember) rolapHierarchy.getDefaultMember(), 413 level); 414 } 415 416 void init(MondrianDef.CubeDimension xmlDimension) { 417 // first init shared hierarchy 418 rolapHierarchy.init(xmlDimension); 419 // second init cube hierarchy 420 super.init(xmlDimension); 421 } 422 423 /** 424 * Converts the unique name of a member of the underlying hierarchy to 425 * the appropriate name for this hierarchy. 426 * 427 * <p>For example, if the shared hierarchy is [Time].[Quarterly] and the 428 * hierarchy usage is [Ship Time].[Quarterly], then [Time].[1997].[Q1] would 429 * be translated to [Ship Time].[Quarerly].[1997].[Q1]. 430 * 431 * @param memberUniqueName Unique name of member from underlying hierarchy 432 * @return Translated unique name 433 */ 434 final String convertMemberName(String memberUniqueName) { 435 if (removePrefixLength > 0 436 && !memberUniqueName.startsWith(uniqueName)) 437 { 438 return uniqueName + memberUniqueName.substring(removePrefixLength); 439 } 440 return memberUniqueName; 441 } 442 443 public final RolapCube getCube() { 444 return cubeDimension.cube; 445 } 446 447 private static RolapCubeMember createAncestorMembers( 448 RolapCubeHierarchyMemberReader memberReader, 449 RolapCubeLevel level, 450 RolapMember member) 451 { 452 if (member == null) { 453 return null; 454 } 455 RolapCubeMember parent = null; 456 if (member.getParentMember() != null) { 457 parent = 458 createAncestorMembers( 459 memberReader, 460 level.getParentLevel(), 461 member.getParentMember()); 462 } 463 return memberReader.lookupCubeMember(parent, member, level); 464 } 465 466 /** 467 * TODO: Since this is part of a caching strategy, should be implemented 468 * as a Strategy Pattern, avoiding hierarchy. 469 */ 470 public static interface RolapCubeHierarchyMemberReader 471 extends MemberReader 472 { 473 public RolapCubeMember lookupCubeMember( 474 final RolapCubeMember parent, 475 final RolapMember member, 476 final RolapCubeLevel level); 477 478 public MemberCacheHelper getRolapCubeMemberCacheHelper(); 479 } 480 481 /****** 482 483 RolapCubeMember Caching Approach: 484 485 - RolapHierarchy.SmartMemberReader.SmartCacheHelper -> 486 This is the shared cache across shared hierarchies. This 487 member cache only 488 contains members loaded by non-cube specific member lookups. This cache 489 should only contain RolapMembers, not RolapCubeMembers 490 491 - RolapCubeHierarchy.RolapCubeHierarchyMemberReader.rolapCubeCacheHelper -> 492 This cache contains the RolapCubeMember objects, which are cube specific 493 wrappers of shared members. 494 495 - RolapCubeHierarchy.RolapCubeHierarchyMemberReader.SmartCacheHelper -> 496 This is the inherited shared cache from SmartMemberReader, and 497 is used when a join with the fact table is necessary, aka a 498 SqlContextConstraint is used. This cache may be redundant with 499 rolapCubeCacheHelper. 500 501 - A Special note regarding RolapCubeHierarchyMemberReader.cubeSource - 502 This class was required for the special situation getMemberBuilder() 503 method call from RolapNativeSet. This class utilizes both the 504 rolapCubeCacheHelper class for storing RolapCubeMembers, and also the 505 RolapCubeHierarchyMemberReader's inherited SmartCacheHelper. 506 507 508 ******/ 509 510 511 /** 512 * member reader wrapper - uses existing member reader, 513 * but wraps and caches all intermediate members. 514 * 515 * <p>Synchronization. Most synchronization takes place within 516 * SmartMemberReader. All synchronization is done on the cacheHelper 517 * object. 518 */ 519 public class CacheRolapCubeHierarchyMemberReader 520 extends SmartMemberReader 521 implements RolapCubeHierarchyMemberReader 522 { 523 /** 524 * cubeSource is passed as our member builder 525 */ 526 protected final RolapCubeSqlMemberSource cubeSource; 527 528 /** 529 * this cache caches RolapCubeMembers that are light wrappers around 530 * shared and non-shared Hierarchy RolapMembers. The inherited 531 * cacheHelper object contains non-shared hierarchy RolapMembers. 532 * non-shared hierarchy RolapMembers are created when a member lookup 533 * involves the Cube's fact table. 534 */ 535 protected MemberCacheHelper rolapCubeCacheHelper; 536 private final boolean enableCache = 537 MondrianProperties.instance().EnableRolapCubeMemberCache.get(); 538 539 public CacheRolapCubeHierarchyMemberReader() { 540 super(new SqlMemberSource(RolapCubeHierarchy.this)); 541 rolapCubeCacheHelper = 542 new MemberCacheHelper(RolapCubeHierarchy.this); 543 544 cubeSource = 545 new RolapCubeSqlMemberSource( 546 this, 547 RolapCubeHierarchy.this, 548 rolapCubeCacheHelper, 549 cacheHelper); 550 551 cubeSource.setCache(getMemberCache()); 552 } 553 554 public MemberBuilder getMemberBuilder() { 555 return this.cubeSource; 556 } 557 558 public MemberCacheHelper getRolapCubeMemberCacheHelper() { 559 return rolapCubeCacheHelper; 560 } 561 562 public List<RolapMember> getRootMembers() { 563 if (rootMembers == null) { 564 rootMembers = getMembersInLevel(cubeLevels[0]); 565 } 566 return rootMembers; 567 } 568 569 protected void readMemberChildren( 570 List<RolapMember> parentMembers, 571 List<RolapMember> children, 572 MemberChildrenConstraint constraint) 573 { 574 List<RolapMember> rolapChildren = new ArrayList<RolapMember>(); 575 List<RolapMember> rolapParents = new ArrayList<RolapMember>(); 576 Map<String, RolapCubeMember> lookup = 577 new HashMap<String, RolapCubeMember>(); 578 579 // extract RolapMembers from their RolapCubeMember objects 580 // populate lookup for reconnecting parents and children 581 for (RolapMember member : parentMembers) { 582 if (member instanceof VisualTotalsFunDef.VisualTotalMember) { 583 continue; 584 } 585 final RolapCubeMember cubeMember = (RolapCubeMember) member; 586 final RolapMember rolapMember = cubeMember.getRolapMember(); 587 lookup.put(rolapMember.getUniqueName(), cubeMember); 588 rolapParents.add(rolapMember); 589 } 590 591 // get member children from shared member reader if possible, 592 // if not get them from our own source 593 boolean joinReq = 594 (constraint instanceof SqlContextConstraint); 595 if (joinReq) { 596 super.readMemberChildren( 597 parentMembers, rolapChildren, constraint); 598 } else { 599 rolapHierarchy.getMemberReader().getMemberChildren( 600 rolapParents, rolapChildren, constraint); 601 } 602 603 // now lookup or create RolapCubeMember 604 for (RolapMember currMember : rolapChildren) { 605 RolapCubeMember parent = 606 lookup.get( 607 currMember.getParentMember().getUniqueName()); 608 RolapCubeLevel level = 609 parent.getLevel().getChildLevel(); 610 if (level == null) { 611 // most likely a parent child hierarchy 612 level = parent.getLevel(); 613 } 614 RolapCubeMember newmember = 615 lookupCubeMember( 616 parent, currMember, level); 617 children.add(newmember); 618 } 619 620 // Put them in a temporary hash table first. Register them later, 621 // when we know their size (hence their 'cost' to the cache pool). 622 Map<RolapMember, List<RolapMember>> tempMap = 623 new HashMap<RolapMember, List<RolapMember>>(); 624 for (RolapMember member1 : parentMembers) { 625 tempMap.put(member1, Collections.<RolapMember>emptyList()); 626 } 627 628 // note that this stores RolapCubeMembers in our cache, 629 // which also stores RolapMembers. 630 631 for (RolapMember child : children) { 632 // todo: We could optimize here. If members.length is small, it's 633 // more efficient to drive from members, rather than hashing 634 // children.length times. We could also exploit the fact that the 635 // result is sorted by ordinal and therefore, unless the "members" 636 // contains members from different levels, children of the same 637 // member will be contiguous. 638 assert child != null : "child"; 639 final RolapMember parentMember = child.getParentMember(); 640 List<RolapMember> cacheList = tempMap.get(parentMember); 641 if (cacheList == null) { 642 // The list is null if, due to dropped constraints, we now 643 // have a children list of a member we didn't explicitly 644 // ask for it. Adding it to the cache would be viable, but 645 // let's ignore it. 646 continue; 647 } else if (cacheList == Collections.EMPTY_LIST) { 648 cacheList = new ArrayList<RolapMember>(); 649 tempMap.put(parentMember, cacheList); 650 } 651 cacheList.add(child); 652 } 653 654 synchronized (cacheHelper) { 655 for (Map.Entry<RolapMember, List<RolapMember>> entry 656 : tempMap.entrySet()) 657 { 658 final RolapMember member = entry.getKey(); 659 if (rolapCubeCacheHelper.getChildrenFromCache( 660 member, constraint) == null) 661 { 662 final List<RolapMember> cacheList = entry.getValue(); 663 if (enableCache) { 664 rolapCubeCacheHelper.putChildren( 665 member, constraint, cacheList); 666 } 667 } 668 } 669 } 670 } 671 672 public Map<? extends Member, Access> getMemberChildren( 673 List<RolapMember> parentMembers, 674 List<RolapMember> children, 675 MemberChildrenConstraint constraint) 676 { 677 synchronized (cacheHelper) { 678 checkCacheStatus(); 679 680 List<RolapMember> missed = new ArrayList<RolapMember>(); 681 for (RolapMember parentMember : parentMembers) { 682 List<RolapMember> list = 683 rolapCubeCacheHelper.getChildrenFromCache( 684 parentMember, constraint); 685 if (list == null) { 686 // the null member has no children 687 if (!parentMember.isNull()) { 688 missed.add(parentMember); 689 } 690 } else { 691 children.addAll(list); 692 } 693 } 694 if (missed.size() > 0) { 695 readMemberChildren(missed, children, constraint); 696 } 697 } 698 return Util.toNullValuesMap(children); 699 } 700 701 702 public List<RolapMember> getMembersInLevel( 703 RolapLevel level, 704 TupleConstraint constraint) 705 { 706 synchronized (cacheHelper) { 707 checkCacheStatus(); 708 709 List<RolapMember> members = 710 rolapCubeCacheHelper.getLevelMembersFromCache( 711 level, constraint); 712 if (members != null) { 713 return members; 714 } 715 716 // if a join is required, we need to pass in the RolapCubeLevel 717 // vs. the regular level 718 boolean joinReq = 719 (constraint instanceof SqlContextConstraint); 720 List<RolapMember> list; 721 final RolapCubeLevel cubeLevel = (RolapCubeLevel) level; 722 if (!joinReq) { 723 list = 724 rolapHierarchy.getMemberReader().getMembersInLevel( 725 cubeLevel.getRolapLevel(), constraint); 726 } else { 727 list = 728 super.getMembersInLevel( 729 level, constraint); 730 } 731 List<RolapMember> newlist = new ArrayList<RolapMember>(); 732 for (RolapMember member : list) { 733 // note that there is a special case for the all member 734 735 // REVIEW: disabled, to see what happens. if this code is 736 // for performance, we should check level.isAll at the top 737 // of the method; if it is for correctness, leave the code 738 // in 739 if (false && member == rolapHierarchy.getAllMember()) { 740 newlist.add(getAllMember()); 741 } else { 742 RolapCubeMember cubeMember = 743 lookupCubeMemberWithParent( 744 member, 745 cubeLevel); 746 newlist.add(cubeMember); 747 } 748 } 749 rolapCubeCacheHelper.putLevelMembersInCache( 750 level, constraint, newlist); 751 752 return newlist; 753 } 754 } 755 756 private RolapCubeMember lookupCubeMemberWithParent( 757 RolapMember member, 758 RolapCubeLevel cubeLevel) 759 { 760 final RolapMember parentMember = member.getParentMember(); 761 final RolapCubeMember parentCubeMember; 762 if (parentMember == null) { 763 parentCubeMember = null; 764 } else { 765 // In parent-child hierarchies, a member's parent may be in the 766 // same level. 767 final RolapCubeLevel parentLevel = 768 parentMember.getLevel() == member.getLevel() 769 ? cubeLevel 770 : cubeLevel.getParentLevel(); 771 parentCubeMember = 772 lookupCubeMemberWithParent( 773 parentMember, parentLevel); 774 } 775 return lookupCubeMember( 776 parentCubeMember, member, cubeLevel); 777 } 778 779 @Override 780 public RolapMember getMemberByKey( 781 RolapLevel level, List<Comparable> keyValues) 782 { 783 synchronized (cacheHelper) { 784 final RolapMember member = 785 super.getMemberByKey(level, keyValues); 786 return createAncestorMembers( 787 this, (RolapCubeLevel) level, member); 788 } 789 } 790 791 public RolapCubeMember lookupCubeMember( 792 RolapCubeMember parent, 793 RolapMember member, 794 RolapCubeLevel level) 795 { 796 synchronized (cacheHelper) { 797 if (member.getKey() == RolapUtil.sqlNullValue) { 798 if (member.isAll()) { 799 return getAllMember(); 800 } 801 } 802 803 RolapCubeMember cubeMember; 804 if (enableCache) { 805 Object key = 806 rolapCubeCacheHelper.makeKey(parent, member.getKey()); 807 cubeMember = (RolapCubeMember) 808 rolapCubeCacheHelper.getMember(key, false); 809 if (cubeMember == null) { 810 cubeMember = new RolapCubeMember(parent, member, level); 811 rolapCubeCacheHelper.putMember(key, cubeMember); 812 } 813 } else { 814 cubeMember = new RolapCubeMember(parent, member, level); 815 } 816 return cubeMember; 817 } 818 } 819 820 public int getMemberCount() { 821 return rolapHierarchy.getMemberReader().getMemberCount(); 822 } 823 824 protected void checkCacheStatus() { 825 synchronized (cacheHelper) { 826 // if necessary, flush all caches: 827 // - shared SmartMemberReader RolapMember cache 828 // - local key to cube member RolapCubeMember cache 829 // - cube source RolapCubeMember cache 830 // - local regular RolapMember cache, used when cube 831 // specific joins occur 832 833 if (cacheHelper.getChangeListener() != null) { 834 if (cacheHelper.getChangeListener().isHierarchyChanged( 835 getHierarchy())) 836 { 837 cacheHelper.flushCache(); 838 rolapCubeCacheHelper.flushCache(); 839 840 if (rolapHierarchy.getMemberReader() 841 instanceof SmartMemberReader) 842 { 843 SmartMemberReader smartMemberReader = 844 (SmartMemberReader) 845 rolapHierarchy.getMemberReader(); 846 if (smartMemberReader.getMemberCache() 847 instanceof MemberCacheHelper) 848 { 849 MemberCacheHelper helper = 850 (MemberCacheHelper) 851 smartMemberReader.getMemberCache(); 852 helper.flushCache(); 853 } 854 } 855 } 856 } 857 } 858 } 859 } 860 861 /** 862 * Same as {@link RolapCubeHierarchyMemberReader} but without caching 863 * anything. 864 */ 865 public class NoCacheRolapCubeHierarchyMemberReader 866 extends NoCacheMemberReader 867 implements RolapCubeHierarchyMemberReader 868 { 869 /** 870 * cubeSource is passed as our member builder 871 */ 872 protected final RolapCubeSqlMemberSource cubeSource; 873 874 /** 875 * this cache caches RolapCubeMembers that are light wrappers around 876 * shared and non-shared Hierarchy RolapMembers. The inherited 877 * cacheHelper object contains non-shared hierarchy RolapMembers. 878 * non-shared hierarchy RolapMembers are created when a member lookup 879 * involves the Cube's fact table. 880 */ 881 protected MemberCacheHelper rolapCubeCacheHelper; 882 883 public NoCacheRolapCubeHierarchyMemberReader() { 884 super(new SqlMemberSource(RolapCubeHierarchy.this)); 885 rolapCubeCacheHelper = 886 new MemberNoCacheHelper(); 887 888 cubeSource = 889 new RolapCubeSqlMemberSource( 890 this, 891 RolapCubeHierarchy.this, 892 rolapCubeCacheHelper, 893 new MemberNoCacheHelper()); 894 895 cubeSource.setCache(rolapCubeCacheHelper); 896 } 897 898 public MemberBuilder getMemberBuilder() { 899 return this.cubeSource; 900 } 901 902 public MemberCacheHelper getRolapCubeMemberCacheHelper() { 903 return rolapCubeCacheHelper; 904 } 905 906 public List<RolapMember> getRootMembers() { 907 return getMembersInLevel(cubeLevels[0]); 908 } 909 910 protected void readMemberChildren( 911 List<RolapMember> parentMembers, 912 List<RolapMember> children, 913 MemberChildrenConstraint constraint) 914 { 915 List<RolapMember> rolapChildren = new ArrayList<RolapMember>(); 916 List<RolapMember> rolapParents = new ArrayList<RolapMember>(); 917 Map<String, RolapCubeMember> lookup = 918 new HashMap<String, RolapCubeMember>(); 919 920 // extract RolapMembers from their RolapCubeMember objects 921 // populate lookup for reconnecting parents and children 922 final List<RolapCubeMember> parentRolapCubeMemberList = 923 Util.cast(parentMembers); 924 for (RolapCubeMember member : parentRolapCubeMemberList) { 925 final RolapMember rolapMember = member.getRolapMember(); 926 lookup.put(rolapMember.getUniqueName(), member); 927 rolapParents.add(rolapMember); 928 } 929 930 // get member children from shared member reader if possible, 931 // if not get them from our own source 932 boolean joinReq = 933 (constraint instanceof SqlContextConstraint); 934 if (joinReq) { 935 super.readMemberChildren( 936 parentMembers, rolapChildren, constraint); 937 } else { 938 rolapHierarchy.getMemberReader().getMemberChildren( 939 rolapParents, rolapChildren, constraint); 940 } 941 942 // now lookup or create RolapCubeMember 943 for (RolapMember currMember : rolapChildren) { 944 RolapCubeMember parent = 945 lookup.get( 946 currMember.getParentMember().getUniqueName()); 947 RolapCubeLevel level = 948 parent.getLevel().getChildLevel(); 949 if (level == null) { 950 // most likely a parent child hierarchy 951 level = parent.getLevel(); 952 } 953 RolapCubeMember newmember = 954 lookupCubeMember( 955 parent, currMember, level); 956 children.add(newmember); 957 } 958 959 // Put them in a temporary hash table first. Register them later, 960 // when we know their size (hence their 'cost' to the cache pool). 961 Map<RolapMember, List<RolapMember>> tempMap = 962 new HashMap<RolapMember, List<RolapMember>>(); 963 for (RolapMember member1 : parentMembers) { 964 tempMap.put(member1, Collections.<RolapMember>emptyList()); 965 } 966 967 // note that this stores RolapCubeMembers in our cache, 968 // which also stores RolapMembers. 969 970 for (RolapMember child : children) { 971 // todo: We could optimize here. If members.length is small, it's 972 // more efficient to drive from members, rather than hashing 973 // children.length times. We could also exploit the fact that the 974 // result is sorted by ordinal and therefore, unless the "members" 975 // contains members from different levels, children of the same 976 // member will be contiguous. 977 assert child != null : "child"; 978 final RolapMember parentMember = child.getParentMember(); 979 } 980 } 981 982 public Map<? extends Member, Access> getMemberChildren( 983 List<RolapMember> parentMembers, 984 List<RolapMember> children, 985 MemberChildrenConstraint constraint) 986 { 987 List<RolapMember> missed = new ArrayList<RolapMember>(); 988 for (RolapMember parentMember : parentMembers) { 989 // the null member has no children 990 if (!parentMember.isNull()) { 991 missed.add(parentMember); 992 } 993 } 994 if (missed.size() > 0) { 995 readMemberChildren(missed, children, constraint); 996 } 997 return Util.toNullValuesMap(children); 998 } 999 1000 1001 public List<RolapMember> getMembersInLevel( 1002 final RolapLevel level, 1003 TupleConstraint constraint) 1004 { 1005 List<RolapMember> members = null; 1006 1007 // if a join is required, we need to pass in the RolapCubeLevel 1008 // vs. the regular level 1009 boolean joinReq = 1010 (constraint instanceof SqlContextConstraint); 1011 final List<RolapMember> list; 1012 1013 if (!joinReq) { 1014 list = 1015 rolapHierarchy.getMemberReader().getMembersInLevel( 1016 ((RolapCubeLevel) level).getRolapLevel(), 1017 constraint); 1018 } else { 1019 list = 1020 super.getMembersInLevel( 1021 level, constraint); 1022 } 1023 1024 return new UnsupportedList<RolapMember>() { 1025 public RolapMember get(final int index) { 1026 return mutate(list.get(index)); 1027 } 1028 1029 public int size() { 1030 return list.size(); 1031 } 1032 1033 public Iterator<RolapMember> iterator() { 1034 final Iterator<RolapMember> it = list.iterator(); 1035 return new Iterator<RolapMember>() { 1036 public boolean hasNext() { 1037 return it.hasNext(); 1038 } 1039 public RolapMember next() { 1040 return mutate(it.next()); 1041 } 1042 1043 public void remove() { 1044 throw new UnsupportedOperationException(); 1045 } 1046 }; 1047 } 1048 1049 private RolapMember mutate(final RolapMember member) { 1050 RolapCubeMember parent = null; 1051 if (member.getParentMember() != null) { 1052 parent = 1053 createAncestorMembers( 1054 NoCacheRolapCubeHierarchyMemberReader.this, 1055 (RolapCubeLevel) level.getParentLevel(), 1056 member.getParentMember()); 1057 } 1058 return lookupCubeMember( 1059 parent, member, (RolapCubeLevel) level); 1060 } 1061 }; 1062 } 1063 1064 public RolapCubeMember lookupCubeMember( 1065 RolapCubeMember parent, 1066 RolapMember member, 1067 RolapCubeLevel level) 1068 { 1069 if (member.getKey() == RolapUtil.sqlNullValue) { 1070 if (member.isAll()) { 1071 return getAllMember(); 1072 } 1073 } 1074 1075 return new RolapCubeMember(parent, member, level); 1076 } 1077 1078 public int getMemberCount() { 1079 return rolapHierarchy.getMemberReader().getMemberCount(); 1080 } 1081 } 1082 1083 public static class RolapCubeSqlMemberSource extends SqlMemberSource { 1084 1085 private final RolapCubeHierarchyMemberReader memberReader; 1086 private final MemberCacheHelper memberSourceCacheHelper; 1087 private final Object memberCacheLock; 1088 1089 public RolapCubeSqlMemberSource( 1090 RolapCubeHierarchyMemberReader memberReader, 1091 RolapCubeHierarchy hierarchy, 1092 MemberCacheHelper memberSourceCacheHelper, 1093 Object memberCacheLock) 1094 { 1095 super(hierarchy); 1096 this.memberReader = memberReader; 1097 this.memberSourceCacheHelper = memberSourceCacheHelper; 1098 this.memberCacheLock = memberCacheLock; 1099 } 1100 1101 public RolapMember makeMember( 1102 RolapMember parentMember, 1103 RolapLevel childLevel, 1104 Object value, 1105 Object captionValue, 1106 boolean parentChild, 1107 SqlStatement stmt, 1108 Object key, 1109 int columnOffset) 1110 throws SQLException 1111 { 1112 final RolapCubeMember parentCubeMember = 1113 (RolapCubeMember) parentMember; 1114 final RolapCubeLevel childCubeLevel = (RolapCubeLevel) childLevel; 1115 final RolapMember parent; 1116 if (parentMember != null) { 1117 parent = parentCubeMember.getRolapMember(); 1118 } else { 1119 parent = null; 1120 } 1121 RolapMember member = 1122 super.makeMember( 1123 parent, 1124 childCubeLevel.getRolapLevel(), 1125 value, captionValue, parentChild, stmt, key, 1126 columnOffset); 1127 return 1128 memberReader.lookupCubeMember( 1129 parentCubeMember, 1130 member, childCubeLevel); 1131 } 1132 1133 public MemberCache getMemberCache() { 1134 // this is a special cache used solely for rolapcubemembers 1135 return memberSourceCacheHelper; 1136 } 1137 1138 /** 1139 * use the same lock in the RolapCubeMemberSource as the 1140 * RolapCubeHiearchyMemberReader to avoid deadlocks 1141 */ 1142 public Object getMemberCacheLock() { 1143 return memberCacheLock; 1144 } 1145 1146 public RolapMember allMember() { 1147 return getHierarchy().getAllMember(); 1148 } 1149 } 1150} 1151 1152// End RolapCubeHierarchy.java