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) 2004-2005 TONBELLER AG 009// Copyright (C) 2005-2012 Pentaho and others 010// All Rights Reserved. 011*/ 012package mondrian.rolap; 013 014import mondrian.olap.Access; 015import mondrian.olap.Id; 016import mondrian.olap.Member; 017import mondrian.olap.Util; 018import mondrian.rolap.TupleReader.MemberBuilder; 019import mondrian.rolap.sql.MemberChildrenConstraint; 020import mondrian.rolap.sql.TupleConstraint; 021import mondrian.util.ConcatenableList; 022 023import java.util.*; 024 025/** 026 * <code>SmartMemberReader</code> implements {@link MemberReader} by keeping a 027 * cache of members and their children. If a member is 'in cache', there is a 028 * list of its children. It also caches the members of levels. 029 * 030 * <p>Synchronization: the MemberReader <code>source</code> must be called 031 * from synchronized(this) context - it does not synchronize itself (probably 032 * it should).</p> 033 * 034 * <p>Constraints: Member.Children and Level.Members may be constrained by a 035 * SqlConstraint object. In this case a subset of all members is returned. 036 * These subsets are cached too and the SqlConstraint is part of the cache key. 037 * This is used in NON EMPTY context.</p> 038 * 039 * <p>Uniqueness. We need to ensure that there is never more than one {@link 040 * RolapMember} object representing the same member.</p> 041 * 042 * @author jhyde 043 * @since 21 December, 2001 044 */ 045public class SmartMemberReader implements MemberReader { 046 private final SqlConstraintFactory sqlConstraintFactory = 047 SqlConstraintFactory.instance(); 048 049 /** access to <code>source</code> must be synchronized(this) */ 050 protected final MemberReader source; 051 052 protected final MemberCacheHelper cacheHelper; 053 054 protected List<RolapMember> rootMembers; 055 056 SmartMemberReader(MemberReader source) { 057 this(source, true); 058 } 059 060 SmartMemberReader(MemberReader source, boolean cacheWriteback) { 061 this.source = source; 062 this.cacheHelper = new MemberCacheHelper(source.getHierarchy()); 063 if (cacheWriteback && !source.setCache(cacheHelper)) { 064 throw Util.newInternal( 065 "MemberSource (" 066 + source 067 + ", " 068 + source.getClass() 069 + ") does not support cache-writeback"); 070 } 071 } 072 // implement MemberReader 073 public RolapHierarchy getHierarchy() { 074 return source.getHierarchy(); 075 } 076 077 public MemberCache getMemberCache() { 078 return cacheHelper; 079 } 080 081 // implement MemberSource 082 public boolean setCache(MemberCache cache) { 083 // we do not support cache writeback -- we must be masters of our 084 // own cache 085 return false; 086 } 087 088 public RolapMember substitute(RolapMember member) { 089 return member; 090 } 091 092 public RolapMember desubstitute(RolapMember member) { 093 return member; 094 } 095 096 public RolapMember getMemberByKey( 097 RolapLevel level, List<Comparable> keyValues) 098 { 099 // Caching by key is not supported. 100 return source.getMemberByKey(level, keyValues); 101 } 102 103 // implement MemberReader 104 public List<RolapMember> getMembers() { 105 List<RolapMember> v = new ConcatenableList<RolapMember>(); 106 RolapLevel[] levels = (RolapLevel[]) getHierarchy().getLevels(); 107 // todo: optimize by walking to children for members we know about 108 for (RolapLevel level : levels) { 109 List<RolapMember> membersInLevel = getMembersInLevel(level); 110 v.addAll(membersInLevel); 111 } 112 return v; 113 } 114 115 public List<RolapMember> getRootMembers() { 116 if (rootMembers == null) { 117 rootMembers = source.getRootMembers(); 118 } 119 return rootMembers; 120 } 121 122 public List<RolapMember> getMembersInLevel( 123 RolapLevel level) 124 { 125 TupleConstraint constraint = 126 sqlConstraintFactory.getLevelMembersConstraint(null); 127 return getMembersInLevel(level, constraint); 128 } 129 130 protected void checkCacheStatus() { 131 cacheHelper.checkCacheStatus(); 132 } 133 134 public List<RolapMember> getMembersInLevel( 135 RolapLevel level, TupleConstraint constraint) 136 { 137 synchronized (cacheHelper) { 138 checkCacheStatus(); 139 140 List<RolapMember> members = 141 cacheHelper.getLevelMembersFromCache(level, constraint); 142 if (members != null) { 143 return members; 144 } 145 146 members = 147 source.getMembersInLevel( 148 level, constraint); 149 cacheHelper.putLevelMembersInCache(level, constraint, members); 150 return members; 151 } 152 } 153 154 public int getLevelMemberCount(RolapLevel level) { 155 // No need to cache the result: the caller saves the result by calling 156 // RolapLevel.setApproxRowCount 157 return source.getLevelMemberCount(level); 158 } 159 160 public void getMemberChildren( 161 RolapMember parentMember, 162 List<RolapMember> children) 163 { 164 MemberChildrenConstraint constraint = 165 sqlConstraintFactory.getMemberChildrenConstraint(null); 166 getMemberChildren(parentMember, children, constraint); 167 } 168 169 public Map<? extends Member, Access> getMemberChildren( 170 RolapMember parentMember, 171 List<RolapMember> children, 172 MemberChildrenConstraint constraint) 173 { 174 List<RolapMember> parentMembers = 175 Collections.singletonList(parentMember); 176 return getMemberChildren(parentMembers, children, constraint); 177 } 178 179 public void getMemberChildren( 180 List<RolapMember> parentMembers, 181 List<RolapMember> children) 182 { 183 MemberChildrenConstraint constraint = 184 sqlConstraintFactory.getMemberChildrenConstraint(null); 185 getMemberChildren(parentMembers, children, constraint); 186 } 187 188 public Map<? extends Member, Access> getMemberChildren( 189 List<RolapMember> parentMembers, 190 List<RolapMember> children, 191 MemberChildrenConstraint constraint) 192 { 193 synchronized (cacheHelper) { 194 checkCacheStatus(); 195 196 List<RolapMember> missed = new ArrayList<RolapMember>(); 197 for (RolapMember parentMember : parentMembers) { 198 List<RolapMember> list = 199 cacheHelper.getChildrenFromCache(parentMember, constraint); 200 if (list == null) { 201 // the null member has no children 202 if (!parentMember.isNull()) { 203 missed.add(parentMember); 204 } 205 } else { 206 children.addAll(list); 207 } 208 } 209 if (missed.size() > 0) { 210 readMemberChildren(missed, children, constraint); 211 } 212 } 213 return Util.toNullValuesMap(children); 214 } 215 216 public RolapMember lookupMember( 217 List<Id.Segment> uniqueNameParts, 218 boolean failIfNotFound) 219 { 220 return RolapUtil.lookupMember(this, uniqueNameParts, failIfNotFound); 221 } 222 223 /** 224 * Reads the children of <code>member</code> into cache, and also into 225 * <code>result</code>. 226 * 227 * @param result Children are written here, in order 228 * @param members Members whose children to read 229 * @param constraint restricts the returned members if possible (optional 230 * optimization) 231 */ 232 protected void readMemberChildren( 233 List<RolapMember> members, 234 List<RolapMember> result, 235 MemberChildrenConstraint constraint) 236 { 237 if (false) { 238 // Pre-condition disabled. It makes sense to have the pre- 239 // condition, because lists of parent members are typically 240 // sorted by construction, and we should be able to exploit this 241 // when constructing the (significantly larger) set of children. 242 // But currently BasicQueryTest.testBasketAnalysis() fails this 243 // assert, and I haven't had time to figure out why. 244 // -- jhyde, 2004/6/10. 245 Util.assertPrecondition(isSorted(members), "isSorted(members)"); 246 } 247 List<RolapMember> children = new ConcatenableList<RolapMember>(); 248 source.getMemberChildren(members, children, constraint); 249 // Put them in a temporary hash table first. Register them later, when 250 // we know their size (hence their 'cost' to the cache pool). 251 Map<RolapMember, List<RolapMember>> tempMap = 252 new HashMap<RolapMember, List<RolapMember>>(); 253 for (RolapMember member1 : members) { 254 tempMap.put(member1, Collections.EMPTY_LIST); 255 } 256 for (final RolapMember child : children) { 257 // todo: We could optimize here. If members.length is small, it's 258 // more efficient to drive from members, rather than hashing 259 // children.length times. We could also exploit the fact that the 260 // result is sorted by ordinal and therefore, unless the "members" 261 // contains members from different levels, children of the same 262 // member will be contiguous. 263 assert child != null : "child"; 264 assert tempMap != null : "tempMap"; 265 final RolapMember parentMember = child.getParentMember(); 266 List<RolapMember> list = tempMap.get(parentMember); 267 if (list == null) { 268 // The list is null if, due to dropped constraints, we now 269 // have a children list of a member we didn't explicitly 270 // ask for it. Adding it to the cache would be viable, but 271 // let's ignore it. 272 continue; 273 } else if (list == Collections.EMPTY_LIST) { 274 list = new ArrayList<RolapMember>(); 275 tempMap.put(parentMember, list); 276 } 277 ((List)list).add(child); 278 ((List)result).add(child); 279 } 280 synchronized (cacheHelper) { 281 for (Map.Entry<RolapMember, List<RolapMember>> entry 282 : tempMap.entrySet()) 283 { 284 final RolapMember member = entry.getKey(); 285 if (cacheHelper.getChildrenFromCache(member, constraint) 286 == null) 287 { 288 final List<RolapMember> list = entry.getValue(); 289 cacheHelper.putChildren(member, constraint, list); 290 } 291 } 292 } 293 } 294 295 /** 296 * Returns true if every element of <code>members</code> is not null and is 297 * strictly less than the following element; false otherwise. 298 */ 299 public boolean isSorted(List<RolapMember> members) { 300 final int count = members.size(); 301 if (count == 0) { 302 return true; 303 } 304 RolapMember m1 = members.get(0); 305 if (m1 == null) { 306 // Special case check for 0th element, just in case length == 1. 307 return false; 308 } 309 for (int i = 1; i < count; i++) { 310 RolapMember m0 = m1; 311 m1 = members.get(i); 312 if (m1 == null || compare(m0, m1, false) >= 0) { 313 return false; 314 } 315 } 316 return true; 317 } 318 319 public RolapMember getLeadMember(RolapMember member, int n) { 320 // uncertain if this method needs to be synchronized 321 synchronized (cacheHelper) { 322 if (n == 0 || member.isNull()) { 323 return member; 324 } else { 325 SiblingIterator iter = new SiblingIterator(this, member); 326 if (n > 0) { 327 RolapMember sibling = null; 328 while (n-- > 0) { 329 if (!iter.hasNext()) { 330 return (RolapMember) 331 member.getHierarchy().getNullMember(); 332 } 333 sibling = iter.nextMember(); 334 } 335 return sibling; 336 } else { 337 n = -n; 338 RolapMember sibling = null; 339 while (n-- > 0) { 340 if (!iter.hasPrevious()) { 341 return (RolapMember) 342 member.getHierarchy().getNullMember(); 343 } 344 sibling = iter.previousMember(); 345 } 346 return sibling; 347 } 348 } 349 } 350 } 351 352 public void getMemberRange( 353 RolapLevel level, 354 RolapMember startMember, 355 RolapMember endMember, 356 List<RolapMember> list) 357 { 358 assert startMember != null; 359 assert endMember != null; 360 assert startMember.getLevel() == endMember.getLevel(); 361 362 if (compare(startMember, endMember, false) > 0) { 363 return; 364 } 365 list.add(startMember); 366 if (startMember.equals(endMember)) { 367 return; 368 } 369 SiblingIterator siblings = new SiblingIterator(this, startMember); 370 while (siblings.hasNext()) { 371 final RolapMember member = siblings.nextMember(); 372 list.add(member); 373 if (member.equals(endMember)) { 374 return; 375 } 376 } 377 throw Util.newInternal( 378 "sibling iterator did not hit end point, start=" 379 + startMember + ", end=" + endMember); 380 } 381 382 public int getMemberCount() { 383 return source.getMemberCount(); 384 } 385 386 public int compare( 387 RolapMember m1, 388 RolapMember m2, 389 boolean siblingsAreEqual) 390 { 391 if (m1.equals(m2)) { 392 return 0; 393 } 394 if (Util.equals(m1.getParentMember(), m2.getParentMember())) { 395 // including case where both parents are null 396 if (siblingsAreEqual) { 397 return 0; 398 } else if (m1.getParentMember() == null) { 399 // at this point we know that both parent members are null. 400 int pos1 = -1, pos2 = -1; 401 List<RolapMember> siblingList = getRootMembers(); 402 for (int i = 0, n = siblingList.size(); i < n; i++) { 403 RolapMember child = siblingList.get(i); 404 if (child.equals(m1)) { 405 pos1 = i; 406 } 407 if (child.equals(m2)) { 408 pos2 = i; 409 } 410 } 411 if (pos1 == -1) { 412 throw Util.newInternal(m1 + " not found among siblings"); 413 } 414 if (pos2 == -1) { 415 throw Util.newInternal(m2 + " not found among siblings"); 416 } 417 Util.assertTrue(pos1 != pos2); 418 return pos1 < pos2 ? -1 : 1; 419 } else { 420 List<RolapMember> children = new ArrayList<RolapMember>(); 421 getMemberChildren(m1.getParentMember(), children); 422 int pos1 = -1, pos2 = -1; 423 for (int i = 0, n = children.size(); i < n; i++) { 424 RolapMember child = children.get(i); 425 if (child.equals(m1)) { 426 pos1 = i; 427 } 428 if (child.equals(m2)) { 429 pos2 = i; 430 } 431 } 432 if (pos1 == -1) { 433 throw Util.newInternal(m1 + " not found among siblings"); 434 } 435 if (pos2 == -1) { 436 throw Util.newInternal(m2 + " not found among siblings"); 437 } 438 Util.assertTrue(pos1 != pos2); 439 return pos1 < pos2 ? -1 : 1; 440 } 441 } 442 int levelDepth1 = m1.getLevel().getDepth(); 443 int levelDepth2 = m2.getLevel().getDepth(); 444 if (levelDepth1 < levelDepth2) { 445 final int c = compare(m1, m2.getParentMember(), false); 446 return (c == 0) ? -1 : c; 447 448 } else if (levelDepth1 > levelDepth2) { 449 final int c = compare(m1.getParentMember(), m2, false); 450 return (c == 0) ? 1 : c; 451 452 } else { 453 return compare(m1.getParentMember(), m2.getParentMember(), false); 454 } 455 } 456 457 /** 458 * <code>SiblingIterator</code> helps traverse a hierarchy of members, by 459 * remembering the position at each level. Each SiblingIterator has a 460 * parent, to which it defers when the last child of the current member is 461 * reached. 462 */ 463 class SiblingIterator { 464 private final MemberReader reader; 465 private final SiblingIterator parentIterator; 466 private List<RolapMember> siblings; 467 private int position; 468 469 SiblingIterator(MemberReader reader, RolapMember member) { 470 this.reader = reader; 471 RolapMember parent = member.getParentMember(); 472 List<RolapMember> siblingList; 473 if (parent == null) { 474 siblingList = reader.getRootMembers(); 475 this.parentIterator = null; 476 } else { 477 siblingList = new ArrayList<RolapMember>(); 478 reader.getMemberChildren(parent, siblingList); 479 this.parentIterator = new SiblingIterator(reader, parent); 480 } 481 this.siblings = siblingList; 482 this.position = -1; 483 for (int i = 0; i < this.siblings.size(); i++) { 484 if (siblings.get(i).equals(member)) { 485 this.position = i; 486 break; 487 } 488 } 489 if (this.position == -1) { 490 throw Util.newInternal( 491 "member " + member + " not found among its siblings"); 492 } 493 } 494 495 boolean hasNext() { 496 return (this.position < this.siblings.size() - 1) 497 || (parentIterator != null) 498 && parentIterator.hasNext(); 499 } 500 501 Object next() { 502 return nextMember(); 503 } 504 505 RolapMember nextMember() { 506 if (++this.position >= this.siblings.size()) { 507 if (parentIterator == null) { 508 throw Util.newInternal("there is no next member"); 509 } 510 RolapMember parent = parentIterator.nextMember(); 511 List<RolapMember> siblingList = new ArrayList<RolapMember>(); 512 reader.getMemberChildren(parent, siblingList); 513 this.siblings = siblingList; 514 this.position = 0; 515 } 516 return this.siblings.get(this.position); 517 } 518 519 boolean hasPrevious() { 520 return (this.position > 0) 521 || (parentIterator != null) 522 && parentIterator.hasPrevious(); 523 } 524 525 Object previous() { 526 return previousMember(); 527 } 528 529 RolapMember previousMember() { 530 if (--this.position < 0) { 531 if (parentIterator == null) { 532 throw Util.newInternal("there is no next member"); 533 } 534 RolapMember parent = parentIterator.previousMember(); 535 List<RolapMember> siblingList = new ArrayList<RolapMember>(); 536 reader.getMemberChildren(parent, siblingList); 537 this.siblings = siblingList; 538 this.position = this.siblings.size() - 1; 539 } 540 return this.siblings.get(this.position); 541 } 542 } 543 544 public MemberBuilder getMemberBuilder() { 545 return source.getMemberBuilder(); 546 } 547 548 public RolapMember getDefaultMember() { 549 RolapMember defaultMember = 550 (RolapMember) getHierarchy().getDefaultMember(); 551 if (defaultMember != null) { 552 return defaultMember; 553 } 554 return getRootMembers().get(0); 555 } 556 557 public RolapMember getMemberParent(RolapMember member) { 558 // This method deals with ragged hierarchies but not access-controlled 559 // hierarchies - assume these have RestrictedMemberReader possibly 560 // wrapped in a SubstitutingMemberReader. 561 RolapMember parentMember = member.getParentMember(); 562 // Skip over hidden parents. 563 while (parentMember != null && parentMember.isHidden()) { 564 parentMember = parentMember.getParentMember(); 565 } 566 return parentMember; 567 } 568} 569 570// End SmartMemberReader.java