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