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