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.Level;
015import mondrian.olap.Util;
016import mondrian.rolap.cache.SmartCache;
017import mondrian.rolap.cache.SoftSmartCache;
018import mondrian.rolap.sql.MemberChildrenConstraint;
019import mondrian.rolap.sql.TupleConstraint;
020import mondrian.spi.DataSourceChangeListener;
021import mondrian.util.Pair;
022
023import java.util.*;
024import java.util.Map.Entry;
025
026/**
027 * Encapsulation of member caching.
028 *
029 * @author Will Gorman
030 */
031public class MemberCacheHelper implements MemberCache {
032
033    private final SqlConstraintFactory sqlConstraintFactory =
034        SqlConstraintFactory.instance();
035
036    /** maps a parent member to a list of its children */
037    final SmartMemberListCache<RolapMember, List<RolapMember>>
038        mapMemberToChildren;
039
040    /** a cache for all members to ensure uniqueness */
041    SmartCache<Object, RolapMember> mapKeyToMember;
042    RolapHierarchy rolapHierarchy;
043    DataSourceChangeListener changeListener;
044
045    /** maps a level to its members */
046    final SmartMemberListCache<RolapLevel, List<RolapMember>>
047        mapLevelToMembers;
048
049    /**
050     * Creates a MemberCacheHelper.
051     *
052     * @param rolapHierarchy Hierarchy
053     */
054    public MemberCacheHelper(RolapHierarchy rolapHierarchy) {
055        this.rolapHierarchy = rolapHierarchy;
056        this.mapLevelToMembers =
057            new SmartMemberListCache<RolapLevel, List<RolapMember>>();
058        this.mapKeyToMember =
059            new SoftSmartCache<Object, RolapMember>();
060        this.mapMemberToChildren =
061            new SmartMemberListCache<RolapMember, List<RolapMember>>();
062
063        if (rolapHierarchy != null) {
064            changeListener =
065                rolapHierarchy.getRolapSchema().getDataSourceChangeListener();
066        } else {
067            changeListener = null;
068        }
069    }
070
071    public RolapMember getMember(
072        Object key,
073        boolean mustCheckCacheStatus)
074    {
075        if (mustCheckCacheStatus) {
076            checkCacheStatus();
077        }
078        return mapKeyToMember.get(key);
079    }
080
081
082    // implement MemberCache
083    public Object putMember(Object key, RolapMember value) {
084        return mapKeyToMember.put(key, value);
085    }
086
087    // implement MemberCache
088    public Object makeKey(RolapMember parent, Object key) {
089        return new MemberKey(parent, key);
090    }
091
092    // implement MemberCache
093    public RolapMember getMember(Object key) {
094        return getMember(key, true);
095    }
096
097    public synchronized void checkCacheStatus() {
098        if (changeListener != null) {
099            if (changeListener.isHierarchyChanged(rolapHierarchy)) {
100                flushCache();
101            }
102        }
103    }
104
105    /**
106     * Deprecated in favor of
107     * {@link #putChildren(RolapLevel, TupleConstraint, List)}
108     */
109    @Deprecated
110    public void putLevelMembersInCache(
111        RolapLevel level,
112        TupleConstraint constraint,
113        List<RolapMember> members)
114    {
115        putChildren(level, constraint, members);
116    }
117
118    public void putChildren(
119        RolapLevel level,
120        TupleConstraint constraint,
121        List<RolapMember> members)
122    {
123        mapLevelToMembers.put(level, constraint, members);
124    }
125
126    public List<RolapMember> getChildrenFromCache(
127        RolapMember member,
128        MemberChildrenConstraint constraint)
129    {
130        if (constraint == null) {
131            constraint =
132                sqlConstraintFactory.getMemberChildrenConstraint(null);
133        }
134        return mapMemberToChildren.get(member, constraint);
135    }
136
137    public void putChildren(
138        RolapMember member,
139        MemberChildrenConstraint constraint,
140        List<RolapMember> children)
141    {
142        if (constraint == null) {
143            constraint =
144                sqlConstraintFactory.getMemberChildrenConstraint(null);
145        }
146        mapMemberToChildren.put(member, constraint, children);
147    }
148
149    public List<RolapMember> getLevelMembersFromCache(
150        RolapLevel level,
151        TupleConstraint constraint)
152    {
153        if (constraint == null) {
154            constraint = sqlConstraintFactory.getLevelMembersConstraint(null);
155        }
156        return mapLevelToMembers.get(level, constraint);
157    }
158
159    // Must sync here because we want the three maps to be modified together.
160    public synchronized void flushCache() {
161        mapMemberToChildren.clear();
162        mapKeyToMember.clear();
163        mapLevelToMembers.clear();
164        // We also need to clear the approxRowCount of each level.
165        for (Level level : rolapHierarchy.getLevels()) {
166            ((RolapLevel)level).setApproxRowCount(Integer.MIN_VALUE);
167        }
168    }
169
170    public DataSourceChangeListener getChangeListener() {
171        return changeListener;
172    }
173
174    public void setChangeListener(DataSourceChangeListener listener) {
175        changeListener = listener;
176    }
177
178    public boolean isMutable()
179    {
180        return true;
181    }
182
183    public synchronized RolapMember removeMember(Object key)
184    {
185        // Flush entries from the level-to-members map
186        // for member's level and all child levels.
187        // Important: Do this even if the member is apparently not in the cache.
188        RolapLevel level = ((MemberKey) key).getLevel();
189        if (level == null) {
190            level = (RolapLevel) this.rolapHierarchy.getLevels()[0];
191        }
192        final RolapLevel levelRef = level;
193        mapLevelToMembers.getCache().execute(
194            new SmartCache.SmartCacheTask
195                <Pair<RolapLevel, Object>, List<RolapMember>>()
196            {
197                public void execute(
198                    Iterator<Entry<Pair
199                        <RolapLevel, Object>, List<RolapMember>>> iterator)
200                {
201                    while (iterator.hasNext()) {
202                        Map.Entry<Pair
203                            <RolapLevel, Object>, List<RolapMember>> entry =
204                            iterator.next();
205                        final RolapLevel cacheLevel = entry.getKey().left;
206                        if (cacheLevel.equals(levelRef)
207                            || (cacheLevel.getHierarchy()
208                            .equals(levelRef.getHierarchy())
209                            && cacheLevel.getDepth()
210                            >= levelRef.getDepth()))
211                        {
212                            iterator.remove();
213                        }
214                    }
215                }
216            });
217
218        final RolapMember member = getMember(key);
219        if (member == null) {
220            // not in cache
221            return null;
222        }
223
224        // Drop member from the member-to-children map, wherever it occurs as
225        // a parent or as a child, regardless of the constraint.
226        final RolapMember parent = member.getParentMember();
227        mapMemberToChildren.cache.execute(
228            new SmartCache.SmartCacheTask
229                <Pair<RolapMember, Object>, List<RolapMember>>()
230            {
231                public void execute(
232                    Iterator<Entry
233                        <Pair<RolapMember, Object>, List<RolapMember>>> iter)
234                {
235                    while (iter.hasNext()) {
236                        Map.Entry<Pair
237                            <RolapMember, Object>, List<RolapMember>> entry =
238                                iter.next();
239                        final RolapMember member1 = entry.getKey().left;
240                        final Object constraint = entry.getKey().right;
241
242                        // Cache key is (member's parent, constraint);
243                        // cache value is a list of member's siblings;
244                        // If constraint is trivial remove member from list
245                        // of siblings; otherwise it's safer to nuke the cache
246                        // entry
247                        if (Util.equals(member1, parent)) {
248                            if (constraint
249                                == DefaultMemberChildrenConstraint.instance())
250                            {
251                                List<RolapMember> siblings = entry.getValue();
252                                boolean removedIt = siblings.remove(member);
253                                Util.discard(removedIt);
254                            } else {
255                                iter.remove();
256                            }
257                        }
258
259                        // cache is (member, some constraint);
260                        // cache value is list of member's children;
261                        // remove cache entry
262                        if (Util.equals(member1, member)) {
263                            iter.remove();
264                        }
265                    }
266                }
267            });
268
269        // drop it from the lookup-cache
270        return mapKeyToMember.put(key, null);
271    }
272
273    public RolapMember removeMemberAndDescendants(Object key) {
274        // Can use mapMemberToChildren recursively. No need to update inferior
275        // lists of children. Do need to update inferior lists of level-peers.
276        return null; // STUB
277    }
278}
279
280// End MemberCacheHelper.java
281