001    /*
002    // $Id: //open/mondrian/src/main/mondrian/rolap/RolapHierarchy.java#84 $
003    // This software is subject to the terms of the Common Public License
004    // Agreement, available at the following URL:
005    // http://www.opensource.org/licenses/cpl.html.
006    // Copyright (C) 2001-2002 Kana Software, Inc.
007    // Copyright (C) 2001-2008 Julian Hyde and others
008    // All Rights Reserved.
009    // You must accept the terms of that agreement to use this software.
010    //
011    // jhyde, 10 August, 2001
012    */
013    
014    package mondrian.rolap;
015    
016    import mondrian.olap.*;
017    import mondrian.olap.DimensionType;
018    import mondrian.olap.LevelType;
019    import mondrian.olap.fun.*;
020    import mondrian.olap.type.*;
021    import mondrian.rolap.sql.SqlQuery;
022    import mondrian.resource.MondrianResource;
023    import mondrian.mdx.*;
024    import mondrian.calc.*;
025    import mondrian.calc.impl.*;
026    
027    import org.apache.log4j.Logger;
028    
029    import java.util.List;
030    import java.io.PrintWriter;
031    
032    /**
033     * <code>RolapHierarchy</code> implements {@link Hierarchy} for a ROLAP database.
034     *
035     * @author jhyde
036     * @since 10 August, 2001
037     * @version $Id: //open/mondrian/src/main/mondrian/rolap/RolapHierarchy.java#84 $
038     */
039    public class RolapHierarchy extends HierarchyBase {
040    
041        private static final Logger LOGGER = Logger.getLogger(RolapHierarchy.class);
042    
043        /**
044         * The raw member reader. For a member reader which incorporates access
045         * control and deals with hidden members (if the hierarchy is ragged), use
046         * {@link #createMemberReader(Role)}.
047         */
048        private MemberReader memberReader;
049        protected MondrianDef.Hierarchy xmlHierarchy;
050        private String memberReaderClass;
051        protected MondrianDef.RelationOrJoin relation;
052        private Member defaultMember;
053        private String defaultMemberName;
054        private RolapNullMember nullMember;
055    
056        private String sharedHierarchyName;
057    
058        private Exp aggregateChildrenExpression;
059    
060        /**
061         * Type for members of this hierarchy. Set once to avoid excessive newing.
062         */
063        final Type memberType = MemberType.forHierarchy(this);
064    
065        /**
066         * The level that the null member belongs too.
067         */
068        protected final RolapLevel nullLevel;
069    
070        /**
071         * The 'all' member of this hierarchy. This exists even if the hierarchy
072         * does not officially have an 'all' member.
073         */
074        private RolapMember allMember;
075        private static final String ALL_LEVEL_CARDINALITY = "1";
076    
077        RolapHierarchy(RolapDimension dimension, String subName, boolean hasAll) {
078            super(dimension, subName, hasAll);
079            this.allLevelName = "(All)";
080            this.allMemberName = "All " + name + "s";
081            if (hasAll) {
082                this.levels = new RolapLevel[1];
083                this.levels[0] = new RolapLevel(
084                        this, 0, this.allLevelName, null, null, null, null,
085                        null, null, null, RolapProperty.emptyArray, RolapLevel.FLAG_ALL | RolapLevel.FLAG_UNIQUE, null,
086                        RolapLevel.HideMemberCondition.Never, LevelType.Regular, "");
087            } else {
088                this.levels = new RolapLevel[0];
089            }
090    
091            // The null member belongs to a level with very similar properties to
092            // the 'all' level.
093            this.nullLevel = new RolapLevel(
094                    this, 0, this.allLevelName, null, null, null, null, null, null,
095                    null, RolapProperty.emptyArray,
096                    RolapLevel.FLAG_ALL | RolapLevel.FLAG_UNIQUE,
097                    null,
098                    RolapLevel.HideMemberCondition.Never,
099                    LevelType.Null, "");
100        }
101    
102        /**
103         * Creates a <code>RolapHierarchy</code>.
104         *
105         * @param dimension the dimension this hierarchy belongs to
106         * @param xmlHierarchy the xml object defining this hierarchy
107         * @param xmlCubeDimension the xml object defining the cube
108         *   dimension for this object
109         */
110        RolapHierarchy(
111            RolapDimension dimension,
112            MondrianDef.Hierarchy xmlHierarchy,
113            MondrianDef.CubeDimension xmlCubeDimension)
114        {
115            this(dimension, xmlHierarchy.name, xmlHierarchy.hasAll);
116    
117            assert !(this instanceof RolapCubeHierarchy);
118    
119            this.xmlHierarchy = xmlHierarchy;
120            this.relation = xmlHierarchy.relation;
121            if (xmlHierarchy.relation instanceof MondrianDef.InlineTable) {
122                this.relation =
123                    RolapUtil.convertInlineTableToRelation(
124                        (MondrianDef.InlineTable) xmlHierarchy.relation,
125                        getRolapSchema().getDialect());
126            }
127            this.memberReaderClass = xmlHierarchy.memberReaderClass;
128    
129            // Create an 'all' level even if the hierarchy does not officially
130            // have one.
131            if (xmlHierarchy.allMemberName != null) {
132                this.allMemberName = xmlHierarchy.allMemberName;
133            }
134            if (xmlHierarchy.allLevelName != null) {
135                this.allLevelName = xmlHierarchy.allLevelName;
136            }
137            RolapLevel allLevel = new RolapLevel(
138                this, 0, this.allLevelName, null, null, null, null, null, null,
139                null, RolapProperty.emptyArray,
140                RolapLevel.FLAG_ALL | RolapLevel.FLAG_UNIQUE,
141                null,
142                RolapLevel.HideMemberCondition.Never,
143                LevelType.Regular, ALL_LEVEL_CARDINALITY);
144            allLevel.init(xmlCubeDimension);
145            this.allMember = new RolapMember(
146                null, allLevel, null, allMemberName, Member.MemberType.ALL);
147            // assign "all member" caption
148            if (xmlHierarchy.allMemberCaption != null &&
149                xmlHierarchy.allMemberCaption.length() > 0) {
150                this.allMember.setCaption(xmlHierarchy.allMemberCaption);
151            }
152            this.allMember.setOrdinal(0);
153    
154            // If the hierarchy has an 'all' member, the 'all' level is level 0.
155            if (hasAll) {
156                this.levels = new RolapLevel[xmlHierarchy.levels.length + 1];
157                this.levels[0] = allLevel;
158                for (int i = 0; i < xmlHierarchy.levels.length; i++) {
159                    final MondrianDef.Level xmlLevel = xmlHierarchy.levels[i];
160                    if (xmlLevel.getKeyExp() == null &&
161                            xmlHierarchy.memberReaderClass == null) {
162                        throw MondrianResource.instance().LevelMustHaveNameExpression.ex(xmlLevel.name);
163                    }
164                    levels[i + 1] = new RolapLevel(this, i + 1, xmlLevel);
165                }
166            } else {
167                this.levels = new RolapLevel[xmlHierarchy.levels.length];
168                for (int i = 0; i < xmlHierarchy.levels.length; i++) {
169                    levels[i] = new RolapLevel(this, i, xmlHierarchy.levels[i]);
170                }
171            }
172    
173            if (xmlCubeDimension instanceof MondrianDef.DimensionUsage) {
174                String sharedDimensionName =
175                    ((MondrianDef.DimensionUsage) xmlCubeDimension).source;
176                this.sharedHierarchyName = sharedDimensionName;
177                if (subName != null) {
178                    this.sharedHierarchyName += "." + subName; // e.g. "Time.Weekly"
179                }
180            } else {
181                this.sharedHierarchyName = null;
182            }
183            if (xmlHierarchy.relation != null &&
184                    xmlHierarchy.memberReaderClass != null) {
185                throw MondrianResource.instance().
186                    HierarchyMustNotHaveMoreThanOneSource.ex(getUniqueName());
187            }
188            if (!Util.isEmpty(xmlHierarchy.caption)) {
189                setCaption(xmlHierarchy.caption);
190            }
191            defaultMemberName = xmlHierarchy.defaultMember;
192        }
193    
194        protected Logger getLogger() {
195            return LOGGER;
196        }
197    
198        public boolean equals(Object o) {
199            if (this == o) {
200                return true;
201            }
202            if (!(o instanceof RolapHierarchy)) {
203                return false;
204            }
205    
206            RolapHierarchy that = (RolapHierarchy)o;
207            if (sharedHierarchyName == null || that.sharedHierarchyName == null) {
208                return false;
209            } else {
210                return sharedHierarchyName.equals(that.sharedHierarchyName) &&
211                    getUniqueName().equals(that.getUniqueName());
212            }
213        }
214    
215        protected int computeHashCode() {
216            return super.computeHashCode()
217                ^ (sharedHierarchyName == null
218                    ? 0
219                    : sharedHierarchyName.hashCode());
220        }
221    
222        /**
223         * Initializes a hierarchy within the context of a cube.
224         */
225        void init(MondrianDef.CubeDimension xmlDimension) {
226            // first create memberReader
227            if (this.memberReader == null) {
228                this.memberReader = getRolapSchema().createMemberReader(
229                    sharedHierarchyName, this, memberReaderClass);
230            }
231            for (Level level : levels) {
232                ((RolapLevel) level).init(xmlDimension);
233            }
234            if (defaultMemberName != null) {
235                List<Id.Segment> uniqueNameParts =
236                    Util.parseIdentifier(defaultMemberName);
237    
238                // We strip off the parent dimension name if the defaultMemberName
239                // is the full unique name, [Time].[2004] rather than simply
240                // [2004].
241                //Dimension dim = getDimension();
242                // What we should strip off is hierarchy name
243                if (this.name.equals(uniqueNameParts.get(0).name)) {
244                    uniqueNameParts =
245                        uniqueNameParts.subList(1, uniqueNameParts.size());
246                }
247    
248                // Now lookup the name from the hierarchy's members.
249                defaultMember = memberReader.lookupMember(uniqueNameParts, false);
250                if (defaultMember == null) {
251                    throw Util.newInternal(
252                        "Can not find Default Member with name \""
253                            + defaultMemberName + "\" in Hierarchy \"" +
254                            getName() + "\"");
255                }
256            }
257        }
258    
259        void setMemberReader(MemberReader memberReader) {
260            this.memberReader = memberReader;
261        }
262    
263        MemberReader getMemberReader() {
264            return this.memberReader;
265        }
266    
267        RolapLevel newMeasuresLevel() {
268            RolapLevel level =
269                new RolapLevel(
270                    this, this.levels.length,
271                    "MeasuresLevel", null, null, null, null,
272                    null, null, null, RolapProperty.emptyArray, 0, null,
273                    RolapLevel.HideMemberCondition.Never, LevelType.Regular, "");
274            this.levels = RolapUtil.addElement(this.levels, level);
275            return level;
276        }
277    
278        /**
279         * If this hierarchy has precisely one table, returns that table;
280         * if this hierarchy has no table, return the cube's fact-table;
281         * otherwise, returns null.
282         */
283        MondrianDef.Relation getUniqueTable() {
284            if (relation instanceof MondrianDef.Relation) {
285                return (MondrianDef.Relation) relation;
286            } else if (relation instanceof MondrianDef.Join) {
287                return null;
288            } else {
289                throw Util.newInternal(
290                        "hierarchy's relation is a " + relation.getClass());
291            }
292        }
293    
294        boolean tableExists(String tableName) {
295            return (relation != null) && tableExists(tableName, relation);
296        }
297    
298        private static boolean tableExists(
299            String tableName,
300            MondrianDef.RelationOrJoin relationOrJoin)
301        {
302            if (relationOrJoin instanceof MondrianDef.Relation) {
303                MondrianDef.Relation relation =
304                    (MondrianDef.Relation) relationOrJoin;
305                return relation.getAlias().equals(tableName);
306            } else {
307                MondrianDef.Join join = (MondrianDef.Join) relationOrJoin;
308                return tableExists(tableName, join.left) ||
309                    tableExists(tableName, join.right);
310            }
311        }
312    
313        public RolapSchema getRolapSchema() {
314            return (RolapSchema) dimension.getSchema();
315        }
316    
317        public MondrianDef.RelationOrJoin getRelation() {
318            return relation;
319        }
320    
321        public MondrianDef.Hierarchy getXmlHierarchy() {
322            return xmlHierarchy;
323        }
324    
325        public Member getDefaultMember() {
326            // use lazy initialization to get around bootstrap issues
327            if (defaultMember == null) {
328                List rootMembers = memberReader.getRootMembers();
329                if (rootMembers.size() == 0) {
330                    throw MondrianResource.instance().InvalidHierarchyCondition.ex(this.getUniqueName());
331                }
332                defaultMember = (RolapMember) rootMembers.get(0);
333            }
334            return defaultMember;
335        }
336    
337        public Member getNullMember() {
338            // use lazy initialization to get around bootstrap issues
339            if (nullMember == null) {
340                nullMember = new RolapNullMember(nullLevel);
341            }
342            return nullMember;
343        }
344    
345        /**
346         * Returns the 'all' member.
347         */
348        public RolapMember getAllMember() {
349            return allMember;
350        }
351    
352        public Member createMember(
353                Member parent,
354                Level level,
355                String name,
356                Formula formula) {
357            if (formula == null) {
358                return new RolapMember(
359                    (RolapMember) parent, (RolapLevel) level, name);
360            } else if (level.getDimension().isMeasures()) {
361                return new RolapCalculatedMeasure(
362                    (RolapMember) parent, (RolapLevel) level, name, formula);
363            } else {
364                return new RolapCalculatedMember(
365                    (RolapMember) parent, (RolapLevel) level, name, formula);
366            }
367        }
368    
369        String getAlias() {
370            return getName();
371        }
372    
373        /**
374         * Returns the name of the source hierarchy, if this hierarchy is shared,
375         * otherwise null.
376         *
377         * <p>If this hierarchy is a public -- that is, it belongs to a dimension
378         * which is a usage of a shared dimension -- then
379         * <code>sharedHierarchyName</code> holds the unique name of the shared
380         * hierarchy; otherwise it is null.
381         *
382         * <p> Suppose this hierarchy is "Weekly" in the dimension "Order Date" of
383         * cube "Sales", and that "Order Date" is a usage of the "Time"
384         * dimension. Then <code>sharedHierarchyName</code> will be
385         * "[Time].[Weekly]".
386         */
387        public String getSharedHierarchyName() {
388            return sharedHierarchyName;
389        }
390    
391        /**
392         * Adds to the FROM clause of the query the tables necessary to access the
393         * members of this hierarchy. If <code>expression</code> is not null, adds
394         * the tables necessary to compute that expression.
395         *
396         * <p> This method is idempotent: if you call it more than once, it only
397         * adds the table(s) to the FROM clause once.
398         *
399         * @param query Query to add the hierarchy to
400         * @param expression Level to qualify up to; if null, qualifies up to the
401         *    topmost ('all') expression, which may require more columns and more joins
402         */
403        void addToFrom(SqlQuery query, MondrianDef.Expression expression) {
404            if (relation == null) {
405                throw Util.newError(
406                        "cannot add hierarchy " + getUniqueName() +
407                        " to query: it does not have a <Table>, <View> or <Join>");
408            }
409            final boolean failIfExists = false;
410            MondrianDef.RelationOrJoin subRelation = relation;
411            if (relation instanceof MondrianDef.Join) {
412                if (expression != null) {
413                    // Suppose relation is
414                    //   (((A join B) join C) join D)
415                    // and the fact table is
416                    //   F
417                    // and our expression uses C. We want to make the expression
418                    //   F left join ((A join B) join C).
419                    // Search for the smallest subset of the relation which
420                    // uses C.
421                    subRelation = relationSubset(relation, expression.getTableAlias());
422    
423                }
424            }
425            query.addFrom(subRelation, null, failIfExists);
426        }
427    
428        /**
429         * Adds a table to the FROM clause of the query.
430         * If <code>table</code> is not null, adds the table. Otherwise, add the
431         * relation on which this hierarchy is based on.
432         *
433         * <p> This method is idempotent: if you call it more than once, it only
434         * adds the table(s) to the FROM clause once.
435         *
436         * @param query Query to add the hierarchy to
437         * @param table table to add to the query
438         */
439        void addToFrom(SqlQuery query, RolapStar.Table table) {
440            if (getRelation() == null) {
441                throw Util.newError(
442                        "cannot add hierarchy " + getUniqueName() +
443                        " to query: it does not have a <Table>, <View> or <Join>");
444            }
445            final boolean failIfExists = false;
446            MondrianDef.RelationOrJoin subRelation = null;
447            if (table != null) {
448                // Suppose relation is
449                //   (((A join B) join C) join D)
450                // and the fact table is
451                //   F
452                // and the table to add is C. We want to make the expression
453                //   F left join ((A join B) join C).
454                // Search for the smallest subset of the relation which
455                // joins with C.
456                subRelation = lookupRelationSubset(getRelation(), table);
457            }
458    
459            if (subRelation == null) {
460                // If no table is found or specified, add the entire base relation.
461                subRelation = getRelation();
462            }
463    
464            query.addFrom(subRelation, null, failIfExists);
465        }
466    
467        /**
468         * Returns the smallest subset of <code>relation</code> which contains
469         * the relation <code>alias</code>, or null if these is no relation with
470         * such an alias.
471         * @param relation the relation in which to look for table by its alias
472         * @param alias table alias to search for
473         * @return the smallest containing relation or null if no matching table
474         * is found in <code>relation</code>
475         */
476        private static MondrianDef.RelationOrJoin relationSubset(
477            MondrianDef.RelationOrJoin relation,
478            String alias)
479        {
480            if (relation instanceof MondrianDef.Relation) {
481                MondrianDef.Relation table =
482                    (MondrianDef.Relation) relation;
483                return table.getAlias().equals(alias)
484                    ? relation
485                    : null;
486    
487            } else if (relation instanceof MondrianDef.Join) {
488                MondrianDef.Join join = (MondrianDef.Join) relation;
489                MondrianDef.RelationOrJoin rightRelation = relationSubset(join.right, alias);
490                return (rightRelation == null)
491                    ? relationSubset(join.left, alias)
492                    : join;
493    
494            } else {
495                throw Util.newInternal("bad relation type " + relation);
496            }
497        }
498    
499        /**
500         * Returns the smallest subset of <code>relation</code> which contains
501         * the table <code>targetTable</code>, or null if the targetTable is not
502         * one of the joining table in <code>relation</code>.
503         *
504         * @param relation the relation in which to look for targetTable
505         * @param targetTable table to add to the query
506         * @return the smallest containing relation or null if no matching table
507         * is found in <code>relation</code>
508         */
509        private static MondrianDef.RelationOrJoin lookupRelationSubset(
510            MondrianDef.RelationOrJoin relation,
511            RolapStar.Table targetTable)
512        {
513            if (relation instanceof MondrianDef.Table) {
514                MondrianDef.Table table = (MondrianDef.Table) relation;
515                if (table.name.equals(targetTable.getTableName())) {
516                    return relation;
517                } else {
518                    // Not the same table if table names are different
519                    return null;
520                }
521            } else if (relation instanceof MondrianDef.Join) {
522                // Search inside relation, starting from the rightmost table,
523                // and move left along the join chain.
524                MondrianDef.Join join = (MondrianDef.Join) relation;
525                MondrianDef.RelationOrJoin rightRelation =
526                    lookupRelationSubset(join.right, targetTable);
527                if (rightRelation == null) {
528                    // Keep searching left.
529                    return lookupRelationSubset(
530                        join.left, targetTable);
531                } else {
532                    // Found a match.
533                    return join;
534                }
535            }
536            return null;
537        }
538    
539        /**
540         * Creates a member reader which enforces the access-control profile of
541         * <code>role</code>.
542         *
543         * <p>This method may not be efficient, so the caller should take care
544         * not to call it too often. A cache is a good idea.
545         *
546         * @pre role != null
547         * @post return != null
548         */
549        MemberReader createMemberReader(Role role) {
550            final Access access = role.getAccess(this);
551            switch (access) {
552            case NONE:
553                throw Util.newInternal("Illegal access to members of hierarchy "
554                        + this);
555            case ALL:
556                return (isRagged())
557                    ? new RestrictedMemberReader(getMemberReader(), role)
558                    : getMemberReader();
559    
560            case CUSTOM:
561                final Role.HierarchyAccess hierarchyAccess =
562                    role.getAccessDetails(this);
563                final Role.RollupPolicy rollupPolicy =
564                    hierarchyAccess.getRollupPolicy();
565                final NumericType returnType = new NumericType();
566                switch (rollupPolicy) {
567                case FULL:
568                    return new RestrictedMemberReader(getMemberReader(), role);
569                case PARTIAL:
570                    Type memberType1 =
571                        new mondrian.olap.type.MemberType(
572                            getDimension(),
573                            getHierarchy(),
574                            null,
575                            null);
576                    SetType setType = new SetType(memberType1);
577                    ListCalc listCalc =
578                        new AbstractMemberListCalc(
579                            new DummyExp(setType), new Calc[0])
580                        {
581                            public List<Member> evaluateMemberList(
582                                Evaluator evaluator) {
583                                return FunUtil.getNonEmptyMemberChildren(
584                                    evaluator,
585                                    ((RolapEvaluator) evaluator).getExpanding());
586                            }
587    
588                            public boolean dependsOn(Dimension dimension) {
589                                return true;
590                            }
591                        };
592                    final Calc partialCalc =
593                        new LimitedRollupAggregateCalc(returnType, listCalc);
594    
595                    final Exp partialExp =
596                        new ResolvedFunCall(
597                            new FunDefBase("$x", "x", "In") {
598                                public Calc compileCall(
599                                    ResolvedFunCall call,
600                                    ExpCompiler compiler)
601                                {
602                                    return partialCalc;
603                                }
604    
605                                public void unparse(Exp[] args, PrintWriter pw) {
606                                    pw.print("$RollupAccessibleChildren()");
607                                }
608                            },
609                            new Exp[0],
610                            returnType);
611                    return new LimitedRollupSubstitutingMemberReader(
612                        role, hierarchyAccess, partialExp);
613    
614                case HIDDEN:
615                    Exp hiddenExp =
616                        new ResolvedFunCall(
617                            new FunDefBase("$x", "x", "In") {
618                                public Calc compileCall(
619                                    ResolvedFunCall call, ExpCompiler compiler)
620                                {
621                                    return new ConstantCalc(returnType, null);
622                                }
623    
624                                public void unparse(Exp[] args, PrintWriter pw) {
625                                    pw.print("$RollupAccessibleChildren()");
626                                }
627                            },
628                            new Exp[0],
629                            returnType);
630                    return new LimitedRollupSubstitutingMemberReader(
631                        role, hierarchyAccess, hiddenExp);
632                default:
633                    throw Util.unexpected(rollupPolicy);
634                }
635            default:
636                throw Util.badValue(access);
637            }
638        }
639    
640        /**
641         * A hierarchy is ragged if it contains one or more levels with hidden
642         * members.
643         */
644        public boolean isRagged() {
645            for (Level level : levels) {
646                if (((RolapLevel) level).getHideMemberCondition() !=
647                    RolapLevel.HideMemberCondition.Never) {
648                    return true;
649                }
650            }
651            return false;
652        }
653    
654        /**
655         * Returns an expression which will compute a member's value by aggregating
656         * its children.
657         *
658         * <p>It is efficient to share one expression between all calculated members in
659         * a parent-child hierarchy, so we only need need to validate the expression
660         * once.
661         */
662        synchronized Exp getAggregateChildrenExpression() {
663            if (aggregateChildrenExpression == null) {
664                UnresolvedFunCall fc = new UnresolvedFunCall(
665                    "$AggregateChildren",
666                    Syntax.Internal,
667                    new Exp[] {new HierarchyExpr(this)});
668                Validator validator =
669                        Util.createSimpleValidator(BuiltinFunTable.instance());
670                aggregateChildrenExpression = fc.accept(validator);
671            }
672            return aggregateChildrenExpression;
673        }
674    
675        /**
676         * Builds a dimension which maps onto a table holding the transitive
677         * closure of the relationship for this parent-child level.
678         *
679         * <p>This method is triggered by the
680         * {@link mondrian.olap.MondrianDef.Closure} element
681         * in a schema, and is only meaningful for a parent-child hierarchy.
682         *
683         * <p>When a Schema contains a parent-child Hierarchy that has an
684         * associated closure table, Mondrian creates a parallel internal
685         * Hierarchy, called a "closed peer", that refers to the closure table.
686         * This is indicated in the schema at the level of a Level, by including a
687         * Closure element. The closure table represents
688         * the transitive closure of the parent-child relationship.
689         *
690         * <p>The peer dimension, with its single hierarchy, and 3 levels (all,
691         * closure, item) really 'belong to' the parent-child level. If a single
692         * hierarchy had two parent-child levels (however unlikely this might be)
693         * then each level would have its own auxiliary dimension.
694         *
695         * <p>For example, in the demo schema the [HR].[Employee] dimension
696         * contains a parent-child hierarchy:
697         *
698         * <pre>
699         * &lt;Dimension name="Employees" foreignKey="employee_id"&gt;
700         *   &lt;Hierarchy hasAll="true" allMemberName="All Employees"
701         *         primaryKey="employee_id"&gt;
702         *     &lt;Table name="employee"/&gt;
703         *     &lt;Level name="Employee Id" type="Numeric" uniqueMembers="true"
704         *            column="employee_id" parentColumn="supervisor_id"
705         *            nameColumn="full_name" nullParentValue="0"&gt;
706         *       &lt;Closure parentColumn="supervisor_id" childColumn="employee_id"&gt;
707         *          &lt;Table name="employee_closure"/&gt;
708         *       &lt;/Closure&gt;
709         *       ...
710         * </pre>
711         * The internal closed peer Hierarchy has this structure:
712         * <pre>
713         * &lt;Dimension name="Employees" foreignKey="employee_id"&gt;
714         *     ...
715         *     &lt;Hierarchy name="Employees$Closure"
716         *         hasAll="true" allMemberName="All Employees"
717         *         primaryKey="employee_id" primaryKeyTable="employee_closure"&gt;
718         *       &lt;Join leftKey="supervisor_id" rightKey="employee_id"&gt;
719         *         &lt;Table name="employee_closure"/&gt;
720         *         &lt;Table name="employee"/&gt;
721         *       &lt;/Join&gt;
722         *       &lt;Level name="Closure"  type="Numeric" uniqueMembers="false"
723         *           table="employee_closure" column="supervisor_id"/&gt;
724         *       &lt;Level name="Employee" type="Numeric" uniqueMembers="true"
725         *           table="employee_closure" column="employee_id"/&gt;
726         *     &lt;/Hierarchy&gt;
727         * </pre>
728         *
729         * <p>Note that the original Level with the Closure produces two Levels in
730         * the closed peer Hierarchy: a simple peer (Employee) and a closed peer
731         * (Closure).
732         *
733         * @param src a parent-child Level that has a Closure clause
734         * @param clos a Closure clause
735         * @return the closed peer Level in the closed peer Hierarchy
736         */
737        RolapDimension createClosedPeerDimension(
738            RolapLevel src,
739            MondrianDef.Closure clos,
740            MondrianDef.CubeDimension xmlDimension) {
741    
742            // REVIEW (mb): What about attribute primaryKeyTable?
743    
744            // Create a peer dimension.
745            RolapDimension peerDimension = new RolapDimension(
746                dimension.getSchema(),
747                dimension.getName() + "$Closure",
748                DimensionType.StandardDimension,
749                dimension.isHighCardinality());
750    
751            // Create a peer hierarchy.
752            RolapHierarchy peerHier = peerDimension.newHierarchy(subName, true);
753            peerHier.allMemberName = getAllMemberName();
754            peerHier.allMember = getAllMember();
755            peerHier.allLevelName = getAllLevelName();
756            peerHier.sharedHierarchyName = getSharedHierarchyName();
757            MondrianDef.Join join = new MondrianDef.Join();
758            peerHier.relation = join;
759            join.left = clos.table;         // the closure table
760            join.leftKey = clos.parentColumn;
761            join.right = relation;     // the unclosed base table
762            join.rightKey = clos.childColumn;
763    
764            // Create the upper level.
765            // This represents all groups of descendants. For example, in the
766            // Employee closure hierarchy, this level has a row for every employee.
767            int index = peerHier.levels.length;
768            int flags = src.getFlags() &~ RolapLevel.FLAG_UNIQUE;
769            MondrianDef.Expression keyExp =
770                new MondrianDef.Column(clos.table.name, clos.parentColumn);
771    
772            RolapLevel level = new RolapLevel(peerHier, index++,
773                "Closure",
774                keyExp, null, null, null,
775                null, null,  // no longer a parent-child hierarchy
776                null,
777                RolapProperty.emptyArray,
778                flags,
779                src.getDatatype(),
780                src.getHideMemberCondition(),
781                src.getLevelType(),
782                "");
783            peerHier.levels = RolapUtil.addElement(peerHier.levels, level);
784    
785            // Create lower level.
786            // This represents individual items. For example, in the Employee
787            // closure hierarchy, this level has a row for every direct and
788            // indirect report of every employee (which is more than the number
789            // of employees).
790            flags = src.getFlags() | RolapLevel.FLAG_UNIQUE;
791            keyExp = new MondrianDef.Column(clos.table.name, clos.childColumn);
792            RolapLevel sublevel = new RolapLevel(
793                peerHier,
794                index++,
795                "Item",
796                keyExp,
797                null,
798                null,
799                null,
800                null,
801                null,  // no longer a parent-child hierarchy
802                null,
803                RolapProperty.emptyArray,
804                flags,
805                src.getDatatype(),
806                src.getHideMemberCondition(),
807                src.getLevelType(),
808                "");
809            peerHier.levels = RolapUtil.addElement(peerHier.levels, sublevel);
810    
811            return peerDimension;
812        }
813    
814        /**
815         * Sets default member of this Hierarchy.
816         *
817         * @param defaultMember Default member
818         */
819        public void setDefaultMember(Member defaultMember) {
820            if (defaultMember != null) {
821                this.defaultMember = defaultMember;
822            }
823        }
824    
825    
826        /**
827         * A <code>RolapNullMember</code> is the null member of its hierarchy.
828         * Every hierarchy has precisely one. They are yielded by operations such as
829         * <code>[Gender].[All].ParentMember</code>. Null members are usually
830         * omitted from sets (in particular, in the set constructor operator "{ ...
831         * }".
832         */
833        static class RolapNullMember extends RolapMember {
834            RolapNullMember(final RolapLevel level) {
835                super(null, level, null, RolapUtil.mdxNullLiteral, MemberType.NULL);
836                assert level != null;
837            }
838        }
839    
840        /**
841         * Calculated member which is also a measure (that is, a member of the
842         * [Measures] dimension).
843         */
844        protected static class RolapCalculatedMeasure
845            extends RolapCalculatedMember
846            implements RolapMeasure
847        {
848            private CellFormatter cellFormatter;
849    
850            public RolapCalculatedMeasure(
851                RolapMember parent, RolapLevel level, String name, Formula formula)
852            {
853                super(parent, level, name, formula);
854            }
855    
856            public synchronized void setProperty(String name, Object value) {
857                if (name.equals(Property.CELL_FORMATTER.getName())) {
858                    String cellFormatterClass = (String) value;
859                    try {
860                        this.cellFormatter =
861                            RolapCube.getCellFormatter(cellFormatterClass);
862                    } catch (Exception e) {
863                        throw MondrianResource.instance().CellFormatterLoadFailed.ex(
864                            cellFormatterClass, getUniqueName(), e);
865                    }
866                }
867                super.setProperty(name, value);
868            }
869    
870            public CellFormatter getFormatter() {
871                return cellFormatter;
872            }
873        }
874    
875        /**
876         * Substitute for a member in a hierarchy whose rollup policy is 'partial'
877         * or 'hidden'. The member is calculated using an expression which
878         * aggregates only visible descendants.
879         *
880         * <p>Note that this class extends RolapCubeMember only because other code
881         * expects that all members in a RolapCubeHierarchy are RolapCubeMembers.
882         *
883         * @see mondrian.olap.Role.RollupPolicy
884         */
885        public static class LimitedRollupMember extends RolapCubeMember {
886            public final RolapMember member;
887            private final Exp exp;
888    
889            LimitedRollupMember(
890                RolapCubeMember member,
891                Exp exp)
892            {
893                super(
894                    member.getParentMember(),
895                    member.getRolapMember(),
896                    member.getLevel(),
897                    member.getCube());
898                assert !(member instanceof LimitedRollupMember);
899                this.member = member;
900                this.exp = exp;
901            }
902    
903            public boolean equals(Object o) {
904                return o instanceof LimitedRollupMember
905                    && ((LimitedRollupMember) o).member.equals(member);
906            }
907    
908            public int hashCode() {
909                return member.hashCode();
910            }
911    
912            public Exp getExpression() {
913                return exp;
914            }
915    
916            protected boolean computeCalculated(final MemberType memberType) {
917                return true;
918            }
919    
920            public boolean isCalculated() {
921                return true;
922            }
923        }
924    
925        /**
926         * Member reader which wraps a hierarchy's member reader, and if the
927         * role has limited access to the hierarchy, replaces members with
928         * dummy members which evaluate to the sum of only the accessible children.
929         */
930        private class LimitedRollupSubstitutingMemberReader
931            extends SubstitutingMemberReader
932        {
933            private final Role.HierarchyAccess hierarchyAccess;
934            private final Exp exp;
935    
936            public LimitedRollupSubstitutingMemberReader(
937                Role role,
938                Role.HierarchyAccess hierarchyAccess,
939                Exp exp)
940            {
941                super(
942                    new RestrictedMemberReader(
943                        RolapHierarchy.this.getMemberReader(), role));
944                this.hierarchyAccess = hierarchyAccess;
945                this.exp = exp;
946            }
947    
948            @Override
949            public RolapMember substitute(final RolapMember member) {
950                if (member != null
951                    && (hierarchyAccess.getAccess(member) == Access.CUSTOM
952                    || hierarchyAccess.hasInaccessibleDescendants(member)))
953                {
954                    // Member is visible, but at least one of its
955                    // descendants is not.
956                    return new LimitedRollupMember((RolapCubeMember)member, exp);
957                } else {
958                    // No need to substitute. Member and all of its
959                    // descendants are accessible. Total for member
960                    // is same as for FULL policy.
961                    return member;
962                }
963            }
964    
965            @Override
966            public RolapMember desubstitute(RolapMember member) {
967                if (member instanceof LimitedRollupMember) {
968                    return ((LimitedRollupMember) member).member;
969                } else {
970                    return member;
971                }
972            }
973        }
974    
975        /**
976         * Compiled expression that computes rollup over a set of visible children.
977         * The {@code listCalc} expression determines that list of children.
978         */
979        private static class LimitedRollupAggregateCalc
980            extends AggregateFunDef.AggregateCalc
981        {
982            public LimitedRollupAggregateCalc(Type returnType, ListCalc listCalc) {
983                super(
984                    new DummyExp(returnType),
985                    listCalc,
986                    new ValueCalc(new DummyExp(returnType)));
987            }
988        }
989    }
990    
991    // End RolapHierarchy.java