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