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