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