001 /*
002 // $Id: //open/mondrian-release/3.2/src/main/mondrian/rolap/SmartMemberReader.java#1 $
003 // This software is subject to the terms of the Eclipse Public License v1.0
004 // Agreement, available at the following URL:
005 // http://www.eclipse.org/legal/epl-v10.html.
006 // Copyright (C) 2001-2002 Kana Software, Inc.
007 // Copyright (C) 2001-2009 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-release/3.2/src/main/mondrian/rolap/SmartMemberReader.java#1 $
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 {
122 TupleConstraint constraint =
123 sqlConstraintFactory.getLevelMembersConstraint(null);
124 return getMembersInLevel(level, startOrdinal, endOrdinal, constraint);
125 }
126
127 protected void checkCacheStatus() {
128 cacheHelper.checkCacheStatus();
129 }
130
131 public List<RolapMember> getMembersInLevel(
132 RolapLevel level,
133 int startOrdinal,
134 int endOrdinal,
135 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, startOrdinal, endOrdinal, 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 void getMemberChildren(
170 RolapMember parentMember,
171 List<RolapMember> children,
172 MemberChildrenConstraint constraint)
173 {
174 List<RolapMember> parentMembers =
175 Collections.singletonList(parentMember);
176 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 void 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 }
214
215 public RolapMember lookupMember(
216 List<Id.Segment> uniqueNameParts,
217 boolean failIfNotFound)
218 {
219 return RolapUtil.lookupMember(this, uniqueNameParts, failIfNotFound);
220 }
221
222 /**
223 * Reads the children of <code>member</code> into cache, and also into
224 * <code>result</code>.
225 *
226 * @param result Children are written here, in order
227 * @param members Members whose children to read
228 * @param constraint restricts the returned members if possible (optional
229 * optimization)
230 */
231 protected void readMemberChildren(
232 List<RolapMember> members,
233 List<RolapMember> result,
234 MemberChildrenConstraint constraint)
235 {
236 if (false) {
237 // Pre-condition disabled. It makes sense to have the pre-
238 // condition, because lists of parent members are typically
239 // sorted by construction, and we should be able to exploit this
240 // when constructing the (significantly larger) set of children.
241 // But currently BasicQueryTest.testBasketAnalysis() fails this
242 // assert, and I haven't had time to figure out why.
243 // -- jhyde, 2004/6/10.
244 Util.assertPrecondition(isSorted(members), "isSorted(members)");
245 }
246 List<RolapMember> children = new ConcatenableList<RolapMember>();
247 source.getMemberChildren(members, children, constraint);
248 // Put them in a temporary hash table first. Register them later, when
249 // we know their size (hence their 'cost' to the cache pool).
250 Map<RolapMember, List<RolapMember>> tempMap =
251 new HashMap<RolapMember, List<RolapMember>>();
252 for (RolapMember member1 : members) {
253 tempMap.put(member1, Collections.EMPTY_LIST);
254 }
255 for (final RolapMember child : children) {
256 // todo: We could optimize here. If members.length is small, it's
257 // more efficient to drive from members, rather than hashing
258 // children.length times. We could also exploit the fact that the
259 // result is sorted by ordinal and therefore, unless the "members"
260 // contains members from different levels, children of the same
261 // member will be contiguous.
262 assert child != null : "child";
263 assert tempMap != null : "tempMap";
264 final RolapMember parentMember = child.getParentMember();
265 List<RolapMember> list = tempMap.get(parentMember);
266 if (list == null) {
267 // The list is null if, due to dropped constraints, we now
268 // have a children list of a member we didn't explicitly
269 // ask for it. Adding it to the cache would be viable, but
270 // let's ignore it.
271 continue;
272 } else if (list == Collections.EMPTY_LIST) {
273 list = new ArrayList<RolapMember>();
274 tempMap.put(parentMember, list);
275 }
276 ((List)list).add(child);
277 ((List)result).add(child);
278 }
279 synchronized (cacheHelper) {
280 for (Map.Entry<RolapMember, List<RolapMember>> entry
281 : tempMap.entrySet())
282 {
283 final RolapMember member = entry.getKey();
284 if (cacheHelper.getChildrenFromCache(member, constraint)
285 == null)
286 {
287 final List<RolapMember> list = entry.getValue();
288 cacheHelper.putChildren(member, constraint, list);
289 }
290 }
291 }
292 }
293
294 /**
295 * Returns true if every element of <code>members</code> is not null and is
296 * strictly less than the following element; false otherwise.
297 */
298 public boolean isSorted(List<RolapMember> members) {
299 final int count = members.size();
300 if (count == 0) {
301 return true;
302 }
303 RolapMember m1 = members.get(0);
304 if (m1 == null) {
305 // Special case check for 0th element, just in case length == 1.
306 return false;
307 }
308 for (int i = 1; i < count; i++) {
309 RolapMember m0 = m1;
310 m1 = members.get(i);
311 if (m1 == null || compare(m0, m1, false) >= 0) {
312 return false;
313 }
314 }
315 return true;
316 }
317
318 public RolapMember getLeadMember(RolapMember member, int n) {
319 // uncertain if this method needs to be synchronized
320 synchronized (cacheHelper) {
321 if (n == 0 || member.isNull()) {
322 return member;
323 } else {
324 SiblingIterator iter = new SiblingIterator(this, member);
325 if (n > 0) {
326 RolapMember sibling = null;
327 while (n-- > 0) {
328 if (!iter.hasNext()) {
329 return (RolapMember)
330 member.getHierarchy().getNullMember();
331 }
332 sibling = iter.nextMember();
333 }
334 return sibling;
335 } else {
336 n = -n;
337 RolapMember sibling = null;
338 while (n-- > 0) {
339 if (!iter.hasPrevious()) {
340 return (RolapMember)
341 member.getHierarchy().getNullMember();
342 }
343 sibling = iter.previousMember();
344 }
345 return sibling;
346 }
347 }
348 }
349 }
350
351 public void getMemberRange(
352 RolapLevel level,
353 RolapMember startMember,
354 RolapMember endMember,
355 List<RolapMember> list)
356 {
357 assert startMember != null;
358 assert endMember != null;
359 assert startMember.getLevel() == endMember.getLevel();
360
361 if (compare(startMember, endMember, false) > 0) {
362 return;
363 }
364 list.add(startMember);
365 if (startMember.equals(endMember)) {
366 return;
367 }
368 SiblingIterator siblings = new SiblingIterator(this, startMember);
369 while (siblings.hasNext()) {
370 final RolapMember member = siblings.nextMember();
371 list.add(member);
372 if (member.equals(endMember)) {
373 return;
374 }
375 }
376 throw Util.newInternal(
377 "sibling iterator did not hit end point, start="
378 + startMember + ", end=" + 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
494 boolean hasNext() {
495 return (this.position < this.siblings.size() - 1)
496 || (parentIterator != null)
497 && parentIterator.hasNext();
498 }
499
500 Object next() {
501 return nextMember();
502 }
503
504 RolapMember nextMember() {
505 if (++this.position >= this.siblings.size()) {
506 if (parentIterator == null) {
507 throw Util.newInternal("there is no next member");
508 }
509 RolapMember parent = parentIterator.nextMember();
510 List<RolapMember> siblingList = new ArrayList<RolapMember>();
511 reader.getMemberChildren(parent, siblingList);
512 this.siblings = siblingList;
513 this.position = 0;
514 }
515 return this.siblings.get(this.position);
516 }
517
518 boolean hasPrevious() {
519 return (this.position > 0)
520 || (parentIterator != null)
521 && parentIterator.hasPrevious();
522 }
523
524 Object previous() {
525 return previousMember();
526 }
527
528 RolapMember previousMember() {
529 if (--this.position < 0) {
530 if (parentIterator == null) {
531 throw Util.newInternal("there is no next member");
532 }
533 RolapMember parent = parentIterator.previousMember();
534 List<RolapMember> siblingList = new ArrayList<RolapMember>();
535 reader.getMemberChildren(parent, siblingList);
536 this.siblings = siblingList;
537 this.position = this.siblings.size() - 1;
538 }
539 return this.siblings.get(this.position);
540 }
541 }
542
543 public MemberBuilder getMemberBuilder() {
544 return source.getMemberBuilder();
545 }
546
547 public RolapMember getDefaultMember() {
548 RolapMember defaultMember =
549 (RolapMember) getHierarchy().getDefaultMember();
550 if (defaultMember != null) {
551 return defaultMember;
552 }
553 return getRootMembers().get(0);
554 }
555
556 public RolapMember getMemberParent(RolapMember member) {
557 // This method deals with ragged hierarchies but not access-controlled
558 // hierarchies - assume these have RestrictedMemberReader possibly
559 // wrapped in a SubstitutingMemberReader.
560 RolapMember parentMember = member.getParentMember();
561 // Skip over hidden parents.
562 while (parentMember != null && parentMember.isHidden()) {
563 parentMember = parentMember.getParentMember();
564 }
565 return parentMember;
566 }
567 }
568
569 // End SmartMemberReader.java