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) 2005-2013 Pentaho and others
009// All Rights Reserved.
010*/
011package mondrian.rolap;
012
013import mondrian.olap.*;
014import mondrian.olap.fun.VisualTotalsFunDef;
015import mondrian.rolap.TupleReader.MemberBuilder;
016import mondrian.rolap.sql.MemberChildrenConstraint;
017import mondrian.rolap.sql.TupleConstraint;
018import mondrian.util.UnsupportedList;
019
020import java.sql.SQLException;
021import java.util.*;
022
023/**
024 * Hierarchy that is associated with a specific Cube.
025 *
026 * @author Will Gorman, 19 October 2007
027 */
028public class RolapCubeHierarchy extends RolapHierarchy {
029
030    private final boolean cachingEnabled =
031        MondrianProperties.instance().EnableRolapCubeMemberCache.get();
032    private final RolapCubeDimension cubeDimension;
033    private final RolapHierarchy rolapHierarchy;
034    private final RolapCubeLevel currentNullLevel;
035    private RolapCubeMember currentNullMember;
036    private RolapCubeMember currentAllMember;
037    private final MondrianDef.RelationOrJoin currentRelation;
038    private final RolapCubeHierarchyMemberReader reader;
039    private HierarchyUsage usage;
040    private final Map<String, String> aliases = new HashMap<String, String>();
041    private RolapCubeMember currentDefaultMember;
042    private final int ordinal;
043
044    /**
045     * True if the hierarchy is degenerate - has no dimension table of its own,
046     * just drives from the cube's fact table.
047     */
048    protected final boolean usingCubeFact;
049
050    /**
051     * Length of prefix to be removed when translating member unique names, or
052     * 0 if no translation is necessary.
053     */
054    private final int removePrefixLength;
055
056    // redundant copy of {@link #levels} with tigher type
057    private final RolapCubeLevel[] cubeLevels;
058
059    /**
060     * Creates a RolapCubeHierarchy.
061     *
062     * @param cubeDimension Dimension
063     * @param cubeDim XML dimension element
064     * @param rolapHierarchy Wrapped hierarchy
065     * @param subName Name of hierarchy within dimension
066     * @param ordinal Ordinal of hierarchy within cube
067     */
068    public RolapCubeHierarchy(
069        RolapCubeDimension cubeDimension,
070        MondrianDef.CubeDimension cubeDim,
071        RolapHierarchy rolapHierarchy,
072        String subName,
073        int ordinal)
074    {
075        super(
076            cubeDimension,
077            subName,
078            applyPrefix(cubeDim, rolapHierarchy.getCaption()),
079            rolapHierarchy.isVisible(),
080            applyPrefix(cubeDim, rolapHierarchy.getDescription()),
081            rolapHierarchy.hasAll(),
082            null,
083            rolapHierarchy.getAnnotationMap());
084        this.ordinal = ordinal;
085        if (!cubeDimension.getCube().isVirtual()) {
086            this.usage =
087                new HierarchyUsage(
088                    cubeDimension.getCube(), rolapHierarchy, cubeDim);
089        }
090
091        this.rolapHierarchy = rolapHierarchy;
092        this.cubeDimension = cubeDimension;
093        this.xmlHierarchy = rolapHierarchy.getXmlHierarchy();
094        // this relation should equal the name of the new dimension table
095        // The null member belongs to a level with very similar properties to
096        // the 'all' level.
097        this.currentNullLevel = new RolapCubeLevel(nullLevel, this);
098
099        usingCubeFact =
100            (cubeDimension.getCube().getFact() == null
101              || cubeDimension.getCube().getFact().equals(
102                  rolapHierarchy.getRelation()));
103
104        // re-alias names if necessary
105        if (!usingCubeFact) {
106            // join expressions are columns only
107            assert (usage.getJoinExp() instanceof MondrianDef.Column);
108            currentRelation =
109                this.cubeDimension.getCube().getStar().getUniqueRelation(
110                    rolapHierarchy.getRelation(),
111                    usage.getForeignKey(),
112                    ((MondrianDef.Column)usage.getJoinExp()).getColumnName(),
113                    usage.getJoinTable().getAlias());
114        } else {
115            currentRelation = rolapHierarchy.getRelation();
116        }
117        extractNewAliases(rolapHierarchy.getRelation(), currentRelation);
118        this.relation = currentRelation;
119        this.levels =
120            this.cubeLevels =
121                new RolapCubeLevel[rolapHierarchy.getLevels().length];
122        for (int i = 0; i < rolapHierarchy.getLevels().length; i++) {
123            this.cubeLevels[i] =
124                new RolapCubeLevel(
125                    (RolapLevel) rolapHierarchy.getLevels()[i], this);
126            if (i == 0) {
127                if (rolapHierarchy.getAllMember() != null) {
128                    RolapCubeLevel allLevel;
129                    if (hasAll()) {
130                        allLevel = this.cubeLevels[0];
131                    } else {
132                        // create an all level if one doesn't normally
133                        // exist in the hierarchy
134                        allLevel =
135                            new RolapCubeLevel(
136                                rolapHierarchy.getAllMember().getLevel(),
137                                this);
138                        allLevel.init(cubeDimension.xmlDimension);
139                    }
140
141                    this.currentAllMember =
142                        new RolapAllCubeMember(
143                            rolapHierarchy.getAllMember(),
144                            allLevel);
145                }
146            }
147        }
148
149        // Compute whether the unique names of members of this hierarchy are
150        // different from members of the underlying hierarchy. If so, compute
151        // the length of the prefix to be removed before this hierarchy's unique
152        // name is added. For example, if this.uniqueName is "[Ship Time]" and
153        // rolapHierarchy.uniqueName is "[Time]", remove prefixLength will be
154        // length("[Ship Time]") = 11.
155        if (uniqueName.equals(rolapHierarchy.getUniqueName())) {
156            this.removePrefixLength = 0;
157        } else {
158            this.removePrefixLength = rolapHierarchy.getUniqueName().length();
159        }
160
161        if (cubeDimension.isHighCardinality() || !cachingEnabled) {
162            this.reader = new NoCacheRolapCubeHierarchyMemberReader();
163        } else {
164            this.reader = new CacheRolapCubeHierarchyMemberReader();
165        }
166    }
167
168    /**
169     * Applies a prefix to a caption or description of a hierarchy in a shared
170     * dimension. Ensures that if a dimension is used more than once in the same
171     * cube then the hierarchies are distinguishable.
172     *
173     * <p>For example, if the [Time] dimension is imported as [Order Time] and
174     * [Ship Time], then the [Time].[Weekly] hierarchy would have caption
175     * "Order Time.Weekly caption" and description "Order Time.Weekly
176     * description".
177     *
178     * <p>If the dimension usage has a caption, it overrides.
179     *
180     * <p>If the dimension usage has a null name, or the name is the same
181     * as the dimension, and no caption, then no prefix is applied.
182     *
183     * @param cubeDim Cube dimension (maybe a usage of a shared dimension)
184     * @param caption Caption or description
185     * @return Caption or description, possibly prefixed by dimension role name
186     */
187    private static String applyPrefix(
188        MondrianDef.CubeDimension cubeDim,
189        String caption)
190    {
191        if (caption == null) {
192            return null;
193        }
194        if (cubeDim instanceof MondrianDef.DimensionUsage) {
195            final MondrianDef.DimensionUsage dimensionUsage =
196                (MondrianDef.DimensionUsage) cubeDim;
197            if (dimensionUsage.name != null
198                && !dimensionUsage.name.equals(dimensionUsage.source))
199            {
200                if (dimensionUsage.caption != null) {
201                    return dimensionUsage.caption + "." + caption;
202                } else {
203                    return dimensionUsage.name + "." + caption;
204                }
205            }
206        }
207        return caption;
208    }
209
210    @Override
211    public RolapCubeLevel[] getLevels() {
212        return cubeLevels;
213    }
214
215    public String getAllMemberName() {
216        return rolapHierarchy.getAllMemberName();
217    }
218
219    public String getSharedHierarchyName() {
220        return rolapHierarchy.getSharedHierarchyName();
221    }
222
223    public String getAllLevelName() {
224        return rolapHierarchy.getAllLevelName();
225    }
226
227    public boolean isUsingCubeFact() {
228        return usingCubeFact;
229    }
230
231    public String lookupAlias(String origTable) {
232        return aliases.get(origTable);
233    }
234
235    public final RolapHierarchy getRolapHierarchy() {
236        return rolapHierarchy;
237    }
238
239    public final int getOrdinalInCube() {
240        return ordinal;
241    }
242
243    /**
244     * Populates the alias map for the old and new relations.
245     *
246     * <p>This method may be simplified when we obsolete
247     * {@link mondrian.rolap.HierarchyUsage}.
248     *
249     * @param oldrel Original relation, as defined in the schema
250     * @param newrel New star relation, generated by RolapStar, canonical, and
251     * shared between all cubes with similar structure
252     */
253    protected void extractNewAliases(
254        MondrianDef.RelationOrJoin oldrel,
255        MondrianDef.RelationOrJoin newrel)
256    {
257        if (oldrel == null && newrel == null) {
258            return;
259        } else if (oldrel instanceof MondrianDef.Relation
260            && newrel instanceof MondrianDef.Relation)
261        {
262            aliases.put(
263                ((MondrianDef.Relation) oldrel).getAlias(),
264                ((MondrianDef.Relation) newrel).getAlias());
265        } else if (oldrel instanceof MondrianDef.Join
266            && newrel instanceof MondrianDef.Join)
267        {
268            MondrianDef.Join oldjoin = (MondrianDef.Join)oldrel;
269            MondrianDef.Join newjoin = (MondrianDef.Join)newrel;
270            extractNewAliases(oldjoin.left, newjoin.left);
271            extractNewAliases(oldjoin.right, newjoin.right);
272        } else {
273            throw new UnsupportedOperationException();
274        }
275    }
276
277    public boolean equals(Object o) {
278        if (this == o) {
279            return true;
280        }
281        if (!(o instanceof RolapCubeHierarchy)) {
282            return false;
283        }
284
285        RolapCubeHierarchy that = (RolapCubeHierarchy)o;
286        return cubeDimension.equals(that.cubeDimension)
287            && getUniqueName().equals(that.getUniqueName());
288    }
289
290    protected int computeHashCode() {
291        return Util.hash(super.computeHashCode(), this.cubeDimension.cube);
292    }
293
294    public Member createMember(
295        Member parent,
296        Level level,
297        String name,
298        Formula formula)
299    {
300        RolapLevel rolapLevel = ((RolapCubeLevel)level).getRolapLevel();
301        if (formula == null) {
302            RolapMember rolapParent = null;
303            if (parent != null) {
304                rolapParent = ((RolapCubeMember)parent).getRolapMember();
305            }
306            RolapMember member =
307                new RolapMemberBase(rolapParent, rolapLevel, name);
308            return new RolapCubeMember(
309                (RolapCubeMember) parent, member,
310                (RolapCubeLevel) level);
311        } else if (level.getDimension().isMeasures()) {
312            RolapCalculatedMeasure member =
313                new RolapCalculatedMeasure(
314                    (RolapMember) parent, rolapLevel, name, formula);
315            return new RolapCubeMember(
316                (RolapCubeMember) parent, member,
317                (RolapCubeLevel) level);
318        } else {
319            RolapCalculatedMember member =
320                new RolapCalculatedMember(
321                    (RolapMember) parent, rolapLevel, name, formula);
322            return new RolapCubeMember(
323                (RolapCubeMember) parent, member,
324                (RolapCubeLevel) level);
325        }
326    }
327
328
329    boolean tableExists(String tableName) {
330        return rolapHierarchy.tableExists(tableName);
331    }
332
333    /**
334     * The currentRelation object is derived from the shared relation object
335     * it is generated via the RolapStar object, and contains unique aliases
336     * for it's particular join path
337     *
338     * @return rolap cube hierarchy relation
339     */
340    public MondrianDef.RelationOrJoin getRelation() {
341        return currentRelation;
342    }
343
344    // override with stricter return type; make final, important for performance
345    public final RolapCubeMember getDefaultMember() {
346        if (currentDefaultMember == null) {
347            reader.getRootMembers();
348            currentDefaultMember =
349                bootstrapLookup(
350                    (RolapMember) rolapHierarchy.getDefaultMember());
351        }
352        return currentDefaultMember;
353    }
354
355    /**
356     * Looks up a {@link RolapCubeMember} corresponding to a {@link RolapMember}
357     * of the underlying hierarchy. Safe to be called while the hierarchy is
358     * initializing.
359     *
360     * @param rolapMember Member of underlying hierarchy
361     * @return Member of this hierarchy
362     */
363    private RolapCubeMember bootstrapLookup(RolapMember rolapMember) {
364        RolapCubeMember parent =
365            rolapMember.getParentMember() == null
366                ? null
367                : rolapMember.getParentMember().isAll()
368                    ? currentAllMember
369                    : bootstrapLookup(rolapMember.getParentMember());
370        RolapCubeLevel level = cubeLevels[rolapMember.getLevel().getDepth()];
371        return reader.lookupCubeMember(parent, rolapMember, level);
372    }
373
374    public Member getNullMember() {
375        // use lazy initialization to get around bootstrap issues
376        if (currentNullMember == null) {
377            currentNullMember =
378                new RolapCubeMember(
379                    null,
380                    (RolapMember) rolapHierarchy.getNullMember(),
381                    currentNullLevel);
382        }
383        return currentNullMember;
384    }
385
386    /**
387     * Returns the 'all' member.
388     */
389    public RolapCubeMember getAllMember() {
390        return currentAllMember;
391    }
392
393    void setMemberReader(MemberReader memberReader) {
394        rolapHierarchy.setMemberReader(memberReader);
395    }
396
397    MemberReader getMemberReader() {
398        return reader;
399    }
400
401    public void setDefaultMember(Member defaultMeasure) {
402        // refactor this!
403        rolapHierarchy.setDefaultMember(defaultMeasure);
404
405        RolapCubeLevel level =
406            new RolapCubeLevel(
407                (RolapLevel)rolapHierarchy.getDefaultMember().getLevel(),
408                this);
409        currentDefaultMember =
410            new RolapCubeMember(
411                null,
412                (RolapMember) rolapHierarchy.getDefaultMember(),
413                level);
414    }
415
416    void init(MondrianDef.CubeDimension xmlDimension) {
417        // first init shared hierarchy
418        rolapHierarchy.init(xmlDimension);
419        // second init cube hierarchy
420        super.init(xmlDimension);
421    }
422
423    /**
424     * Converts the unique name of a member of the underlying hierarchy to
425     * the appropriate name for this hierarchy.
426     *
427     * <p>For example, if the shared hierarchy is [Time].[Quarterly] and the
428     * hierarchy usage is [Ship Time].[Quarterly], then [Time].[1997].[Q1] would
429     * be translated to [Ship Time].[Quarerly].[1997].[Q1].
430     *
431     * @param memberUniqueName Unique name of member from underlying hierarchy
432     * @return Translated unique name
433     */
434    final String convertMemberName(String memberUniqueName) {
435        if (removePrefixLength > 0
436            && !memberUniqueName.startsWith(uniqueName))
437        {
438            return uniqueName + memberUniqueName.substring(removePrefixLength);
439        }
440        return memberUniqueName;
441    }
442
443    public final RolapCube getCube() {
444        return cubeDimension.cube;
445    }
446
447    private static RolapCubeMember createAncestorMembers(
448        RolapCubeHierarchyMemberReader memberReader,
449        RolapCubeLevel level,
450        RolapMember member)
451    {
452        if (member == null) {
453            return null;
454        }
455        RolapCubeMember parent = null;
456        if (member.getParentMember() != null) {
457            parent =
458                createAncestorMembers(
459                    memberReader,
460                    level.getParentLevel(),
461                    member.getParentMember());
462        }
463        return memberReader.lookupCubeMember(parent, member, level);
464    }
465
466    /**
467     * TODO: Since this is part of a caching strategy, should be implemented
468     * as a Strategy Pattern, avoiding hierarchy.
469     */
470    public static interface RolapCubeHierarchyMemberReader
471        extends MemberReader
472    {
473        public RolapCubeMember lookupCubeMember(
474            final RolapCubeMember parent,
475            final RolapMember member,
476            final RolapCubeLevel level);
477
478        public MemberCacheHelper getRolapCubeMemberCacheHelper();
479    }
480
481    /******
482
483     RolapCubeMember Caching Approach:
484
485     - RolapHierarchy.SmartMemberReader.SmartCacheHelper ->
486       This is the shared cache across shared hierarchies.  This
487       member cache only
488       contains members loaded by non-cube specific member lookups.  This cache
489       should only contain RolapMembers, not RolapCubeMembers
490
491     - RolapCubeHierarchy.RolapCubeHierarchyMemberReader.rolapCubeCacheHelper ->
492       This cache contains the RolapCubeMember objects, which are cube specific
493       wrappers of shared members.
494
495     - RolapCubeHierarchy.RolapCubeHierarchyMemberReader.SmartCacheHelper ->
496       This is the inherited shared cache from SmartMemberReader, and
497       is used when a join with the fact table is necessary, aka a
498       SqlContextConstraint is used. This cache may be redundant with
499       rolapCubeCacheHelper.
500
501     - A Special note regarding RolapCubeHierarchyMemberReader.cubeSource -
502       This class was required for the special situation getMemberBuilder()
503       method call from RolapNativeSet.  This class utilizes both the
504       rolapCubeCacheHelper class for storing RolapCubeMembers, and also the
505       RolapCubeHierarchyMemberReader's inherited SmartCacheHelper.
506
507
508     ******/
509
510
511    /**
512     *  member reader wrapper - uses existing member reader,
513     *  but wraps and caches all intermediate members.
514     *
515     *  <p>Synchronization. Most synchronization takes place within
516     * SmartMemberReader.  All synchronization is done on the cacheHelper
517     * object.
518      */
519    public class CacheRolapCubeHierarchyMemberReader
520        extends SmartMemberReader
521        implements RolapCubeHierarchyMemberReader
522    {
523        /**
524         * cubeSource is passed as our member builder
525         */
526        protected final RolapCubeSqlMemberSource cubeSource;
527
528        /**
529         * this cache caches RolapCubeMembers that are light wrappers around
530         * shared and non-shared Hierarchy RolapMembers.  The inherited
531         * cacheHelper object contains non-shared hierarchy RolapMembers.
532         * non-shared hierarchy RolapMembers are created when a member lookup
533         * involves the Cube's fact table.
534         */
535        protected MemberCacheHelper rolapCubeCacheHelper;
536        private final boolean enableCache =
537            MondrianProperties.instance().EnableRolapCubeMemberCache.get();
538
539        public CacheRolapCubeHierarchyMemberReader() {
540            super(new SqlMemberSource(RolapCubeHierarchy.this));
541            rolapCubeCacheHelper =
542                new MemberCacheHelper(RolapCubeHierarchy.this);
543
544            cubeSource =
545                new RolapCubeSqlMemberSource(
546                    this,
547                    RolapCubeHierarchy.this,
548                    rolapCubeCacheHelper,
549                    cacheHelper);
550
551            cubeSource.setCache(getMemberCache());
552        }
553
554        public MemberBuilder getMemberBuilder() {
555            return this.cubeSource;
556        }
557
558        public MemberCacheHelper getRolapCubeMemberCacheHelper() {
559            return rolapCubeCacheHelper;
560        }
561
562        public List<RolapMember> getRootMembers() {
563            if (rootMembers == null) {
564                rootMembers = getMembersInLevel(cubeLevels[0]);
565            }
566            return rootMembers;
567        }
568
569        protected void readMemberChildren(
570            List<RolapMember> parentMembers,
571            List<RolapMember> children,
572            MemberChildrenConstraint constraint)
573        {
574            List<RolapMember> rolapChildren = new ArrayList<RolapMember>();
575            List<RolapMember> rolapParents = new ArrayList<RolapMember>();
576            Map<String, RolapCubeMember> lookup =
577                new HashMap<String, RolapCubeMember>();
578
579            // extract RolapMembers from their RolapCubeMember objects
580            // populate lookup for reconnecting parents and children
581            for (RolapMember member : parentMembers) {
582                if (member instanceof VisualTotalsFunDef.VisualTotalMember) {
583                    continue;
584                }
585                final RolapCubeMember cubeMember = (RolapCubeMember) member;
586                final RolapMember rolapMember = cubeMember.getRolapMember();
587                lookup.put(rolapMember.getUniqueName(), cubeMember);
588                rolapParents.add(rolapMember);
589            }
590
591            // get member children from shared member reader if possible,
592            // if not get them from our own source
593            boolean joinReq =
594                (constraint instanceof SqlContextConstraint);
595            if (joinReq) {
596                super.readMemberChildren(
597                    parentMembers, rolapChildren, constraint);
598            } else {
599                rolapHierarchy.getMemberReader().getMemberChildren(
600                    rolapParents, rolapChildren, constraint);
601            }
602
603            // now lookup or create RolapCubeMember
604            for (RolapMember currMember : rolapChildren) {
605                RolapCubeMember parent =
606                    lookup.get(
607                        currMember.getParentMember().getUniqueName());
608                RolapCubeLevel level =
609                    parent.getLevel().getChildLevel();
610                if (level == null) {
611                    // most likely a parent child hierarchy
612                    level = parent.getLevel();
613                }
614                RolapCubeMember newmember =
615                    lookupCubeMember(
616                        parent, currMember, level);
617                children.add(newmember);
618            }
619
620            // Put them in a temporary hash table first. Register them later,
621            // when we know their size (hence their 'cost' to the cache pool).
622            Map<RolapMember, List<RolapMember>> tempMap =
623                new HashMap<RolapMember, List<RolapMember>>();
624            for (RolapMember member1 : parentMembers) {
625                tempMap.put(member1, Collections.<RolapMember>emptyList());
626            }
627
628            // note that this stores RolapCubeMembers in our cache,
629            // which also stores RolapMembers.
630
631            for (RolapMember child : children) {
632            // todo: We could optimize here. If members.length is small, it's
633            // more efficient to drive from members, rather than hashing
634            // children.length times. We could also exploit the fact that the
635            // result is sorted by ordinal and therefore, unless the "members"
636            // contains members from different levels, children of the same
637            // member will be contiguous.
638                assert child != null : "child";
639                final RolapMember parentMember = child.getParentMember();
640                List<RolapMember> cacheList = tempMap.get(parentMember);
641                if (cacheList == null) {
642                    // The list is null if, due to dropped constraints, we now
643                    // have a children list of a member we didn't explicitly
644                    // ask for it. Adding it to the cache would be viable, but
645                    // let's ignore it.
646                    continue;
647                } else if (cacheList == Collections.EMPTY_LIST) {
648                    cacheList = new ArrayList<RolapMember>();
649                    tempMap.put(parentMember, cacheList);
650                }
651                cacheList.add(child);
652            }
653
654            synchronized (cacheHelper) {
655                for (Map.Entry<RolapMember, List<RolapMember>> entry
656                    : tempMap.entrySet())
657                {
658                    final RolapMember member = entry.getKey();
659                    if (rolapCubeCacheHelper.getChildrenFromCache(
660                            member, constraint) == null)
661                    {
662                        final List<RolapMember> cacheList = entry.getValue();
663                        if (enableCache) {
664                            rolapCubeCacheHelper.putChildren(
665                                member, constraint, cacheList);
666                        }
667                    }
668                }
669            }
670        }
671
672        public Map<? extends Member, Access> getMemberChildren(
673            List<RolapMember> parentMembers,
674            List<RolapMember> children,
675            MemberChildrenConstraint constraint)
676        {
677            synchronized (cacheHelper) {
678                checkCacheStatus();
679
680                List<RolapMember> missed = new ArrayList<RolapMember>();
681                for (RolapMember parentMember : parentMembers) {
682                    List<RolapMember> list =
683                        rolapCubeCacheHelper.getChildrenFromCache(
684                            parentMember, constraint);
685                    if (list == null) {
686                        // the null member has no children
687                        if (!parentMember.isNull()) {
688                            missed.add(parentMember);
689                        }
690                    } else {
691                        children.addAll(list);
692                    }
693                }
694                if (missed.size() > 0) {
695                    readMemberChildren(missed, children, constraint);
696                }
697            }
698            return Util.toNullValuesMap(children);
699        }
700
701
702        public List<RolapMember> getMembersInLevel(
703            RolapLevel level,
704            TupleConstraint constraint)
705        {
706            synchronized (cacheHelper) {
707                checkCacheStatus();
708
709                List<RolapMember> members =
710                    rolapCubeCacheHelper.getLevelMembersFromCache(
711                        level, constraint);
712                if (members != null) {
713                    return members;
714                }
715
716                // if a join is required, we need to pass in the RolapCubeLevel
717                // vs. the regular level
718                boolean joinReq =
719                    (constraint instanceof SqlContextConstraint);
720                List<RolapMember> list;
721                final RolapCubeLevel cubeLevel = (RolapCubeLevel) level;
722                if (!joinReq) {
723                    list =
724                        rolapHierarchy.getMemberReader().getMembersInLevel(
725                            cubeLevel.getRolapLevel(), constraint);
726                } else {
727                    list =
728                        super.getMembersInLevel(
729                            level, constraint);
730                }
731                List<RolapMember> newlist = new ArrayList<RolapMember>();
732                for (RolapMember member : list) {
733                    // note that there is a special case for the all member
734
735                    // REVIEW: disabled, to see what happens. if this code is
736                    // for performance, we should check level.isAll at the top
737                    // of the method; if it is for correctness, leave the code
738                    // in
739                    if (false && member == rolapHierarchy.getAllMember()) {
740                        newlist.add(getAllMember());
741                    } else {
742                        RolapCubeMember cubeMember =
743                            lookupCubeMemberWithParent(
744                                member,
745                                cubeLevel);
746                        newlist.add(cubeMember);
747                    }
748                }
749                rolapCubeCacheHelper.putLevelMembersInCache(
750                    level, constraint, newlist);
751
752                return newlist;
753            }
754        }
755
756        private RolapCubeMember lookupCubeMemberWithParent(
757            RolapMember member,
758            RolapCubeLevel cubeLevel)
759        {
760            final RolapMember parentMember = member.getParentMember();
761            final RolapCubeMember parentCubeMember;
762            if (parentMember == null) {
763                parentCubeMember = null;
764            } else {
765                // In parent-child hierarchies, a member's parent may be in the
766                // same level.
767                final RolapCubeLevel parentLevel =
768                    parentMember.getLevel() == member.getLevel()
769                        ? cubeLevel
770                        : cubeLevel.getParentLevel();
771                parentCubeMember =
772                    lookupCubeMemberWithParent(
773                        parentMember, parentLevel);
774            }
775            return lookupCubeMember(
776                parentCubeMember, member, cubeLevel);
777        }
778
779        @Override
780        public RolapMember getMemberByKey(
781            RolapLevel level, List<Comparable> keyValues)
782        {
783            synchronized (cacheHelper) {
784                final RolapMember member =
785                    super.getMemberByKey(level, keyValues);
786                return createAncestorMembers(
787                    this, (RolapCubeLevel) level, member);
788            }
789        }
790
791        public RolapCubeMember lookupCubeMember(
792            RolapCubeMember parent,
793            RolapMember member,
794            RolapCubeLevel level)
795        {
796            synchronized (cacheHelper) {
797                if (member.getKey() == RolapUtil.sqlNullValue) {
798                    if (member.isAll()) {
799                        return getAllMember();
800                    }
801                }
802
803                RolapCubeMember cubeMember;
804                if (enableCache) {
805                    Object key =
806                        rolapCubeCacheHelper.makeKey(parent, member.getKey());
807                    cubeMember = (RolapCubeMember)
808                        rolapCubeCacheHelper.getMember(key, false);
809                    if (cubeMember == null) {
810                        cubeMember = new RolapCubeMember(parent, member, level);
811                        rolapCubeCacheHelper.putMember(key, cubeMember);
812                    }
813                } else {
814                    cubeMember = new RolapCubeMember(parent, member, level);
815                }
816                return cubeMember;
817            }
818        }
819
820        public int getMemberCount() {
821            return rolapHierarchy.getMemberReader().getMemberCount();
822        }
823
824        protected void checkCacheStatus() {
825            synchronized (cacheHelper) {
826                // if necessary, flush all caches:
827                //   - shared SmartMemberReader RolapMember cache
828                //   - local key to cube member RolapCubeMember cache
829                //   - cube source RolapCubeMember cache
830                //   - local regular RolapMember cache, used when cube
831                //     specific joins occur
832
833                if (cacheHelper.getChangeListener() != null) {
834                    if (cacheHelper.getChangeListener().isHierarchyChanged(
835                            getHierarchy()))
836                    {
837                        cacheHelper.flushCache();
838                        rolapCubeCacheHelper.flushCache();
839
840                        if (rolapHierarchy.getMemberReader()
841                                instanceof SmartMemberReader)
842                        {
843                            SmartMemberReader smartMemberReader =
844                                (SmartMemberReader)
845                                    rolapHierarchy.getMemberReader();
846                            if (smartMemberReader.getMemberCache()
847                                    instanceof MemberCacheHelper)
848                            {
849                                MemberCacheHelper helper =
850                                    (MemberCacheHelper)
851                                        smartMemberReader.getMemberCache();
852                                helper.flushCache();
853                            }
854                        }
855                    }
856                }
857            }
858        }
859    }
860
861    /**
862     * Same as {@link RolapCubeHierarchyMemberReader} but without caching
863     * anything.
864     */
865    public class NoCacheRolapCubeHierarchyMemberReader
866        extends NoCacheMemberReader
867        implements RolapCubeHierarchyMemberReader
868    {
869        /**
870         * cubeSource is passed as our member builder
871         */
872        protected final RolapCubeSqlMemberSource cubeSource;
873
874        /**
875         * this cache caches RolapCubeMembers that are light wrappers around
876         * shared and non-shared Hierarchy RolapMembers.  The inherited
877         * cacheHelper object contains non-shared hierarchy RolapMembers.
878         * non-shared hierarchy RolapMembers are created when a member lookup
879         * involves the Cube's fact table.
880         */
881        protected MemberCacheHelper rolapCubeCacheHelper;
882
883        public NoCacheRolapCubeHierarchyMemberReader() {
884            super(new SqlMemberSource(RolapCubeHierarchy.this));
885            rolapCubeCacheHelper =
886                new MemberNoCacheHelper();
887
888            cubeSource =
889                new RolapCubeSqlMemberSource(
890                    this,
891                    RolapCubeHierarchy.this,
892                    rolapCubeCacheHelper,
893                    new MemberNoCacheHelper());
894
895            cubeSource.setCache(rolapCubeCacheHelper);
896        }
897
898        public MemberBuilder getMemberBuilder() {
899            return this.cubeSource;
900        }
901
902        public MemberCacheHelper getRolapCubeMemberCacheHelper() {
903            return rolapCubeCacheHelper;
904        }
905
906        public List<RolapMember> getRootMembers() {
907            return getMembersInLevel(cubeLevels[0]);
908        }
909
910        protected void readMemberChildren(
911            List<RolapMember> parentMembers,
912            List<RolapMember> children,
913            MemberChildrenConstraint constraint)
914        {
915            List<RolapMember> rolapChildren = new ArrayList<RolapMember>();
916            List<RolapMember> rolapParents = new ArrayList<RolapMember>();
917            Map<String, RolapCubeMember> lookup =
918                new HashMap<String, RolapCubeMember>();
919
920            // extract RolapMembers from their RolapCubeMember objects
921            // populate lookup for reconnecting parents and children
922            final List<RolapCubeMember> parentRolapCubeMemberList =
923                Util.cast(parentMembers);
924            for (RolapCubeMember member : parentRolapCubeMemberList) {
925                final RolapMember rolapMember = member.getRolapMember();
926                lookup.put(rolapMember.getUniqueName(), member);
927                rolapParents.add(rolapMember);
928            }
929
930            // get member children from shared member reader if possible,
931            // if not get them from our own source
932            boolean joinReq =
933                (constraint instanceof SqlContextConstraint);
934            if (joinReq) {
935                super.readMemberChildren(
936                    parentMembers, rolapChildren, constraint);
937            } else {
938                rolapHierarchy.getMemberReader().getMemberChildren(
939                    rolapParents, rolapChildren, constraint);
940            }
941
942            // now lookup or create RolapCubeMember
943            for (RolapMember currMember : rolapChildren) {
944                RolapCubeMember parent =
945                    lookup.get(
946                        currMember.getParentMember().getUniqueName());
947                RolapCubeLevel level =
948                    parent.getLevel().getChildLevel();
949                if (level == null) {
950                    // most likely a parent child hierarchy
951                    level = parent.getLevel();
952                }
953                RolapCubeMember newmember =
954                    lookupCubeMember(
955                        parent, currMember, level);
956                children.add(newmember);
957            }
958
959            // Put them in a temporary hash table first. Register them later,
960            // when we know their size (hence their 'cost' to the cache pool).
961            Map<RolapMember, List<RolapMember>> tempMap =
962                new HashMap<RolapMember, List<RolapMember>>();
963            for (RolapMember member1 : parentMembers) {
964                tempMap.put(member1, Collections.<RolapMember>emptyList());
965            }
966
967            // note that this stores RolapCubeMembers in our cache,
968            // which also stores RolapMembers.
969
970            for (RolapMember child : children) {
971            // todo: We could optimize here. If members.length is small, it's
972            // more efficient to drive from members, rather than hashing
973            // children.length times. We could also exploit the fact that the
974            // result is sorted by ordinal and therefore, unless the "members"
975            // contains members from different levels, children of the same
976            // member will be contiguous.
977                assert child != null : "child";
978                final RolapMember parentMember = child.getParentMember();
979            }
980        }
981
982        public Map<? extends Member, Access> getMemberChildren(
983            List<RolapMember> parentMembers,
984            List<RolapMember> children,
985            MemberChildrenConstraint constraint)
986        {
987            List<RolapMember> missed = new ArrayList<RolapMember>();
988            for (RolapMember parentMember : parentMembers) {
989                // the null member has no children
990                if (!parentMember.isNull()) {
991                    missed.add(parentMember);
992                }
993            }
994            if (missed.size() > 0) {
995                readMemberChildren(missed, children, constraint);
996            }
997            return Util.toNullValuesMap(children);
998        }
999
1000
1001        public List<RolapMember> getMembersInLevel(
1002            final RolapLevel level,
1003            TupleConstraint constraint)
1004        {
1005                List<RolapMember> members = null;
1006
1007                // if a join is required, we need to pass in the RolapCubeLevel
1008                // vs. the regular level
1009                boolean joinReq =
1010                    (constraint instanceof SqlContextConstraint);
1011                final List<RolapMember> list;
1012
1013                if (!joinReq) {
1014                    list =
1015                        rolapHierarchy.getMemberReader().getMembersInLevel(
1016                            ((RolapCubeLevel) level).getRolapLevel(),
1017                            constraint);
1018                } else {
1019                    list =
1020                        super.getMembersInLevel(
1021                            level, constraint);
1022                }
1023
1024                return new UnsupportedList<RolapMember>() {
1025                    public RolapMember get(final int index) {
1026                        return mutate(list.get(index));
1027                    }
1028
1029                    public int size() {
1030                        return list.size();
1031                    }
1032
1033                    public Iterator<RolapMember> iterator() {
1034                        final Iterator<RolapMember> it = list.iterator();
1035                        return new Iterator<RolapMember>() {
1036                            public boolean hasNext() {
1037                                return it.hasNext();
1038                            }
1039                            public RolapMember next() {
1040                                return mutate(it.next());
1041                            }
1042
1043                            public void remove() {
1044                                throw new UnsupportedOperationException();
1045                            }
1046                        };
1047                    }
1048
1049                    private RolapMember mutate(final RolapMember member) {
1050                        RolapCubeMember parent = null;
1051                        if (member.getParentMember() != null) {
1052                            parent =
1053                                createAncestorMembers(
1054                                    NoCacheRolapCubeHierarchyMemberReader.this,
1055                                    (RolapCubeLevel) level.getParentLevel(),
1056                                    member.getParentMember());
1057                        }
1058                        return lookupCubeMember(
1059                            parent, member, (RolapCubeLevel) level);
1060                    }
1061                };
1062        }
1063
1064        public RolapCubeMember lookupCubeMember(
1065            RolapCubeMember parent,
1066            RolapMember member,
1067            RolapCubeLevel level)
1068        {
1069            if (member.getKey() == RolapUtil.sqlNullValue) {
1070                if (member.isAll()) {
1071                    return getAllMember();
1072                }
1073            }
1074
1075            return new RolapCubeMember(parent, member, level);
1076        }
1077
1078        public int getMemberCount() {
1079            return rolapHierarchy.getMemberReader().getMemberCount();
1080        }
1081    }
1082
1083    public static class RolapCubeSqlMemberSource extends SqlMemberSource {
1084
1085        private final RolapCubeHierarchyMemberReader memberReader;
1086        private final MemberCacheHelper memberSourceCacheHelper;
1087        private final Object memberCacheLock;
1088
1089        public RolapCubeSqlMemberSource(
1090            RolapCubeHierarchyMemberReader memberReader,
1091            RolapCubeHierarchy hierarchy,
1092            MemberCacheHelper memberSourceCacheHelper,
1093            Object memberCacheLock)
1094        {
1095            super(hierarchy);
1096            this.memberReader = memberReader;
1097            this.memberSourceCacheHelper = memberSourceCacheHelper;
1098            this.memberCacheLock = memberCacheLock;
1099        }
1100
1101        public RolapMember makeMember(
1102            RolapMember parentMember,
1103            RolapLevel childLevel,
1104            Object value,
1105            Object captionValue,
1106            boolean parentChild,
1107            SqlStatement stmt,
1108            Object key,
1109            int columnOffset)
1110            throws SQLException
1111        {
1112            final RolapCubeMember parentCubeMember =
1113                (RolapCubeMember) parentMember;
1114            final RolapCubeLevel childCubeLevel = (RolapCubeLevel) childLevel;
1115            final RolapMember parent;
1116            if (parentMember != null) {
1117                parent = parentCubeMember.getRolapMember();
1118            } else {
1119                parent = null;
1120            }
1121            RolapMember member =
1122                super.makeMember(
1123                    parent,
1124                    childCubeLevel.getRolapLevel(),
1125                    value, captionValue, parentChild, stmt, key,
1126                    columnOffset);
1127            return
1128                memberReader.lookupCubeMember(
1129                    parentCubeMember,
1130                    member, childCubeLevel);
1131        }
1132
1133        public MemberCache getMemberCache() {
1134            // this is a special cache used solely for rolapcubemembers
1135            return memberSourceCacheHelper;
1136        }
1137
1138        /**
1139         * use the same lock in the RolapCubeMemberSource as the
1140         * RolapCubeHiearchyMemberReader to avoid deadlocks
1141         */
1142        public Object getMemberCacheLock() {
1143            return memberCacheLock;
1144        }
1145
1146        public RolapMember allMember() {
1147            return getHierarchy().getAllMember();
1148        }
1149    }
1150}
1151
1152// End RolapCubeHierarchy.java