001    /*
002    // $Id: //open/mondrian-release/3.2/src/main/mondrian/rolap/RolapCube.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    // jhyde, 10 August, 2001
012    */
013    
014    package mondrian.rolap;
015    
016    import mondrian.mdx.*;
017    import mondrian.olap.*;
018    import mondrian.olap.fun.FunDefBase;
019    import mondrian.resource.MondrianResource;
020    import mondrian.rolap.aggmatcher.ExplicitRules;
021    import mondrian.rolap.cache.SoftSmartCache;
022    import mondrian.calc.*;
023    import org.apache.log4j.Logger;
024    import org.eigenbase.xom.*;
025    import org.eigenbase.xom.Parser;
026    
027    import java.lang.reflect.Constructor;
028    import java.util.*;
029    
030    /**
031     * <code>RolapCube</code> implements {@link Cube} for a ROLAP database.
032     *
033     * @author jhyde
034     * @since 10 August, 2001
035     * @version $Id: //open/mondrian-release/3.2/src/main/mondrian/rolap/RolapCube.java#7 $
036     */
037    public class RolapCube extends CubeBase {
038    
039        private static final Logger LOGGER = Logger.getLogger(RolapCube.class);
040    
041        private final RolapSchema schema;
042        private final Map<String, Annotation> annotationMap;
043        private final RolapHierarchy measuresHierarchy;
044    
045        /** For SQL generator. Fact table. */
046        final MondrianDef.Relation fact;
047    
048        /** Schema reader which can see this cube and nothing else. */
049        private SchemaReader schemaReader;
050    
051        /**
052         * List of calculated members.
053         */
054        private final List<Formula> calculatedMemberList = new ArrayList<Formula>();
055    
056        /**
057         * Role-based cache of calculated members
058         */
059        private final SoftSmartCache<Role, List<Member>>
060            roleToAccessibleCalculatedMembers =
061            new SoftSmartCache<Role, List<Member>>();
062    
063        /**
064         * List of named sets.
065         */
066        private final List<Formula> namedSetList = new ArrayList<Formula>();
067    
068        /** Contains {@link HierarchyUsage}s for this cube */
069        private final List<HierarchyUsage> hierarchyUsages;
070    
071        private RolapStar star;
072        private ExplicitRules.Group aggGroup;
073    
074        /**
075         * True if the cube is being created while loading the schema
076         */
077        private boolean load;
078    
079        private final Map<Hierarchy, HierarchyUsage> firstUsageMap =
080            new HashMap<Hierarchy, HierarchyUsage>();
081    
082        /**
083         * Refers {@link RolapCubeUsages} if this is a virtual cube
084         */
085        private RolapCubeUsages cubeUsages;
086    
087        RolapBaseCubeMeasure factCountMeasure;
088    
089        final List<RolapHierarchy> hierarchyList =
090            new ArrayList<RolapHierarchy>();
091    
092        /**
093         * Set to true when a cube is being modified after creation.
094         *
095         * @see #isLoadInProgress()
096         */
097        private boolean loadInProgress = false;
098    
099        private Map<RolapLevel, RolapCubeLevel> virtualToBaseMap =
100            new HashMap<RolapLevel, RolapCubeLevel>();
101    
102        final BitKey closureColumnBitKey;
103    
104        /**
105         * Private constructor used by both normal cubes and virtual cubes.
106         *
107         * @param schema Schema cube belongs to
108         * @param name Name of cube
109         * @param caption Caption
110         * @param description Description
111         * @param fact Definition of fact table
112         * @param annotationMap Annotations
113         */
114        private RolapCube(
115            RolapSchema schema,
116            MondrianDef.Schema xmlSchema,
117            String name,
118            String caption,
119            String description,
120            boolean isCache,
121            MondrianDef.Relation fact,
122            MondrianDef.CubeDimension[] dimensions,
123            boolean load,
124            Map<String, Annotation> annotationMap)
125        {
126            super(
127                name,
128                caption,
129                description,
130                new RolapDimension[dimensions.length + 1]);
131    
132            assert annotationMap != null;
133            this.schema = schema;
134            this.annotationMap = annotationMap;
135            this.caption = caption;
136            this.fact = fact;
137            this.hierarchyUsages = new ArrayList<HierarchyUsage>();
138            this.load = load;
139    
140            if (! isVirtual()) {
141                this.star = schema.getRolapStarRegistry().getOrCreateStar(fact);
142                // only set if different from default (so that if two cubes share
143                // the same fact table, either can turn off caching and both are
144                // effected).
145                if (! isCache) {
146                    star.setCacheAggregations(isCache);
147                }
148                closureColumnBitKey =
149                    BitKey.Factory.makeBitKey(star.getColumnCount());
150            } else {
151                closureColumnBitKey = null;
152            }
153    
154            if (getLogger().isDebugEnabled()) {
155                if (isVirtual()) {
156                    getLogger().debug(
157                        "RolapCube<init>: virtual cube="  + this.name);
158                } else {
159                    getLogger().debug("RolapCube<init>: cube="  + this.name);
160                }
161            }
162    
163            RolapDimension measuresDimension =
164                new RolapDimension(
165                    schema,
166                    Dimension.MEASURES_NAME,
167                    null,
168                    null,
169                    DimensionType.MeasuresDimension,
170                    false,
171                    Collections.<String, Annotation>emptyMap());
172    
173            this.dimensions[0] = measuresDimension;
174    
175            this.measuresHierarchy =
176                measuresDimension.newHierarchy(null, false, null);
177            hierarchyList.add(measuresHierarchy);
178    
179            if (!Util.isEmpty(xmlSchema.measuresCaption)) {
180                measuresDimension.setCaption(xmlSchema.measuresCaption);
181                this.measuresHierarchy.setCaption(xmlSchema.measuresCaption);
182            }
183    
184            for (int i = 0; i < dimensions.length; i++) {
185                MondrianDef.CubeDimension xmlCubeDimension = dimensions[i];
186                // Look up usages of shared dimensions in the schema before
187                // consulting the XML schema (which may be null).
188                RolapCubeDimension dimension =
189                    getOrCreateDimension(
190                        xmlCubeDimension, schema, xmlSchema, i + 1, hierarchyList);
191                if (getLogger().isDebugEnabled()) {
192                    getLogger().debug(
193                        "RolapCube<init>: dimension=" + dimension.getName());
194                }
195                this.dimensions[i + 1] = dimension;
196    
197                if (! isVirtual()) {
198                    createUsages(dimension, xmlCubeDimension);
199                }
200    
201                // the register Dimension call was moved here
202                // to keep the RolapStar in sync with the realiasing
203                // within the RolapCubeHierarchy objects.
204                registerDimension(dimension);
205            }
206    
207            schema.addCube(this);
208        }
209    
210        /**
211         * Creates a <code>RolapCube</code> from a regular cube.
212         */
213        RolapCube(
214            RolapSchema schema,
215            MondrianDef.Schema xmlSchema,
216            MondrianDef.Cube xmlCube,
217            boolean load)
218        {
219            this(
220                schema,
221                xmlSchema,
222                xmlCube.name,
223                xmlCube.caption,
224                xmlCube.description,
225                xmlCube.cache,
226                xmlCube.fact,
227                xmlCube.dimensions,
228                load,
229                RolapHierarchy.createAnnotationMap(xmlCube.annotations));
230    
231            if (fact == null) {
232                throw Util.newError(
233                    "Must specify fact table of cube '" + getName() + "'");
234            }
235    
236            if (fact.getAlias() == null) {
237                throw Util.newError(
238                    "Must specify alias for fact table of cube '" + getName()
239                    + "'");
240            }
241    
242            // since MondrianDef.Measure and MondrianDef.VirtualCubeMeasure
243            // can not be treated as the same, measure creation can not be
244            // done in a common constructor.
245            RolapLevel measuresLevel = this.measuresHierarchy.newMeasuresLevel();
246    
247            List<RolapMember> measureList =
248                new ArrayList<RolapMember>(xmlCube.measures.length);
249            Member defaultMeasure = null;
250            for (int i = 0; i < xmlCube.measures.length; i++) {
251                RolapBaseCubeMeasure measure =
252                    createMeasure(xmlCube, measuresLevel, i, xmlCube.measures[i]);
253                measureList.add(measure);
254    
255                // Is this the default measure?
256                if (Util.equalName(measure.getName(), xmlCube.defaultMeasure)) {
257                    defaultMeasure = measure;
258                }
259    
260                if (measure.getAggregator() == RolapAggregator.Count) {
261                    factCountMeasure = measure;
262                }
263            }
264    
265            boolean writebackEnabled = false;
266            for (RolapHierarchy hierarchy : hierarchyList) {
267                if (ScenarioImpl.isScenario(hierarchy)) {
268                    writebackEnabled = true;
269                }
270            }
271    
272            // Ensure that cube has an atomic cell count
273            // measure even if the schema does not contain one.
274            if (factCountMeasure == null) {
275                final MondrianDef.Measure xmlMeasure = new MondrianDef.Measure();
276                xmlMeasure.aggregator = "count";
277                xmlMeasure.name = "Fact Count";
278                xmlMeasure.visible = false;
279                factCountMeasure =
280                    createMeasure(
281                        xmlCube, measuresLevel, measureList.size(), xmlMeasure);
282                measureList.add(factCountMeasure);
283            }
284    
285            setMeasuresHierarchyMemberReader(
286                new CacheMemberReader(
287                    new MeasureMemberSource(this.measuresHierarchy, measureList)));
288    
289            this.measuresHierarchy.setDefaultMember(defaultMeasure);
290            init(xmlCube.dimensions);
291            init(xmlCube, measureList);
292    
293            setMeasuresHierarchyMemberReader(
294                new CacheMemberReader(
295                    new MeasureMemberSource(this.measuresHierarchy, measureList)));
296    
297            checkOrdinals(xmlCube.name, measureList);
298            loadAggGroup(xmlCube);
299        }
300    
301        /**
302         * Creates a measure.
303         *
304         * @param xmlCube XML cube
305         * @param measuresLevel Member that all measures belong to
306         * @param ordinal Ordinal of measure
307         * @param xmlMeasure XML measure
308         * @return Measure
309         */
310        private RolapBaseCubeMeasure createMeasure(
311            MondrianDef.Cube xmlCube,
312            RolapLevel measuresLevel,
313            int ordinal,
314            final MondrianDef.Measure xmlMeasure)
315        {
316            MondrianDef.Expression measureExp;
317            if (xmlMeasure.column != null) {
318                if (xmlMeasure.measureExp != null) {
319                    throw MondrianResource.instance().BadMeasureSource.ex(
320                        xmlCube.name, xmlMeasure.name);
321                }
322                measureExp = new MondrianDef.Column(
323                    fact.getAlias(), xmlMeasure.column);
324            } else if (xmlMeasure.measureExp != null) {
325                measureExp = xmlMeasure.measureExp;
326            } else if (xmlMeasure.aggregator.equals("count")) {
327                // it's ok if count has no expression; it means 'count(*)'
328                measureExp = null;
329            } else {
330                throw MondrianResource.instance().BadMeasureSource.ex(
331                    xmlCube.name, xmlMeasure.name);
332            }
333    
334            // Validate aggregator name. Substitute deprecated "distinct count"
335            // with modern "distinct-count".
336            String aggregator = xmlMeasure.aggregator;
337            if (aggregator.equals("distinct count")) {
338                aggregator = RolapAggregator.DistinctCount.getName();
339            }
340            final RolapBaseCubeMeasure measure =
341                new RolapBaseCubeMeasure(
342                    this, null, measuresLevel, xmlMeasure.name,
343                    xmlMeasure.caption, xmlMeasure.description,
344                    xmlMeasure.formatString, measureExp,
345                    aggregator, xmlMeasure.datatype,
346                    RolapHierarchy.createAnnotationMap(xmlMeasure.annotations));
347    
348            try {
349                CellFormatter cellFormatter =
350                    getCellFormatter(xmlMeasure.formatter);
351                if (cellFormatter != null) {
352                    measure.setFormatter(cellFormatter);
353                }
354            } catch (Exception e) {
355                throw MondrianResource.instance().CellFormatterLoadFailed.ex(
356                    xmlMeasure.formatter, measure.getUniqueName(), e);
357            }
358    
359            // Set member's caption, if present.
360            if (!Util.isEmpty(xmlMeasure.caption)) {
361                // there is a special caption string
362                measure.setProperty(
363                    Property.CAPTION.name,
364                    xmlMeasure.caption);
365            }
366    
367            // Set member's visibility, default true.
368            Boolean visible = xmlMeasure.visible;
369            if (visible == null) {
370                visible = Boolean.TRUE;
371            }
372            measure.setProperty(Property.VISIBLE.name, visible);
373    
374            List<String> propNames = new ArrayList<String>();
375            List<String> propExprs = new ArrayList<String>();
376            validateMemberProps(
377                xmlMeasure.memberProperties, propNames, propExprs, xmlMeasure.name);
378            for (int j = 0; j < propNames.size(); j++) {
379                String propName = propNames.get(j);
380                final Object propExpr = propExprs.get(j);
381                measure.setProperty(propName, propExpr);
382                if (propName.equals(Property.MEMBER_ORDINAL.name)
383                    && propExpr instanceof String)
384                {
385                    final String expr = (String) propExpr;
386                    if (expr.startsWith("\"")
387                        && expr.endsWith("\""))
388                    {
389                        try {
390                            ordinal =
391                                Integer.valueOf(
392                                    expr.substring(1, expr.length() - 1));
393                        } catch (NumberFormatException e) {
394                            Util.discard(e);
395                        }
396                    }
397                }
398            }
399            measure.setOrdinal(ordinal);
400            return measure;
401        }
402    
403        /**
404         * Makes sure that the schemaReader cache is invalidated.
405         * Problems can occur if the measure hierarchy member reader is out
406         * of sync with the cache.
407         *
408         * @param memberReader new member reader for measures hierarchy
409         */
410        private void setMeasuresHierarchyMemberReader(MemberReader memberReader) {
411            this.measuresHierarchy.setMemberReader(memberReader);
412            // this invalidates any cached schema reader
413            this.schemaReader = null;
414        }
415    
416        /**
417         * Given the name of a cell formatter class, returns a cell formatter.
418         * If class name is null, returns null.
419         *
420         * @param cellFormatterClassName Name of cell formatter class
421         * @return Cell formatter or null
422         * @throws Exception if class cannot be instantiated
423         */
424        @SuppressWarnings({"unchecked"})
425        static CellFormatter getCellFormatter(
426            String cellFormatterClassName)
427            throws Exception
428        {
429            if (Util.isEmpty(cellFormatterClassName)) {
430                return null;
431            }
432            Class<CellFormatter> clazz =
433                (Class<CellFormatter>)
434                    Class.forName(cellFormatterClassName);
435            Constructor<CellFormatter> ctor = clazz.getConstructor();
436            return ctor.newInstance();
437        }
438    
439        /**
440         * Creates a <code>RolapCube</code> from a virtual cube.
441         */
442        RolapCube(
443            RolapSchema schema,
444            MondrianDef.Schema xmlSchema,
445            MondrianDef.VirtualCube xmlVirtualCube,
446            boolean load)
447        {
448            this(
449                schema,
450                xmlSchema,
451                xmlVirtualCube.name,
452                xmlVirtualCube.caption,
453                xmlVirtualCube.description,
454                true,
455                null,
456                xmlVirtualCube.dimensions,
457                load,
458                RolapHierarchy.createAnnotationMap(xmlVirtualCube.annotations));
459    
460            // Since MondrianDef.Measure and MondrianDef.VirtualCubeMeasure cannot
461            // be treated as the same, measure creation cannot be done in a common
462            // constructor.
463            RolapLevel measuresLevel = this.measuresHierarchy.newMeasuresLevel();
464    
465            // Recreate CalculatedMembers, as the original members point to
466            // incorrect dimensional ordinals for the virtual cube.
467            List<RolapVirtualCubeMeasure> origMeasureList =
468                new ArrayList<RolapVirtualCubeMeasure>();
469            List<MondrianDef.CalculatedMember> origCalcMeasureList =
470                new ArrayList<MondrianDef.CalculatedMember>();
471            CubeComparator cubeComparator = new CubeComparator();
472            Map<RolapCube, List<MondrianDef.CalculatedMember>>
473                calculatedMembersMap =
474                new TreeMap<RolapCube, List<MondrianDef.CalculatedMember>>(
475                    cubeComparator);
476            Member defaultMeasure = null;
477    
478            this.cubeUsages = new RolapCubeUsages(xmlVirtualCube.cubeUsage);
479    
480            for (MondrianDef.VirtualCubeMeasure xmlMeasure
481                : xmlVirtualCube.measures)
482            {
483                // Lookup a measure in an existing cube.
484                RolapCube cube = schema.lookupCube(xmlMeasure.cubeName);
485                if (cube == null) {
486                    throw Util.newError(
487                        "Cube '" + xmlMeasure.cubeName + "' not found");
488                }
489                List<Member> cubeMeasures = cube.getMeasures();
490                boolean found = false;
491                for (Member cubeMeasure : cubeMeasures) {
492                    if (cubeMeasure.getUniqueName().equals(xmlMeasure.name)) {
493                        if (cubeMeasure.getName().equalsIgnoreCase(
494                            xmlVirtualCube.defaultMeasure))
495                        {
496                            defaultMeasure = cubeMeasure;
497                        }
498                        found = true;
499                        if (cubeMeasure instanceof RolapCalculatedMember) {
500                            // We have a calulated member!  Keep track of which
501                            // base cube each calculated member is associated
502                            // with, so we can resolve the calculated member
503                            // relative to its base cube.  We're using a treeMap
504                            // to store the mapping to ensure a deterministic
505                            // order for the members.
506                            MondrianDef.CalculatedMember calcMember =
507                                schema.lookupXmlCalculatedMember(
508                                    xmlMeasure.name, xmlMeasure.cubeName);
509                            if (calcMember == null) {
510                                throw Util.newInternal(
511                                    "Could not find XML Calculated Member '"
512                                    + xmlMeasure.name + "' in XML cube '"
513                                    + xmlMeasure.cubeName + "'");
514                            }
515                            List<MondrianDef.CalculatedMember> memberList =
516                                calculatedMembersMap.get(cube);
517                            if (memberList == null) {
518                                memberList =
519                                    new ArrayList<MondrianDef.CalculatedMember>();
520                            }
521                            memberList.add(calcMember);
522                            origCalcMeasureList.add(calcMember);
523                            calculatedMembersMap.put(cube, memberList);
524                        } else {
525                            // This is the a standard measure. (Don't know
526                            // whether it will confuse things that this
527                            // measure still points to its 'real' cube.)
528                            RolapVirtualCubeMeasure virtualCubeMeasure =
529                                new RolapVirtualCubeMeasure(
530                                    null,
531                                    measuresLevel,
532                                    (RolapStoredMeasure) cubeMeasure,
533                                    RolapHierarchy.createAnnotationMap(
534                                        xmlMeasure.annotations));
535    
536                            // Set member's visibility, default true.
537                            Boolean visible = xmlMeasure.visible;
538                            if (visible == null) {
539                                visible = Boolean.TRUE;
540                            }
541                            virtualCubeMeasure.setProperty(
542                                Property.VISIBLE.name,
543                                visible);
544                            // Inherit caption from the "real" measure
545                            virtualCubeMeasure.setProperty(
546                                Property.CAPTION.name,
547                                cubeMeasure.getCaption());
548                            origMeasureList.add(virtualCubeMeasure);
549                        }
550                        break;
551                    }
552                }
553                if (!found) {
554                    throw Util.newInternal(
555                        "could not find measure '" + xmlMeasure.name
556                        + "' in cube '" + xmlMeasure.cubeName + "'");
557                }
558            }
559    
560            // Must init the dimensions before dealing with calculated members
561            init(xmlVirtualCube.dimensions);
562    
563            // Loop through the base cubes containing calculated members
564            // referenced by this virtual cube.  Resolve those members relative
565            // to their base cubes first, then resolve them relative to this
566            // cube so the correct dimension ordinals are used
567            List<RolapVirtualCubeMeasure> modifiedMeasureList =
568                new ArrayList<RolapVirtualCubeMeasure>(origMeasureList);
569            for (Object o : calculatedMembersMap.keySet()) {
570                RolapCube baseCube = (RolapCube) o;
571                List<MondrianDef.CalculatedMember> xmlCalculatedMemberList =
572                    calculatedMembersMap.get(baseCube);
573                Query queryExp =
574                    resolveCalcMembers(
575                        xmlCalculatedMemberList,
576                        Collections.<MondrianDef.NamedSet>emptyList(),
577                        baseCube,
578                        false);
579                MeasureFinder measureFinder =
580                    new MeasureFinder(this, baseCube, measuresLevel);
581                queryExp.accept(measureFinder);
582                modifiedMeasureList.addAll(measureFinder.getMeasuresFound());
583            }
584    
585            // Add the original calculated members from the base cubes to our
586            // list of calculated members
587            List<MondrianDef.CalculatedMember> xmlCalculatedMemberList =
588                new ArrayList<MondrianDef.CalculatedMember>();
589            for (Object o : calculatedMembersMap.keySet()) {
590                RolapCube baseCube = (RolapCube) o;
591                xmlCalculatedMemberList.addAll(
592                    calculatedMembersMap.get(baseCube));
593            }
594            xmlCalculatedMemberList.addAll(
595                Arrays.asList(xmlVirtualCube.calculatedMembers));
596    
597    
598            // Resolve all calculated members relative to this virtual cube,
599            // whose measureHierarchy member reader now contains all base
600            // measures referenced in those calculated members
601            setMeasuresHierarchyMemberReader(
602                new CacheMemberReader(
603                    new MeasureMemberSource(
604                        this.measuresHierarchy,
605                        Util.<RolapMember>cast(modifiedMeasureList))));
606    
607            createCalcMembersAndNamedSets(
608                xmlCalculatedMemberList,
609                Arrays.asList(xmlVirtualCube.namedSets),
610                new ArrayList<RolapMember>(),
611                new ArrayList<Formula>(),
612                this,
613                false);
614    
615            // reset the measureHierarchy member reader back to the list of
616            // measures that are only defined on this virtual cube
617            setMeasuresHierarchyMemberReader(
618                new CacheMemberReader(
619                    new MeasureMemberSource(
620                        this.measuresHierarchy,
621                        Util.<RolapMember>cast(origMeasureList))));
622    
623            this.measuresHierarchy.setDefaultMember(defaultMeasure);
624    
625            List<MondrianDef.CalculatedMember> xmlVirtualCubeCalculatedMemberList =
626                    Arrays.asList(xmlVirtualCube.calculatedMembers);
627            if (!vcHasAllCalcMembers(
628                origCalcMeasureList, xmlVirtualCubeCalculatedMemberList))
629            {
630                // Remove from the calculated members array
631                // those members that weren't originally defined
632                // on this virtual cube.
633                List<Formula> calculatedMemberListCopy =
634                    new ArrayList<Formula>(calculatedMemberList);
635                calculatedMemberList.clear();
636                for (Formula calculatedMember : calculatedMemberListCopy) {
637                    if (findOriginalMembers(
638                        calculatedMember,
639                        origCalcMeasureList,
640                        calculatedMemberList))
641                    {
642                        continue;
643                    }
644                    findOriginalMembers(
645                        calculatedMember,
646                        xmlVirtualCubeCalculatedMemberList,
647                        calculatedMemberList);
648                }
649            }
650    
651            for (Formula calcMember : calculatedMemberList) {
652                if (calcMember.getName().equalsIgnoreCase(
653                    xmlVirtualCube.defaultMeasure))
654                {
655                    this.measuresHierarchy.setDefaultMember(
656                        calcMember.getMdxMember());
657                    break;
658                }
659            }
660    
661            // Note: virtual cubes do not get aggregate
662        }
663    
664        private boolean vcHasAllCalcMembers(
665            List<MondrianDef.CalculatedMember> origCalcMeasureList,
666            List<MondrianDef.CalculatedMember> xmlVirtualCubeCalculatedMemberList)
667        {
668            return calculatedMemberList.size()
669                == (origCalcMeasureList.size()
670                + xmlVirtualCubeCalculatedMemberList.size());
671        }
672    
673        private boolean findOriginalMembers(
674            Formula formula,
675            List<MondrianDef.CalculatedMember> xmlCalcMemberList,
676            List<Formula> calcMemberList)
677        {
678            for (MondrianDef.CalculatedMember xmlCalcMember : xmlCalcMemberList) {
679                Dimension dimension =
680                    lookupDimension(
681                        new Id.Segment(
682                            xmlCalcMember.dimension,
683                            Id.Quoting.UNQUOTED));
684                if (formula.getName().equals(xmlCalcMember.name)
685                    && formula.getMdxMember().getDimension().getName().equals(
686                        dimension.getName()))
687                {
688                    calcMemberList.add(formula);
689                    return true;
690                }
691            }
692            return false;
693        }
694    
695        protected Logger getLogger() {
696            return LOGGER;
697        }
698    
699        public Map<String, Annotation> getAnnotationMap() {
700            return annotationMap;
701        }
702    
703        public boolean hasAggGroup() {
704            return aggGroup != null;
705        }
706    
707        public ExplicitRules.Group getAggGroup() {
708            return aggGroup;
709        }
710    
711        void loadAggGroup(MondrianDef.Cube xmlCube) {
712            aggGroup = ExplicitRules.Group.make(this, xmlCube);
713        }
714    
715        /**
716         * Creates a dimension from its XML definition. If the XML definition is
717         * a &lt;DimensionUsage&gt;, and the shared dimension is cached in the
718         * schema, returns that.
719         *
720         * @param xmlCubeDimension XML Dimension or DimensionUsage
721         * @param schema Schema
722         * @param xmlSchema XML Schema
723         * @param dimensionOrdinal Ordinal of dimension
724         * @param cubeHierarchyList List of hierarchies in cube
725         * @return A dimension
726         */
727        private RolapCubeDimension getOrCreateDimension(
728            MondrianDef.CubeDimension xmlCubeDimension,
729            RolapSchema schema,
730            MondrianDef.Schema xmlSchema,
731            int dimensionOrdinal,
732            List<RolapHierarchy> cubeHierarchyList)
733        {
734            RolapDimension dimension = null;
735            if (xmlCubeDimension instanceof MondrianDef.DimensionUsage) {
736                MondrianDef.DimensionUsage usage =
737                    (MondrianDef.DimensionUsage) xmlCubeDimension;
738                final RolapHierarchy sharedHierarchy =
739                    schema.getSharedHierarchy(usage.source);
740                if (sharedHierarchy != null) {
741                    dimension =
742                        (RolapDimension) sharedHierarchy.getDimension();
743                }
744            }
745    
746            if (dimension == null) {
747                MondrianDef.Dimension xmlDimension =
748                    xmlCubeDimension.getDimension(xmlSchema);
749                dimension =
750                    new RolapDimension(
751                        schema, this, xmlDimension, xmlCubeDimension);
752            }
753    
754            // wrap the shared or regular dimension with a
755            // rolap cube dimension object
756            return new RolapCubeDimension(
757                this, dimension, xmlCubeDimension,
758                xmlCubeDimension.name, dimensionOrdinal,
759                cubeHierarchyList, xmlCubeDimension.highCardinality);
760        }
761    
762        /**
763         * Post-initialization, doing things which cannot be done in the
764         * constructor.
765         */
766        private void init(
767            MondrianDef.Cube xmlCube,
768            final List<RolapMember> memberList)
769        {
770            // Load calculated members and named sets.
771            // (We cannot do this in the constructor, because
772            // cannot parse the generated query, because the schema has not been
773            // set in the cube at this point.)
774            List<Formula> formulaList = new ArrayList<Formula>();
775            createCalcMembersAndNamedSets(
776                Arrays.asList(xmlCube.calculatedMembers),
777                Arrays.asList(xmlCube.namedSets),
778                memberList,
779                formulaList,
780                this,
781                true);
782        }
783    
784        /**
785         * Checks that the ordinals of measures (including calculated measures)
786         * are unique.
787         *
788         * @param cubeName        name of the cube (required for error messages)
789         * @param measures        measure list
790         */
791        private void checkOrdinals(
792            String cubeName,
793            List<RolapMember> measures)
794        {
795            Map<Integer, String> ordinals = new HashMap<Integer, String>();
796            for (RolapMember measure : measures) {
797                Integer ordinal = measure.getOrdinal();
798                if (!ordinals.containsKey(ordinal)) {
799                    ordinals.put(ordinal, measure.getUniqueName());
800                } else {
801                    throw MondrianResource.instance().MeasureOrdinalsNotUnique.ex(
802                        cubeName,
803                        ordinal.toString(),
804                        ordinals.get(ordinal),
805                        measure.getUniqueName());
806                }
807            }
808        }
809    
810        /**
811         * Adds a collection of calculated members and named sets to this cube.
812         * The members and sets can refer to each other.
813         *
814         * @param xmlCalcMembers XML objects representing members
815         * @param xmlNamedSets Array of XML definition of named set
816         * @param memberList Output list of {@link mondrian.olap.Member} objects
817         * @param formulaList Output list of {@link mondrian.olap.Formula} objects
818         * @param cube the cube that the calculated members originate from
819         * @param errOnDups throws an error if a duplicate member is found
820         */
821        private void createCalcMembersAndNamedSets(
822            List<MondrianDef.CalculatedMember> xmlCalcMembers,
823            List<MondrianDef.NamedSet> xmlNamedSets,
824            List<RolapMember> memberList,
825            List<Formula> formulaList,
826            RolapCube cube,
827            boolean errOnDups)
828        {
829            final Query queryExp =
830                resolveCalcMembers(
831                    xmlCalcMembers,
832                    xmlNamedSets,
833                    cube,
834                    errOnDups);
835            if (queryExp == null) {
836                return;
837            }
838    
839            // Now pick through the formulas.
840            Util.assertTrue(
841                queryExp.formulas.length
842                == xmlCalcMembers.size() + xmlNamedSets.size());
843            for (int i = 0; i < xmlCalcMembers.size(); i++) {
844                postCalcMember(xmlCalcMembers, i, queryExp, memberList);
845            }
846            for (int i = 0; i < xmlNamedSets.size(); i++) {
847                postNamedSet(
848                    xmlNamedSets, xmlCalcMembers.size(), i, queryExp, formulaList);
849            }
850        }
851    
852        private Query resolveCalcMembers(
853            List<MondrianDef.CalculatedMember> xmlCalcMembers,
854            List<MondrianDef.NamedSet> xmlNamedSets,
855            RolapCube cube,
856            boolean errOnDups)
857        {
858            // If there are no objects to create, our generated SQL will be so
859            // silly, the parser will laugh.
860            if (xmlCalcMembers.size() == 0 && xmlNamedSets.size() == 0) {
861                return null;
862            }
863    
864            StringBuilder buf = new StringBuilder(256);
865            buf.append("WITH").append(Util.nl);
866    
867            // Check the members individually, and generate SQL.
868            for (int i = 0; i < xmlCalcMembers.size(); i++) {
869                preCalcMember(xmlCalcMembers, i, buf, cube, errOnDups);
870            }
871    
872            // Check the named sets individually (for uniqueness) and generate SQL.
873            Set<String> nameSet = new HashSet<String>();
874            for (Formula namedSet : namedSetList) {
875                nameSet.add(namedSet.getName());
876            }
877            for (MondrianDef.NamedSet xmlNamedSet : xmlNamedSets) {
878                preNamedSet(xmlNamedSet, nameSet, buf);
879            }
880    
881            buf.append("SELECT FROM ").append(cube.getUniqueName());
882    
883            // Parse and validate this huge MDX query we've created.
884            final String queryString = buf.toString();
885            final Query queryExp;
886            try {
887                RolapConnection conn = schema.getInternalConnection();
888                queryExp = conn.parseQuery(queryString, load);
889            } catch (Exception e) {
890                throw MondrianResource.instance().UnknownNamedSetHasBadFormula.ex(
891                    getName(), e);
892            }
893            queryExp.resolve();
894            return queryExp;
895        }
896    
897        private void postNamedSet(
898            List<MondrianDef.NamedSet> xmlNamedSets,
899            final int offset,
900            int i,
901            final Query queryExp,
902            List<Formula> formulaList)
903        {
904            MondrianDef.NamedSet xmlNamedSet = xmlNamedSets.get(i);
905            Util.discard(xmlNamedSet);
906            Formula formula = queryExp.formulas[offset + i];
907            final SetBase namedSet = (SetBase) formula.getNamedSet();
908            if (xmlNamedSet.caption != null
909                && xmlNamedSet.caption.length() > 0)
910            {
911                namedSet.setCaption(xmlNamedSet.caption);
912            }
913    
914            if (xmlNamedSet.description != null
915                && xmlNamedSet.description.length() > 0)
916            {
917                namedSet.setDescription(xmlNamedSet.description);
918            }
919    
920            namedSet.setAnnotationMap(
921                RolapHierarchy.createAnnotationMap(xmlNamedSet.annotations));
922    
923            namedSetList.add(formula);
924            formulaList.add(formula);
925        }
926    
927        private void preNamedSet(
928            MondrianDef.NamedSet xmlNamedSet,
929            Set<String> nameSet,
930            StringBuilder buf)
931        {
932            if (!nameSet.add(xmlNamedSet.name)) {
933                throw MondrianResource.instance().NamedSetNotUnique.ex(
934                    xmlNamedSet.name, getName());
935            }
936    
937            buf.append("SET ")
938                .append(Util.makeFqName(xmlNamedSet.name))
939                .append(Util.nl)
940                .append(" AS ");
941            Util.singleQuoteString(xmlNamedSet.getFormula(), buf);
942            buf.append(Util.nl);
943        }
944    
945        private void postCalcMember(
946            List<MondrianDef.CalculatedMember> xmlCalcMembers,
947            int i,
948            final Query queryExp,
949            List<RolapMember> memberList)
950        {
951            MondrianDef.CalculatedMember xmlCalcMember = xmlCalcMembers.get(i);
952            final Formula formula = queryExp.formulas[i];
953    
954            calculatedMemberList.add(formula);
955    
956            Member member = formula.getMdxMember();
957    
958            Boolean visible = xmlCalcMember.visible;
959            if (visible == null) {
960                visible = Boolean.TRUE;
961            }
962            member.setProperty(Property.VISIBLE.name, visible);
963    
964            if (xmlCalcMember.caption != null
965                && xmlCalcMember.caption.length() > 0)
966            {
967                member.setProperty(
968                    Property.CAPTION.name, xmlCalcMember.caption);
969            }
970    
971            if (xmlCalcMember.description != null
972                && xmlCalcMember.description.length() > 0)
973            {
974                member.setProperty(
975                    Property.DESCRIPTION.name, xmlCalcMember.description);
976            }
977    
978            // Remove RolapCubeMember wrapper.
979            final Member member1;
980            if (member instanceof RolapCubeMember) {
981                member1 = ((RolapCubeMember) member).getRolapMember();
982            } else {
983                member1 = member;
984            }
985            ((RolapCalculatedMember) member1).setAnnotationMap(
986                RolapHierarchy.createAnnotationMap(xmlCalcMember.annotations));
987    
988            memberList.add((RolapMember) member);
989        }
990    
991        private void preCalcMember(
992            List<MondrianDef.CalculatedMember> xmlCalcMembers,
993            int j,
994            StringBuilder buf,
995            RolapCube cube,
996            boolean errOnDup)
997        {
998            MondrianDef.CalculatedMember xmlCalcMember = xmlCalcMembers.get(j);
999    
1000            // Lookup dimension
1001            final Dimension dimension =
1002                lookupDimension(
1003                    new Id.Segment(
1004                        xmlCalcMember.dimension,
1005                        Id.Quoting.UNQUOTED));
1006            if (dimension == null) {
1007                throw MondrianResource.instance().CalcMemberHasBadDimension.ex(
1008                    xmlCalcMember.dimension, xmlCalcMember.name, getName());
1009            }
1010    
1011            // If we're processing a virtual cube, it's possible that we've
1012            // already processed this calculated member because it's
1013            // referenced in another measure; in that case, remove it from the
1014            // list, since we'll add it back in later; otherwise, in the
1015            // non-virtual cube case, throw an exception
1016            for (int i = 0; i < calculatedMemberList.size(); i++) {
1017                Formula formula = calculatedMemberList.get(i);
1018                if (formula.getName().equals(xmlCalcMember.name)
1019                    && formula.getMdxMember().getDimension().getName().equals(
1020                    dimension.getName()))
1021                {
1022                    if (errOnDup) {
1023                        throw MondrianResource.instance().CalcMemberNotUnique.ex(
1024                            Util.makeFqName(dimension, xmlCalcMember.name),
1025                            getName());
1026                    } else {
1027                        calculatedMemberList.remove(i);
1028                        --i;
1029                    }
1030                }
1031            }
1032    
1033            // Check this calc member doesn't clash with one earlier in this
1034            // batch.
1035            for (int k = 0; k < j; k++) {
1036                MondrianDef.CalculatedMember xmlCalcMember2 = xmlCalcMembers.get(k);
1037                if (xmlCalcMember2.name.equals(xmlCalcMember.name)
1038                    && xmlCalcMember2.dimension.equals(xmlCalcMember.dimension))
1039                {
1040                    throw MondrianResource.instance().CalcMemberNotUnique.ex(
1041                        Util.makeFqName(dimension, xmlCalcMember.name),
1042                        getName());
1043                }
1044            }
1045    
1046            final String memberUniqueName = Util.makeFqName(
1047                    dimension.getUniqueName(), xmlCalcMember.name);
1048            final MondrianDef.CalculatedMemberProperty[] xmlProperties =
1049                    xmlCalcMember.memberProperties;
1050            List<String> propNames = new ArrayList<String>();
1051            List<String> propExprs = new ArrayList<String>();
1052            validateMemberProps(
1053                xmlProperties, propNames, propExprs, xmlCalcMember.name);
1054    
1055            final int measureCount =
1056                cube.measuresHierarchy.getMemberReader().getMemberCount();
1057    
1058            // Generate SQL.
1059            assert memberUniqueName.startsWith("[");
1060            buf.append("MEMBER ").append(memberUniqueName)
1061                    .append(Util.nl)
1062                    .append("  AS ");
1063            Util.singleQuoteString(xmlCalcMember.getFormula(), buf);
1064    
1065            assert propNames.size() == propExprs.size();
1066            processFormatStringAttribute(xmlCalcMember, buf);
1067    
1068            for (int i = 0; i < propNames.size(); i++) {
1069                String name = propNames.get(i);
1070                String expr = propExprs.get(i);
1071                buf.append(",").append(Util.nl);
1072                expr = removeSurroundingQuotesIfNumericProperty(name, expr);
1073                buf.append(name).append(" = ").append(expr);
1074            }
1075            // Flag that the calc members are defined against a cube; will
1076            // determine the value of Member.isCalculatedInQuery
1077            buf.append(",")
1078                .append(Util.nl);
1079            Util.quoteMdxIdentifier(Property.MEMBER_SCOPE.name, buf);
1080            buf.append(" = 'CUBE'");
1081    
1082            // Assign the member an ordinal higher than all of the stored measures.
1083            if (!propNames.contains(Property.MEMBER_ORDINAL.getName())) {
1084                buf.append(",")
1085                    .append(Util.nl)
1086                    .append(Property.MEMBER_ORDINAL)
1087                    .append(" = ")
1088                    .append(measureCount + j);
1089            }
1090            buf.append(Util.nl);
1091        }
1092    
1093        private String removeSurroundingQuotesIfNumericProperty(
1094            String name,
1095            String expr)
1096        {
1097            Property prop = Property.lookup(name, false);
1098            if (prop != null
1099                && prop.getType() == Property.Datatype.TYPE_NUMERIC
1100                && isSurroundedWithQuotes(expr)
1101                && expr.length() > 2)
1102            {
1103                return expr.substring(1, expr.length() - 1);
1104            }
1105            return expr;
1106        }
1107    
1108        private boolean isSurroundedWithQuotes(String expr) {
1109            return expr.startsWith("\"") && expr.endsWith("\"");
1110        }
1111    
1112        void processFormatStringAttribute(
1113            MondrianDef.CalculatedMember xmlCalcMember,
1114            StringBuilder buf)
1115        {
1116            if (xmlCalcMember.formatString != null) {
1117                buf.append(",")
1118                    .append(Util.nl)
1119                    .append(Property.FORMAT_STRING.name)
1120                    .append(" = ")
1121                    .append(Util.quoteForMdx(xmlCalcMember.formatString));
1122            }
1123        }
1124    
1125        /**
1126         * Validates an array of member properties, and populates a list of names
1127         * and expressions, one for each property.
1128         *
1129         * @param xmlProperties Array of property definitions.
1130         * @param propNames Output array of property names.
1131         * @param propExprs Output array of property expressions.
1132         * @param memberName Name of member which the properties belong to.
1133         */
1134        private void validateMemberProps(
1135            final MondrianDef.CalculatedMemberProperty[] xmlProperties,
1136            List<String> propNames,
1137            List<String> propExprs,
1138            String memberName)
1139        {
1140            if (xmlProperties == null) {
1141                return;
1142            }
1143            for (MondrianDef.CalculatedMemberProperty xmlProperty : xmlProperties) {
1144                if (xmlProperty.expression == null && xmlProperty.value == null) {
1145                    throw MondrianResource.instance()
1146                        .NeitherExprNorValueForCalcMemberProperty.ex(
1147                            xmlProperty.name, memberName, getName());
1148                }
1149                if (xmlProperty.expression != null && xmlProperty.value != null) {
1150                    throw MondrianResource.instance().ExprAndValueForMemberProperty
1151                        .ex(
1152                            xmlProperty.name, memberName, getName());
1153                }
1154                propNames.add(xmlProperty.name);
1155                if (xmlProperty.expression != null) {
1156                    propExprs.add(xmlProperty.expression);
1157                } else {
1158                    propExprs.add(Util.quoteForMdx(xmlProperty.value));
1159                }
1160            }
1161        }
1162    
1163        public RolapSchema getSchema() {
1164            return schema;
1165        }
1166    
1167        /**
1168         * Returns the named sets of this cube.
1169         */
1170        public NamedSet[] getNamedSets() {
1171            NamedSet[] namedSetsArray = new NamedSet[namedSetList.size()];
1172            for (int i = 0; i < namedSetList.size(); i++) {
1173                namedSetsArray[i] = namedSetList.get(i).getNamedSet();
1174            }
1175            return namedSetsArray;
1176        }
1177    
1178        /**
1179         * Returns the schema reader which enforces the appropriate access-control
1180         * context. schemaReader is cached, and needs to stay in sync with
1181         * any changes to the cube.
1182         *
1183         * @post return != null
1184         * @see #getSchemaReader(Role)
1185         */
1186        public synchronized SchemaReader getSchemaReader() {
1187            if (schemaReader == null) {
1188                RoleImpl schemaDefaultRoleImpl = schema.getDefaultRole();
1189                RoleImpl roleImpl = schemaDefaultRoleImpl.makeMutableClone();
1190                roleImpl.grant(this, Access.ALL);
1191                schemaReader = new RolapCubeSchemaReader(roleImpl);
1192            }
1193            return schemaReader;
1194        }
1195    
1196        public SchemaReader getSchemaReader(Role role) {
1197            if (role == null) {
1198                return getSchemaReader();
1199            } else {
1200                return new RolapCubeSchemaReader(role);
1201            }
1202        }
1203    
1204        MondrianDef.CubeDimension lookup(
1205            MondrianDef.CubeDimension[] xmlDimensions,
1206            String name)
1207        {
1208            for (MondrianDef.CubeDimension cd : xmlDimensions) {
1209                if (name.equals(cd.name)) {
1210                    return cd;
1211                }
1212            }
1213            // TODO: this ought to be a fatal error.
1214            return null;
1215        }
1216    
1217        private void init(MondrianDef.CubeDimension[] xmlDimensions) {
1218            for (Dimension dimension1 : dimensions) {
1219                final RolapDimension dimension = (RolapDimension) dimension1;
1220                dimension.init(lookup(xmlDimensions, dimension.getName()));
1221            }
1222            register();
1223        }
1224    
1225        private void register() {
1226            if (isVirtual()) {
1227                return;
1228            }
1229            List<RolapBaseCubeMeasure> storedMeasures =
1230                new ArrayList<RolapBaseCubeMeasure>();
1231            for (Member measure : getMeasures()) {
1232                if (measure instanceof RolapBaseCubeMeasure) {
1233                    storedMeasures.add((RolapBaseCubeMeasure) measure);
1234                }
1235            }
1236    
1237            RolapStar star = getStar();
1238            RolapStar.Table table = star.getFactTable();
1239    
1240            // create measures (and stars for them, if necessary)
1241            for (RolapBaseCubeMeasure storedMeasure : storedMeasures) {
1242                table.makeMeasure(storedMeasure);
1243            }
1244        }
1245    
1246        /**
1247         * Returns true if this Cube is either virtual or if the Cube's
1248         * RolapStar is caching aggregates.
1249         *
1250         * @return Whether this Cube's RolapStar should cache aggregations
1251         */
1252        public boolean isCacheAggregations() {
1253            return isVirtual() || star.isCacheAggregations();
1254        }
1255    
1256        /**
1257         * Set if this (non-virtual) Cube's RolapStar should cache
1258         * aggregations.
1259         *
1260         * @param cache Whether this Cube's RolapStar should cache aggregations
1261         */
1262        public void setCacheAggregations(boolean cache) {
1263            if (! isVirtual()) {
1264                star.setCacheAggregations(cache);
1265            }
1266        }
1267    
1268        /**
1269         * Clear the in memory aggregate cache associated with this Cube, but
1270         * only if Disabling Caching has been enabled.
1271         */
1272        public void clearCachedAggregations() {
1273            clearCachedAggregations(false);
1274        }
1275    
1276        /**
1277         * Clear the in memory aggregate cache associated with this Cube.
1278         */
1279        public void clearCachedAggregations(boolean forced) {
1280            if (isVirtual()) {
1281                // TODO:
1282                // Currently a virtual cube does not keep a list of all of its
1283                // base cubes, so we need to iterate through each and flush
1284                // the ones that should be flushed. Could use a CacheControl
1285                // method here.
1286                for (RolapStar star1 : schema.getStars()) {
1287                    // this will only flush the star's aggregate cache if
1288                    // 1) DisableCaching is true or 2) the star's cube has
1289                    // cacheAggregations set to false in the schema.
1290                    star1.clearCachedAggregations(forced);
1291                }
1292            } else {
1293                star.clearCachedAggregations(forced);
1294            }
1295        }
1296    
1297        /**
1298         * Check if there are modifications in the aggregations cache
1299         */
1300        public void checkAggregateModifications() {
1301            if (isVirtual()) {
1302                // TODO:
1303                // Currently a virtual cube does not keep a list of all of its
1304                // base cubes, so we need to iterate through each and flush
1305                // the ones that should be flushed
1306                schema.checkAggregateModifications();
1307            } else {
1308                star.checkAggregateModifications();
1309            }
1310        }
1311        /**
1312         * Push all modifications of the aggregations to global cache,
1313         * so other queries can start using the new cache
1314         */
1315        public void pushAggregateModificationsToGlobalCache() {
1316            if (isVirtual()) {
1317                // TODO:
1318                // Currently a virtual cube does not keep a list of all of its
1319                // base cubes, so we need to iterate through each and flush
1320                // the ones that should be flushed
1321                schema.pushAggregateModificationsToGlobalCache();
1322            } else {
1323                star.pushAggregateModificationsToGlobalCache();
1324            }
1325        }
1326    
1327    
1328    
1329        /**
1330         * Returns this cube's underlying star schema.
1331         */
1332        public RolapStar getStar() {
1333            return star;
1334        }
1335    
1336        private void createUsages(
1337            RolapCubeDimension dimension,
1338            MondrianDef.CubeDimension xmlCubeDimension)
1339        {
1340            // RME level may not be in all hierarchies
1341            // If one uses the DimensionUsage attribute "level", which level
1342            // in a hierarchy to join on, and there is more than one hierarchy,
1343            // then a HierarchyUsage can not be created for the hierarchies
1344            // that do not have the level defined.
1345            RolapCubeHierarchy[] hierarchies =
1346                (RolapCubeHierarchy[]) dimension.getHierarchies();
1347    
1348            if (hierarchies.length == 1) {
1349                // Only one, so let lower level error checking handle problems
1350                createUsage(hierarchies[0], xmlCubeDimension);
1351    
1352            } else if ((xmlCubeDimension instanceof MondrianDef.DimensionUsage)
1353                && (((MondrianDef.DimensionUsage) xmlCubeDimension).level
1354                    != null))
1355            {
1356                // More than one, make sure if we are joining by level, that
1357                // at least one hierarchy can and those that can not are
1358                // not registered
1359                MondrianDef.DimensionUsage du =
1360                    (MondrianDef.DimensionUsage) xmlCubeDimension;
1361    
1362                int cnt = 0;
1363    
1364                for (RolapCubeHierarchy hierarchy : hierarchies) {
1365                    if (getLogger().isDebugEnabled()) {
1366                        getLogger().debug(
1367                            "RolapCube<init>: hierarchy="
1368                            + hierarchy.getName());
1369                    }
1370                    RolapLevel joinLevel = (RolapLevel)
1371                        Util.lookupHierarchyLevel(hierarchy, du.level);
1372                    if (joinLevel == null) {
1373                        continue;
1374                    }
1375                    createUsage(hierarchy, xmlCubeDimension);
1376                    cnt++;
1377                }
1378    
1379                if (cnt == 0) {
1380                    // None of the hierarchies had the level, let lower level
1381                    // detect and throw error
1382                    createUsage(hierarchies[0], xmlCubeDimension);
1383                }
1384    
1385            } else {
1386                // just do it
1387                for (RolapCubeHierarchy hierarchy : hierarchies) {
1388                    if (getLogger().isDebugEnabled()) {
1389                        getLogger().debug(
1390                            "RolapCube<init>: hierarchy="
1391                            + hierarchy.getName());
1392                    }
1393                    createUsage(hierarchy, xmlCubeDimension);
1394                }
1395            }
1396        }
1397    
1398        synchronized void createUsage(
1399            RolapCubeHierarchy hierarchy,
1400            MondrianDef.CubeDimension cubeDim)
1401        {
1402            HierarchyUsage usage = new HierarchyUsage(this, hierarchy, cubeDim);
1403            if (LOGGER.isDebugEnabled()) {
1404                LOGGER.debug(
1405                    "RolapCube.createUsage: "
1406                    + "cube=" + getName()
1407                    + ", hierarchy=" + hierarchy.getName()
1408                    + ", usage=" + usage);
1409            }
1410            for (HierarchyUsage hierUsage : hierarchyUsages) {
1411                if (hierUsage.equals(usage)) {
1412                    getLogger().warn(
1413                        "RolapCube.createUsage: duplicate " + hierUsage);
1414                    return;
1415                }
1416            }
1417            if (getLogger().isDebugEnabled()) {
1418                getLogger().debug("RolapCube.createUsage: register " + usage);
1419            }
1420            this.hierarchyUsages.add(usage);
1421        }
1422    
1423        private synchronized HierarchyUsage getUsageByName(String name) {
1424            for (HierarchyUsage hierUsage : hierarchyUsages) {
1425                if (hierUsage.getFullName().equals(name)) {
1426                    return hierUsage;
1427                }
1428            }
1429            return null;
1430        }
1431    
1432        /**
1433         * A Hierarchy may have one or more HierarchyUsages. This method returns
1434         * an array holding the one or more usages associated with a Hierarchy.
1435         * The HierarchyUsages hierarchyName attribute always equals the name
1436         * attribute of the Hierarchy.
1437         *
1438         * @param hierarchy Hierarchy
1439         * @return an HierarchyUsages array with 0 or more members.
1440         */
1441        public synchronized HierarchyUsage[] getUsages(Hierarchy hierarchy) {
1442            String name = hierarchy.getName();
1443            if (!name.equals(hierarchy.getDimension().getName())
1444                && MondrianProperties.instance().SsasCompatibleNaming.get())
1445            {
1446                name = hierarchy.getDimension().getName() + "." + name;
1447            }
1448            if (getLogger().isDebugEnabled()) {
1449                getLogger().debug("RolapCube.getUsages: name=" + name);
1450            }
1451    
1452            HierarchyUsage hierUsage = null;
1453            List<HierarchyUsage> list = null;
1454    
1455            for (HierarchyUsage hu : hierarchyUsages) {
1456                if (hu.getHierarchyName().equals(name)) {
1457                    if (list != null) {
1458                        if (getLogger().isDebugEnabled()) {
1459                            getLogger().debug(
1460                                "RolapCube.getUsages: "
1461                                + "add list HierarchyUsage.name=" + hu.getName());
1462                        }
1463                        list.add(hu);
1464                    } else if (hierUsage == null) {
1465                        hierUsage = hu;
1466                    } else {
1467                        list = new ArrayList<HierarchyUsage>();
1468                        if (getLogger().isDebugEnabled()) {
1469                            getLogger().debug(
1470                                "RolapCube.getUsages: "
1471                                + "add list hierUsage.name="
1472                                + hierUsage.getName()
1473                                + ", hu.name="
1474                                + hu.getName());
1475                        }
1476                        list.add(hierUsage);
1477                        list.add(hu);
1478                        hierUsage = null;
1479                    }
1480                }
1481            }
1482            if (hierUsage != null) {
1483                return new HierarchyUsage[] { hierUsage };
1484            } else if (list != null) {
1485                if (getLogger().isDebugEnabled()) {
1486                    getLogger().debug("RolapCube.getUsages: return list");
1487                }
1488                return list.toArray(new HierarchyUsage[list.size()]);
1489            } else {
1490                return new HierarchyUsage[0];
1491            }
1492        }
1493    
1494        synchronized HierarchyUsage getFirstUsage(Hierarchy hier) {
1495            HierarchyUsage hierarchyUsage = firstUsageMap.get(hier);
1496            if (hierarchyUsage == null) {
1497                HierarchyUsage[] hierarchyUsages = getUsages(hier);
1498                if (hierarchyUsages.length != 0) {
1499                    hierarchyUsage = hierarchyUsages[0];
1500                    firstUsageMap.put(hier, hierarchyUsage);
1501                }
1502            }
1503            return hierarchyUsage;
1504        }
1505    
1506        /**
1507         * Looks up all of the HierarchyUsages with the same "source" returning
1508         * an array of HierarchyUsage of length 0 or more.
1509         *
1510         * This method is currently only called if an error occurs in lookupChild(),
1511         * so that more information can be displayed in the error log.
1512         *
1513         * @param source Name of shared dimension
1514         * @return array of HierarchyUsage (HierarchyUsage[]) - never null.
1515         */
1516        private synchronized HierarchyUsage[] getUsagesBySource(String source) {
1517            if (getLogger().isDebugEnabled()) {
1518                getLogger().debug("RolapCube.getUsagesBySource: source=" + source);
1519            }
1520    
1521            HierarchyUsage hierUsage = null;
1522            List<HierarchyUsage> list = null;
1523    
1524            for (HierarchyUsage hu : hierarchyUsages) {
1525                String s = hu.getSource();
1526                if ((s != null) && s.equals(source)) {
1527                    if (list != null) {
1528                        if (getLogger().isDebugEnabled()) {
1529                            getLogger().debug(
1530                                "RolapCube.getUsagesBySource: "
1531                                + "add list HierarchyUsage.name="
1532                                + hu.getName());
1533                        }
1534                        list.add(hu);
1535                    } else if (hierUsage == null) {
1536                        hierUsage = hu;
1537                    } else {
1538                        list = new ArrayList<HierarchyUsage>();
1539                        if (getLogger().isDebugEnabled()) {
1540                            getLogger().debug(
1541                                "RolapCube.getUsagesBySource: "
1542                                + "add list hierUsage.name="
1543                                + hierUsage.getName()
1544                                + ", hu.name="
1545                                + hu.getName());
1546                        }
1547                        list.add(hierUsage);
1548                        list.add(hu);
1549                        hierUsage = null;
1550                    }
1551                }
1552            }
1553            if (hierUsage != null) {
1554                return new HierarchyUsage[] { hierUsage };
1555            } else if (list != null) {
1556                if (getLogger().isDebugEnabled()) {
1557                    getLogger().debug("RolapCube.getUsagesBySource: return list");
1558                }
1559                return list.toArray(new HierarchyUsage[list.size()]);
1560            } else {
1561                return new HierarchyUsage[0];
1562            }
1563        }
1564    
1565    
1566        /**
1567         * Understand this and you are no longer a novice.
1568         *
1569         * @param dimension Dimension
1570         */
1571        void registerDimension(RolapCubeDimension dimension) {
1572            RolapStar star = getStar();
1573    
1574            Hierarchy[] hierarchies = dimension.getHierarchies();
1575    
1576            for (Hierarchy hierarchy1 : hierarchies) {
1577                RolapHierarchy hierarchy = (RolapHierarchy) hierarchy1;
1578    
1579                MondrianDef.RelationOrJoin relation = hierarchy.getRelation();
1580                if (relation == null) {
1581                    continue; // e.g. [Measures] hierarchy
1582                }
1583                RolapCubeLevel[] levels = (RolapCubeLevel[]) hierarchy.getLevels();
1584    
1585                HierarchyUsage[] hierarchyUsages = getUsages(hierarchy);
1586                if (hierarchyUsages.length == 0) {
1587                    if (getLogger().isDebugEnabled()) {
1588                        StringBuilder buf = new StringBuilder(64);
1589                        buf.append("RolapCube.registerDimension: ");
1590                        buf.append("hierarchyUsages == null for cube=\"");
1591                        buf.append(this.name);
1592                        buf.append("\", hierarchy=\"");
1593                        buf.append(hierarchy.getName());
1594                        buf.append("\"");
1595                        getLogger().debug(buf.toString());
1596                    }
1597                    continue;
1598                }
1599    
1600                for (HierarchyUsage hierarchyUsage : hierarchyUsages) {
1601                    String usagePrefix = hierarchyUsage.getUsagePrefix();
1602                    RolapStar.Table table = star.getFactTable();
1603    
1604                    String levelName = hierarchyUsage.getLevelName();
1605    
1606                    // RME
1607                    // If a DimensionUsage has its level attribute set, then
1608                    // one wants joins to occur at that level and not below (not
1609                    // at a finer level), i.e., if you have levels: Year, Quarter,
1610                    // Month, and Day, and the level attribute is set to Month, the
1611                    // you do not want aggregate joins to include the Day level.
1612                    // By default, it is the lowest level that the fact table
1613                    // joins to, the Day level.
1614                    // To accomplish this, we reorganize the relation and then
1615                    // copy it (so that elsewhere the original relation can
1616                    // still be used), and finally, clip off those levels below
1617                    // the DimensionUsage level attribute.
1618                    // Note also, if the relation (MondrianDef.Relation) is not
1619                    // a MondrianDef.Join, i.e., the dimension is not a snowflake,
1620                    // there is a single dimension table, then this is currently
1621                    // an unsupported configuation and all bets are off.
1622                    if (relation instanceof MondrianDef.Join) {
1623                        // RME
1624                        // take out after things seem to be working
1625                        MondrianDef.RelationOrJoin relationTmp1 = relation;
1626    
1627                        relation = reorder(relation, levels);
1628    
1629                        if (relation == null && getLogger().isDebugEnabled()) {
1630                            getLogger().debug(
1631                                "RolapCube.registerDimension: after reorder relation==null");
1632                            getLogger().debug(
1633                                "RolapCube.registerDimension: reorder relationTmp1="
1634                                    + format(relationTmp1));
1635                        }
1636                    }
1637    
1638                    MondrianDef.RelationOrJoin relationTmp2 = relation;
1639    
1640                    if (levelName != null) {
1641                        // When relation is a table, this does nothing. Otherwise
1642                        // it tries to arrange the joins so that the fact table
1643                        // in the RolapStar will be joining at the lowest level.
1644                        //
1645    
1646                        // Make sure the level exists
1647                        RolapLevel level =
1648                            RolapLevel.lookupLevel(levels, levelName);
1649                        if (level == null) {
1650                            StringBuilder buf = new StringBuilder(64);
1651                            buf.append("For cube \"");
1652                            buf.append(getName());
1653                            buf.append("\" and HierarchyUsage [");
1654                            buf.append(hierarchyUsage);
1655                            buf.append("], there is no level with given");
1656                            buf.append(" level name \"");
1657                            buf.append(levelName);
1658                            buf.append("\"");
1659                            throw Util.newInternal(buf.toString());
1660                        }
1661    
1662                        // If level has child, not the lowest level, then snip
1663                        // relation between level and its child so that
1664                        // joins do not include the lower levels.
1665                        // If the child level is null, then the DimensionUsage
1666                        // level attribute was simply set to the default, lowest
1667                        // level and we do nothing.
1668                        if (relation instanceof MondrianDef.Join) {
1669                            RolapLevel childLevel =
1670                                (RolapLevel) level.getChildLevel();
1671                            if (childLevel != null) {
1672                                String tableName = childLevel.getTableName();
1673                                if (tableName != null) {
1674                                    relation = snip(relation, tableName);
1675    
1676                                    if (relation == null
1677                                        && getLogger().isDebugEnabled())
1678                                    {
1679                                        getLogger().debug(
1680                                            "RolapCube.registerDimension: after snip relation==null");
1681                                        getLogger().debug(
1682                                            "RolapCube.registerDimension: snip relationTmp2="
1683                                            + format(relationTmp2));
1684                                    }
1685                                }
1686                            }
1687                        }
1688                    }
1689    
1690                    // cube and dimension usage are in different tables
1691                    if (!relation.equals(table.getRelation())) {
1692                        // HierarchyUsage should have checked this.
1693                        if (hierarchyUsage.getForeignKey() == null) {
1694                            throw MondrianResource.instance()
1695                                .HierarchyMustHaveForeignKey.ex(
1696                                    hierarchy.getName(), getName());
1697                        }
1698                        // jhyde: check is disabled until we handle <View> correctly
1699                        if (false
1700                            && !star.getFactTable().containsColumn(
1701                                hierarchyUsage.getForeignKey()))
1702                        {
1703                            throw MondrianResource.instance()
1704                                .HierarchyInvalidForeignKey.ex(
1705                                    hierarchyUsage.getForeignKey(),
1706                                    hierarchy.getName(),
1707                                    getName());
1708                        }
1709                        // parameters:
1710                        //   fact table,
1711                        //   fact table foreign key,
1712                        MondrianDef.Column column =
1713                            new MondrianDef.Column(
1714                                table.getAlias(),
1715                                hierarchyUsage.getForeignKey());
1716                        // parameters:
1717                        //   left column
1718                        //   right column
1719                        RolapStar.Condition joinCondition =
1720                            new RolapStar.Condition(
1721                                column,
1722                                hierarchyUsage.getJoinExp());
1723    
1724                        // (rchen) potential bug?:
1725                        // FACT table joins with tables in a hierarchy in the
1726                        // order they appear in the schema definition, even though
1727                        // the primary key for this hierarchy can be on a table
1728                        // which is not the leftmost.
1729                        // e.g.
1730                        //
1731                        // <Dimension name="Product">
1732                        // <Hierarchy hasAll="true" primaryKey="product_id"
1733                        //    primaryKeyTable="product">
1734                        //  <Join
1735                        //      leftKey="product_class_id"
1736                        //      rightKey="product_class_id">
1737                        //    <Table name="product_class"/>
1738                        //    <Table name="product"/>
1739                        //  </Join>
1740                        // </Hierarchy>
1741                        // </Dimension>
1742                        //
1743                        // When this hierarchy is referenced in a cube, the fact
1744                        // table is joined with the dimension tables using this
1745                        // incorrect join condition which assumes the leftmost
1746                        // table produces the primaryKey:
1747                        //   "fact"."foreignKey" = "product_class"."product_id"
1748    
1749                        table = table.addJoin(this, relation, joinCondition);
1750                    }
1751    
1752                    // The parent Column is used so that non-shared dimensions
1753                    // which use the fact table (not a separate dimension table)
1754                    // can keep a record of what other columns are in the
1755                    // same set of levels.
1756                    RolapStar.Column parentColumn = null;
1757    
1758                    //RME
1759                    // If the level name is not null, then we need only register
1760                    // those columns for that level and above.
1761                    if (levelName != null) {
1762                        for (RolapCubeLevel level : levels) {
1763                            if (level.getKeyExp() != null) {
1764                                parentColumn = makeColumns(
1765                                    table, level, parentColumn, usagePrefix);
1766                            }
1767                            if (levelName.equals(level.getName())) {
1768                                break;
1769                            }
1770                        }
1771                    } else {
1772                        // This is the normal case, no level attribute so register
1773                        // all columns.
1774                        for (RolapCubeLevel level : levels) {
1775                            if (level.getKeyExp() != null) {
1776                                parentColumn = makeColumns(
1777                                    table, level, parentColumn, usagePrefix);
1778                            }
1779                        }
1780                    }
1781                }
1782            }
1783        }
1784    
1785        /**
1786         * Adds a column to the appropriate table in the {@link RolapStar}.
1787         * Note that if the RolapLevel has a table attribute, then the associated
1788         * column needs to be associated with that table.
1789         */
1790        protected RolapStar.Column makeColumns(
1791            RolapStar.Table table,
1792            RolapCubeLevel level,
1793            RolapStar.Column parentColumn,
1794            String usagePrefix)
1795        {
1796            // If there is a table name, then first see if the table name is the
1797            // table parameter's name or alias and, if so, simply add the column
1798            // to that table. On the other hand, find the ancestor of the table
1799            // parameter and if found, then associate the new column with
1800            // that table.
1801            //
1802            // Lastly, if the ancestor can not be found, i.e., there is no table
1803            // with the level's table name, what to do.  Here we simply punt and
1804            // associated the new column with the table parameter which might
1805            // be an error. We do issue a warning in any case.
1806            String tableName = level.getTableName();
1807            if (tableName != null) {
1808                if (table.getAlias().equals(tableName)) {
1809                    parentColumn = table.makeColumns(
1810                        this, level, parentColumn, usagePrefix);
1811                } else if (table.equalsTableName(tableName)) {
1812                    parentColumn = table.makeColumns(
1813                        this, level, parentColumn, usagePrefix);
1814                } else {
1815                    RolapStar.Table t = table.findAncestor(tableName);
1816                    if (t != null) {
1817                        parentColumn = t.makeColumns(
1818                            this, level, parentColumn, usagePrefix);
1819                    } else {
1820                        // Issue warning and keep going.
1821                        getLogger().warn(
1822                            "RolapCube.makeColumns: for cube \""
1823                            + getName()
1824                            + "\" the Level \""
1825                            + level.getName()
1826                            + "\" has a table name attribute \""
1827                            + tableName
1828                            + "\" but the associated RolapStar does not"
1829                            + " have a table with that name.");
1830    
1831                        parentColumn = table.makeColumns(
1832                            this, level, parentColumn, usagePrefix);
1833                    }
1834                }
1835            } else {
1836                // level's expr is not a MondrianDef.Column (this is used by tests)
1837                // or there is no table name defined
1838                parentColumn = table.makeColumns(
1839                    this, level, parentColumn, usagePrefix);
1840            }
1841    
1842            return parentColumn;
1843        }
1844    
1845        // The following code deals with handling the DimensionUsage level attribute
1846        // and snowflake dimensions only.
1847    
1848        /**
1849         * Formats a {@link mondrian.olap.MondrianDef.RelationOrJoin}, indenting
1850         * joins for readability.
1851         *
1852         * @param relation
1853         */
1854        private static String format(MondrianDef.RelationOrJoin relation) {
1855            StringBuilder buf = new StringBuilder();
1856            format(relation, buf, "");
1857            return buf.toString();
1858        }
1859    
1860        private static void format(
1861            MondrianDef.RelationOrJoin relation,
1862            StringBuilder buf,
1863            String indent)
1864        {
1865            if (relation instanceof MondrianDef.Table) {
1866                MondrianDef.Table table = (MondrianDef.Table) relation;
1867    
1868                buf.append(indent);
1869                buf.append(table.name);
1870                if (table.alias != null) {
1871                    buf.append('(');
1872                    buf.append(table.alias);
1873                    buf.append(')');
1874                }
1875                buf.append(Util.nl);
1876            } else {
1877                MondrianDef.Join join = (MondrianDef.Join) relation;
1878                String subindent = indent + "  ";
1879    
1880                buf.append(indent);
1881                //buf.append(join.leftAlias);
1882                buf.append(join.getLeftAlias());
1883                buf.append('.');
1884                buf.append(join.leftKey);
1885                buf.append('=');
1886                buf.append(join.getRightAlias());
1887                //buf.append(join.rightAlias);
1888                buf.append('.');
1889                buf.append(join.rightKey);
1890                buf.append(Util.nl);
1891                format(join.left, buf, subindent);
1892                format(join.right, buf, indent);
1893            }
1894        }
1895    
1896        /**
1897         * This method tells us if unrelated dimensions to measures from
1898         * the input base cube should be pushed to default member or not
1899         * during aggregation.
1900         * @param baseCubeName name of the base cube for which we want
1901         * to check this property
1902         * @return boolean
1903         */
1904        public boolean shouldIgnoreUnrelatedDimensions(String baseCubeName) {
1905            return cubeUsages != null
1906                && cubeUsages.shouldIgnoreUnrelatedDimensions(baseCubeName);
1907        }
1908    
1909        /**
1910         * Returns a list of all hierarchies in this cube, in order of dimension.
1911         *
1912         * <p>TODO: Make this method return RolapCubeHierarchy, when the measures
1913         * hierarchy is a RolapCubeHierarchy.
1914         *
1915         * @return List of hierarchies
1916         */
1917        public List<RolapHierarchy> getHierarchies() {
1918            return hierarchyList;
1919        }
1920    
1921        public boolean isLoadInProgress() {
1922            return loadInProgress
1923                || getSchema().getSchemaLoadDate() == null;
1924        }
1925    
1926        /**
1927         * Association between a MondrianDef.Table with its associated
1928         * level's depth. This is used to rank tables in a snowflake so that
1929         * the table with the lowest rank, level depth, is furthest from
1930         * the base fact table in the RolapStar.
1931         */
1932        private static class RelNode {
1933    
1934            /**
1935             * Finds a RelNode by table name or, if that fails, by table alias
1936             * from a map of RelNodes.
1937             *
1938             * @param table
1939             * @param map
1940             */
1941            private static RelNode lookup(
1942                MondrianDef.Relation table,
1943                Map<String, RelNode> map)
1944            {
1945                RelNode relNode;
1946                if (table instanceof MondrianDef.Table) {
1947                    relNode = map.get(((MondrianDef.Table) table).name);
1948                    if (relNode != null) {
1949                        return relNode;
1950                    }
1951                }
1952                return map.get(table.getAlias());
1953            }
1954    
1955            private int depth;
1956            private String alias;
1957            private MondrianDef.Relation table;
1958    
1959            RelNode(String alias, int depth) {
1960                this.alias = alias;
1961                this.depth = depth;
1962            }
1963        }
1964    
1965        /**
1966         * Attempts to transform a {@link mondrian.olap.MondrianDef.RelationOrJoin}
1967         * into the "canonical" form.
1968         *
1969         * <p>What is the canonical form? It is only relevant
1970         * when the relation is a snowflake (nested joins), not simply a table.
1971         * The canonical form has lower levels to the left of higher levels (Day
1972         * before Month before Quarter before Year) and the nested joins are always
1973         * on the right side of the parent join.
1974         *
1975         * <p>The canonical form is (using a Time dimension example):
1976         * <pre>
1977         *            |
1978         *    ----------------
1979         *    |             |
1980         *   Day      --------------
1981         *            |            |
1982         *          Month      ---------
1983         *                     |       |
1984         *                   Quarter  Year
1985         * </pre>
1986         * <p>
1987         * When the relation looks like the above, then the fact table joins to the
1988         * lowest level table (the Day table) which joins to the next level (the
1989         * Month table) which joins to the next (the Quarter table) which joins to
1990         * the top level table (the Year table).
1991         * <p>
1992         * This method supports the transformation of a subset of all possible
1993         * join/table relation trees (and anyone who whats to generalize it is
1994         * welcome to). It will take any of the following and convert them to
1995         * the canonical.
1996         * <pre>
1997         *            |
1998         *    ----------------
1999         *    |             |
2000         *   Year     --------------
2001         *            |            |
2002         *         Quarter     ---------
2003         *                     |       |
2004         *                   Month    Day
2005         *
2006         *                  |
2007         *           ----------------
2008         *           |              |
2009         *        --------------   Year
2010         *        |            |
2011         *    ---------     Quarter
2012         *    |       |
2013         *   Day     Month
2014         *
2015         *                  |
2016         *           ----------------
2017         *           |              |
2018         *        --------------   Day
2019         *        |            |
2020         *    ---------      Month
2021         *    |       |
2022         *   Year   Quarter
2023         *
2024         *            |
2025         *    ----------------
2026         *    |             |
2027         *   Day      --------------
2028         *            |            |
2029         *          Month      ---------
2030         *                     |       |
2031         *                   Quarter  Year
2032         *
2033         * </pre>
2034         * <p>
2035         * In addition, at any join node, it can exchange the left and right
2036         * child relations so that the lower level depth is to the left.
2037         * For example, it can also transform the following:
2038         * <pre>
2039         *                |
2040         *         ----------------
2041         *         |              |
2042         *      --------------   Day
2043         *      |            |
2044         *    Month     ---------
2045         *              |       |
2046         *             Year   Quarter
2047         * </pre>
2048         * <p>
2049         * What it can not handle are cases where on both the left and right side of
2050         * a join there are child joins:
2051         * <pre>
2052         *                |
2053         *         ----------------
2054         *         |              |
2055         *      ---------     ----------
2056         *      |       |     |        |
2057         *    Month    Day   Year    Quarter
2058         *
2059         *                |
2060         *         ----------------
2061         *         |              |
2062         *      ---------     ----------
2063         *      |       |     |        |
2064         *    Year     Day   Month   Quarter
2065         * </pre>
2066         * <p>
2067         * When does this method do nothing? 1) when there are less than 2 levels,
2068         * 2) when any level does not have a table name, and 3) when for every table
2069         * in the relation there is not a level. In these cases, this method simply
2070         * return the original relation.
2071         *
2072         * @param relation
2073         * @param levels
2074         */
2075        private static MondrianDef.RelationOrJoin reorder(
2076            MondrianDef.RelationOrJoin relation,
2077            RolapLevel[] levels)
2078        {
2079            // Need at least two levels, with only one level theres nothing to do.
2080            if (levels.length < 2) {
2081                return relation;
2082            }
2083    
2084            Map<String, RelNode> nodeMap = new HashMap<String, RelNode>();
2085    
2086            // Create RelNode in top down order (year -> day)
2087            for (int i = 0; i < levels.length; i++) {
2088                RolapLevel level = levels[i];
2089    
2090                if (level.isAll()) {
2091                    continue;
2092                }
2093    
2094                // this is the table alias
2095                String tableName = level.getTableName();
2096                if (tableName == null) {
2097                    // punt, no table name
2098                    return relation;
2099                }
2100                RelNode rnode = new RelNode(tableName, i);
2101                nodeMap.put(tableName, rnode);
2102            }
2103            if (! validateNodes(relation, nodeMap)) {
2104                return relation;
2105            }
2106            relation = copy(relation);
2107    
2108            // Put lower levels to the left of upper levels
2109            leftToRight(relation, nodeMap);
2110    
2111            // Move joins to the right side
2112            topToBottom(relation);
2113    
2114            return relation;
2115        }
2116    
2117        /**
2118         * The map has to be validated against the relation because there are
2119         * certain cases where we do not want to (read: can not) do reordering, for
2120         * instance, when closures are involved.
2121         *
2122         * @param relation
2123         * @param map
2124         */
2125        private static boolean validateNodes(
2126            MondrianDef.RelationOrJoin relation,
2127            Map<String, RelNode> map)
2128        {
2129            if (relation instanceof MondrianDef.Relation) {
2130                MondrianDef.Relation table =
2131                    (MondrianDef.Relation) relation;
2132    
2133                RelNode relNode = RelNode.lookup(table, map);
2134                return (relNode != null);
2135    
2136            } else if (relation instanceof MondrianDef.Join) {
2137                MondrianDef.Join join = (MondrianDef.Join) relation;
2138    
2139                return validateNodes(join.left, map)
2140                    && validateNodes(join.right, map);
2141    
2142            } else {
2143                throw Util.newInternal("bad relation type " + relation);
2144            }
2145        }
2146    
2147        /**
2148         * Transforms the Relation moving the tables associated with
2149         * lower levels (greater level depth, i.e., Day is lower than Month) to the
2150         * left of tables with high levels.
2151         *
2152         * @param relation
2153         * @param map
2154         */
2155        private static int leftToRight(
2156            MondrianDef.RelationOrJoin relation,
2157            Map<String, RelNode> map)
2158        {
2159            if (relation instanceof MondrianDef.Relation) {
2160                MondrianDef.Relation table =
2161                    (MondrianDef.Relation) relation;
2162    
2163                RelNode relNode = RelNode.lookup(table, map);
2164                // Associate the table with its RelNode!!!! This is where this
2165                // happens.
2166                relNode.table = table;
2167    
2168                return relNode.depth;
2169    
2170            } else if (relation instanceof MondrianDef.Join) {
2171                MondrianDef.Join join = (MondrianDef.Join) relation;
2172                int leftDepth = leftToRight(join.left, map);
2173                int rightDepth = leftToRight(join.right, map);
2174    
2175                // we want the right side to be less than the left
2176                if (rightDepth > leftDepth) {
2177                    // switch
2178                    String leftAlias = join.leftAlias;
2179                    String leftKey = join.leftKey;
2180                    MondrianDef.RelationOrJoin left = join.left;
2181                    join.leftAlias = join.rightAlias;
2182                    join.leftKey = join.rightKey;
2183                    join.left = join.right;
2184                    join.rightAlias = leftAlias;
2185                    join.rightKey = leftKey;
2186                    join.right = left;
2187                }
2188                // Does not currently matter which is returned because currently we
2189                // only support structures where the left and right depth values
2190                // form an inclusive subset of depth values, that is, any
2191                // node with a depth value between the left or right values is
2192                // a child of this current join.
2193                return leftDepth;
2194    
2195            } else {
2196                throw Util.newInternal("bad relation type " + relation);
2197            }
2198        }
2199    
2200        /**
2201         * Transforms so that all joins have a table as their left child and either
2202         * a table of child join on the right.
2203         *
2204         * @param relation
2205         */
2206        private static void topToBottom(MondrianDef.RelationOrJoin relation) {
2207            if (relation instanceof MondrianDef.Table) {
2208                // nothing
2209    
2210            } else if (relation instanceof MondrianDef.Join) {
2211                MondrianDef.Join join = (MondrianDef.Join) relation;
2212    
2213                while (join.left instanceof MondrianDef.Join) {
2214                    MondrianDef.Join jleft = (MondrianDef.Join) join.left;
2215    
2216                    join.right =
2217                        new MondrianDef.Join(
2218                            join.leftAlias,
2219                            join.leftKey,
2220                            jleft.right,
2221                            join.rightAlias,
2222                            join.rightKey,
2223                            join.right);
2224    
2225                    join.left = jleft.left;
2226    
2227                    join.rightAlias = jleft.rightAlias;
2228                    join.rightKey = jleft.rightKey;
2229                    join.leftAlias = jleft.leftAlias;
2230                    join.leftKey = jleft.leftKey;
2231                }
2232            }
2233        }
2234    
2235        /**
2236         * Copies a {@link mondrian.olap.MondrianDef.RelationOrJoin}.
2237         *
2238         * @param relation
2239         */
2240        private static MondrianDef.RelationOrJoin copy(
2241            MondrianDef.RelationOrJoin relation)
2242        {
2243            if (relation instanceof MondrianDef.Table) {
2244                MondrianDef.Table table = (MondrianDef.Table) relation;
2245                return new MondrianDef.Table(table);
2246    
2247            } else if (relation instanceof MondrianDef.InlineTable) {
2248                MondrianDef.InlineTable table = (MondrianDef.InlineTable) relation;
2249                return new MondrianDef.InlineTable(table);
2250    
2251            } else if (relation instanceof MondrianDef.Join) {
2252                MondrianDef.Join join = (MondrianDef.Join) relation;
2253    
2254                MondrianDef.RelationOrJoin left = copy(join.left);
2255                MondrianDef.RelationOrJoin right = copy(join.right);
2256    
2257                return new MondrianDef.Join(
2258                    join.leftAlias, join.leftKey, left,
2259                    join.rightAlias, join.rightKey, right);
2260    
2261            } else {
2262                throw Util.newInternal("bad relation type " + relation);
2263            }
2264        }
2265    
2266        /**
2267         * Takes a relation in canonical form and snips off the
2268         * the tables with the given tableName (or table alias). The matching table
2269         * only appears once in the relation.
2270         *
2271         * @param relation
2272         * @param tableName
2273         */
2274        private static MondrianDef.RelationOrJoin snip(
2275            MondrianDef.RelationOrJoin relation,
2276            String tableName)
2277        {
2278            if (relation instanceof MondrianDef.Table) {
2279                MondrianDef.Table table = (MondrianDef.Table) relation;
2280                // Return null if the table's name or alias matches tableName
2281                return ((table.alias != null) && table.alias.equals(tableName))
2282                    ? null
2283                    : (table.name.equals(tableName) ? null : table);
2284    
2285            } else if (relation instanceof MondrianDef.Join) {
2286                MondrianDef.Join join = (MondrianDef.Join) relation;
2287    
2288                // snip left
2289                MondrianDef.RelationOrJoin left = snip(join.left, tableName);
2290                if (left == null) {
2291                    // left got snipped so return the right
2292                    // (the join is no longer a join).
2293                    return join.right;
2294    
2295                } else {
2296                    // whatever happened on the left, save it
2297                    join.left = left;
2298    
2299                    // snip right
2300                    MondrianDef.RelationOrJoin right = snip(join.right, tableName);
2301                    if (right == null) {
2302                        // right got snipped so return the left.
2303                        return join.left;
2304    
2305                    } else {
2306                        // save the right, join still has right and left children
2307                        // so return it.
2308                        join.right = right;
2309                        return join;
2310                    }
2311                }
2312    
2313    
2314            } else {
2315                throw Util.newInternal("bad relation type " + relation);
2316            }
2317        }
2318    
2319        public Member[] getMembersForQuery(String query, List<Member> calcMembers) {
2320            throw new UnsupportedOperationException();
2321        }
2322    
2323        /**
2324         * Returns the time hierarchy for this cube. If there is no time hierarchy,
2325         * throws.
2326         */
2327        public RolapHierarchy getTimeHierarchy(String funName) {
2328            for (RolapHierarchy hierarchy : hierarchyList) {
2329                if (hierarchy.getDimension().getDimensionType()
2330                    == DimensionType.TimeDimension)
2331                {
2332                    return hierarchy;
2333                }
2334            }
2335    
2336            throw MondrianResource.instance().NoTimeDimensionInCube.ex(funName);
2337        }
2338    
2339        /**
2340         * Finds out non joining dimensions for this cube.
2341         * Useful for finding out non joining dimensions for a stored measure from
2342         * a base cube.
2343         *
2344         * @param tuple array of members
2345         * @return Set of dimensions that do not exist (non joining) in this cube
2346         */
2347        public Set<Dimension> nonJoiningDimensions(Member[] tuple) {
2348            Set<Dimension> otherDims = new HashSet<Dimension>();
2349            for (Member member : tuple) {
2350                if (!member.isCalculated()) {
2351                    otherDims.add(member.getDimension());
2352                }
2353            }
2354            return nonJoiningDimensions(otherDims);
2355        }
2356    
2357        /**
2358         * Finds out non joining dimensions for this cube.  Equality test for
2359         * dimensions is done based on the unique name. Object equality can't be
2360         * used.
2361         *
2362         * @param otherDims Set of dimensions to be tested for existence in this
2363         * cube
2364         * @return Set of dimensions that do not exist (non joining) in this cube
2365         */
2366        public Set<Dimension> nonJoiningDimensions(Set<Dimension> otherDims) {
2367            Dimension[] baseCubeDimensions = getDimensions();
2368            Set<String>  baseCubeDimNames = new HashSet<String>();
2369            for (Dimension baseCubeDimension : baseCubeDimensions) {
2370                baseCubeDimNames.add(baseCubeDimension.getUniqueName());
2371            }
2372            Set<Dimension> nonJoiningDimensions = new HashSet<Dimension>();
2373            for (Dimension otherDim : otherDims) {
2374                if (!baseCubeDimNames.contains(otherDim.getUniqueName())) {
2375                    nonJoiningDimensions.add(otherDim);
2376                }
2377            }
2378            return nonJoiningDimensions;
2379        }
2380    
2381        List<Member> getMeasures() {
2382            Level measuresLevel = dimensions[0].getHierarchies()[0].getLevels()[0];
2383            return getSchemaReader().getLevelMembers(measuresLevel, true);
2384        }
2385    
2386        /**
2387         * Returns this cube's fact table, null if the cube is virtual.
2388         */
2389        MondrianDef.RelationOrJoin getFact() {
2390            return fact;
2391        }
2392    
2393        /**
2394         * Returns whether this cube is virtual. We use the fact that virtual cubes
2395         * do not have fact tables.
2396         */
2397        public boolean isVirtual() {
2398            return fact == null;
2399        }
2400    
2401        /**
2402         * Returns the system measure that counts the number of fact table rows in
2403         * a given cell.
2404         *
2405         * <p>Never null, because if there is no count measure explicitly defined,
2406         * the system creates one.
2407         */
2408        RolapMeasure getFactCountMeasure() {
2409            return factCountMeasure;
2410        }
2411    
2412        /**
2413         * Returns the system measure that counts the number of atomic cells in
2414         * a given cell.
2415         *
2416         * <p>A cell is atomic if all dimensions are at their lowest level.
2417         * If the fact table has a primary key, this measure is equivalent to the
2418         * {@link #getFactCountMeasure() fact count measure}.
2419         */
2420        RolapMeasure getAtomicCellCountMeasure() {
2421            // TODO: separate measure
2422            return factCountMeasure;
2423        }
2424    
2425        /**
2426         * Locates the base cube hierarchy for a particular virtual hierarchy.
2427         * If not found, return null. This may be converted to a map lookup
2428         * or cached in some way in the future to increase performance
2429         * with cubes that have large numbers of hierarchies
2430         *
2431         * @param hierarchy virtual hierarchy
2432         * @return base cube hierarchy if found
2433         */
2434        RolapHierarchy findBaseCubeHierarchy(RolapHierarchy hierarchy) {
2435            for (int i = 0; i < getDimensions().length; i++) {
2436                Dimension dimension = getDimensions()[i];
2437                if (dimension.getName().equals(
2438                    hierarchy.getDimension().getName()))
2439                {
2440                    for (int j = 0; j <  dimension.getHierarchies().length; j++) {
2441                        Hierarchy hier = dimension.getHierarchies()[j];
2442                        if (hier.getName().equals(hierarchy.getName())) {
2443                            return (RolapHierarchy)hier;
2444                        }
2445                    }
2446                }
2447            }
2448            return null;
2449        }
2450    
2451    
2452        /**
2453         * Locates the base cube level for a particular virtual level.
2454         * If not found, return null. This may be converted to a map lookup
2455         * or cached in some way in the future to increase performance
2456         * with cubes that have large numbers of hierarchies and levels
2457         *
2458         * @param level virtual level
2459         * @return base cube level if found
2460         */
2461        public RolapCubeLevel findBaseCubeLevel(RolapLevel level) {
2462            if (virtualToBaseMap.containsKey(level)) {
2463                return virtualToBaseMap.get(level);
2464            }
2465            String levelDimName = level.getDimension().getName();
2466            String levelHierName = level.getHierarchy().getName();
2467    
2468            // Closures are not in the dimension list so we need special logic for
2469            // locating the level.
2470            //
2471            // REVIEW: jhyde, 2009/7/21: This may no longer be the case, and we may
2472            // be able to improve performance. RolapCube.hierarchyList now contains
2473            // all hierarchies, including closure hierarchies; and
2474            // RolapHierarchy.closureFor indicates the base hierarchy for a closure
2475            // hierarchy.
2476    
2477            boolean isClosure = false;
2478            String closDimName = null;
2479            String closHierName = null;
2480            if (levelDimName.endsWith("$Closure")) {
2481                isClosure = true;
2482                closDimName = levelDimName.substring(0, levelDimName.length() - 8);
2483                closHierName =
2484                    levelHierName.substring(0, levelHierName.length() - 8);
2485            }
2486    
2487            for (Dimension dimension : getDimensions()) {
2488                final String dimensionName = dimension.getName();
2489                if (dimensionName.equals(levelDimName)
2490                    || (isClosure && dimensionName.equals(closDimName)))
2491                {
2492                    for (Hierarchy hier : dimension.getHierarchies()) {
2493                        final String hierarchyName = hier.getName();
2494                        if (hierarchyName.equals(levelHierName)
2495                            || (isClosure && hierarchyName.equals(closHierName)))
2496                        {
2497                            if (isClosure) {
2498                                final RolapCubeLevel baseLevel =
2499                                    ((RolapCubeLevel)
2500                                        hier.getLevels()[1]).getClosedPeer();
2501                                virtualToBaseMap.put(level, baseLevel);
2502                                return baseLevel;
2503                            }
2504                            for (Level lvl : hier.getLevels()) {
2505                                if (lvl.getName().equals(level.getName())) {
2506                                    final RolapCubeLevel baseLevel =
2507                                        (RolapCubeLevel) lvl;
2508                                    virtualToBaseMap.put(level, baseLevel);
2509                                    return baseLevel;
2510                                }
2511                            }
2512                        }
2513                    }
2514                }
2515            }
2516            return null;
2517        }
2518    
2519        RolapCubeDimension createDimension(
2520            MondrianDef.CubeDimension xmlCubeDimension,
2521            MondrianDef.Schema xmlSchema)
2522        {
2523            RolapCubeDimension dimension =
2524                getOrCreateDimension(
2525                    xmlCubeDimension, schema, xmlSchema,
2526                    dimensions.length, hierarchyList);
2527    
2528            if (! isVirtual()) {
2529                createUsages(dimension, xmlCubeDimension);
2530            }
2531            registerDimension(dimension);
2532    
2533            dimension.init(xmlCubeDimension);
2534    
2535            // add to dimensions array
2536            this.dimensions = Util.append(dimensions, dimension);
2537    
2538            return dimension;
2539        }
2540    
2541        public OlapElement lookupChild(SchemaReader schemaReader, Id.Segment s) {
2542            return lookupChild(schemaReader, s, MatchType.EXACT);
2543        }
2544    
2545        public OlapElement lookupChild(
2546            SchemaReader schemaReader, Id.Segment s, MatchType matchType)
2547        {
2548            // Note that non-exact matches aren't supported at this level,
2549            // so the matchType is ignored
2550            String status = null;
2551            OlapElement oe = null;
2552            if (matchType == MatchType.EXACT_SCHEMA) {
2553                oe = super.lookupChild(schemaReader, s, MatchType.EXACT_SCHEMA);
2554            } else {
2555                oe = super.lookupChild(schemaReader, s, MatchType.EXACT);
2556            }
2557    
2558            if (oe == null) {
2559                HierarchyUsage[] usages = getUsagesBySource(s.name);
2560                if (usages.length > 0) {
2561                    StringBuilder buf = new StringBuilder(64);
2562                    buf.append("RolapCube.lookupChild: ");
2563                    buf.append("In cube \"");
2564                    buf.append(getName());
2565                    buf.append("\" use of unaliased Dimension name \"");
2566                    buf.append(s);
2567                    if (usages.length == 1) {
2568                        // ERROR: this will work but is bad coding
2569                        buf.append("\" rather than the alias name ");
2570                        buf.append("\"");
2571                        buf.append(usages[0].getName());
2572                        buf.append("\" ");
2573                        getLogger().error(buf.toString());
2574                        throw new MondrianException(buf.toString());
2575                    } else {
2576                        // ERROR: this is not allowed
2577                        buf.append("\" rather than one of the alias names ");
2578                        for (HierarchyUsage usage : usages) {
2579                            buf.append("\"");
2580                            buf.append(usage.getName());
2581                            buf.append("\" ");
2582                        }
2583                        getLogger().error(buf.toString());
2584                        throw new MondrianException(buf.toString());
2585                    }
2586                }
2587            }
2588    
2589            if (getLogger().isDebugEnabled()) {
2590                if (!s.matches("Measures")) {
2591                    HierarchyUsage hierUsage = getUsageByName(s.name);
2592                    if (hierUsage == null) {
2593                        status = "hierUsage == null";
2594                    } else {
2595                        status =
2596                            "hierUsage == "
2597                            + (hierUsage.isShared() ? "shared" : "not shared");
2598                    }
2599                }
2600                StringBuilder buf = new StringBuilder(64);
2601                buf.append("RolapCube.lookupChild: ");
2602                buf.append("name=");
2603                buf.append(getName());
2604                buf.append(", childname=");
2605                buf.append(s);
2606                if (status != null) {
2607                    buf.append(", status=");
2608                    buf.append(status);
2609                }
2610                if (oe == null) {
2611                    buf.append(" returning null");
2612                } else {
2613                    buf.append(" returning elementname=").append(oe.getName());
2614                }
2615                getLogger().debug(buf.toString());
2616            }
2617    
2618            return oe;
2619        }
2620    
2621        /**
2622         * Returns the the measures hierarchy.
2623         */
2624        public Hierarchy getMeasuresHierarchy() {
2625            return measuresHierarchy;
2626        }
2627    
2628        public List<RolapMember> getMeasuresMembers() {
2629            return measuresHierarchy.getMemberReader().getMembers();
2630        }
2631    
2632        public Member createCalculatedMember(String xml) {
2633            MondrianDef.CalculatedMember xmlCalcMember;
2634            try {
2635                final Parser xmlParser = XOMUtil.createDefaultParser();
2636                final DOMWrapper def = xmlParser.parse(xml);
2637                final String tagName = def.getTagName();
2638                if (tagName.equals("CalculatedMember")) {
2639                    xmlCalcMember = new MondrianDef.CalculatedMember(def);
2640                } else {
2641                    throw new XOMException(
2642                        "Got <" + tagName + "> when expecting <CalculatedMember>");
2643                }
2644            } catch (XOMException e) {
2645                throw Util.newError(
2646                    e,
2647                    "Error while creating calculated member from XML ["
2648                    + xml + "]");
2649            }
2650    
2651            try {
2652                loadInProgress = true;
2653                final List<RolapMember> memberList = new ArrayList<RolapMember>();
2654                createCalcMembersAndNamedSets(
2655                    Collections.singletonList(xmlCalcMember),
2656                    Collections.<MondrianDef.NamedSet>emptyList(),
2657                    memberList,
2658                    new ArrayList<Formula>(),
2659                    this,
2660                    true);
2661                assert memberList.size() == 1;
2662                return memberList.get(0);
2663            } finally {
2664                loadInProgress = false;
2665            }
2666        }
2667    
2668        /**
2669         * Creates a calculated member.
2670         *
2671         * <p>The member will be called [{dimension name}].[{name}].
2672         *
2673         * <p>Not for public use.
2674         *
2675         * @param hierarchy Hierarchy the calculated member belongs to
2676         * @param name Name of member
2677         * @param calc Compiled expression
2678         */
2679        RolapMember createCalculatedMember(
2680            RolapHierarchy hierarchy,
2681            String name,
2682            Calc calc)
2683        {
2684            final List<Id.Segment> segmentList = new ArrayList<Id.Segment>();
2685            segmentList.addAll(
2686                Util.parseIdentifier(hierarchy.getUniqueName()));
2687            segmentList.add(new Id.Segment(name, Id.Quoting.QUOTED));
2688            final Formula formula = new Formula(
2689                new Id(segmentList),
2690                createDummyExp(calc),
2691                new MemberProperty[0]);
2692            final Query query =
2693                new Query(
2694                    schema.getInternalConnection(),
2695                    this,
2696                    new Formula[] {formula},
2697                    new QueryAxis[0],
2698                    null,
2699                    new QueryPart[0],
2700                    new Parameter[0],
2701                    false);
2702            query.createValidator().validate(formula);
2703            calculatedMemberList.add(formula);
2704            return (RolapMember) formula.getMdxMember();
2705        }
2706    
2707        /**
2708         * Schema reader which works from the perspective of a particular cube
2709         * (and hence includes calculated members defined in that cube) and also
2710         * applies the access-rights of a given role.
2711         */
2712        private class RolapCubeSchemaReader extends RolapSchemaReader {
2713            public RolapCubeSchemaReader(Role role) {
2714                super(role, RolapCube.this.schema);
2715                assert role != null : "precondition: role != null";
2716            }
2717    
2718            public List<Member> getLevelMembers(
2719                Level level,
2720                boolean includeCalculated)
2721            {
2722                List<Member> members = super.getLevelMembers(level, false);
2723                if (includeCalculated) {
2724                    members = Util.addLevelCalculatedMembers(this, level, members);
2725                }
2726                return members;
2727            }
2728    
2729            public Member getCalculatedMember(List<Id.Segment> nameParts) {
2730                final String uniqueName = Util.implode(nameParts);
2731                for (Formula formula : calculatedMemberList) {
2732                    final String formulaUniqueName =
2733                        formula.getMdxMember().getUniqueName();
2734                    if (formulaUniqueName.equals(uniqueName)
2735                        && getRole().canAccess(formula.getMdxMember()))
2736                    {
2737                        return formula.getMdxMember();
2738                    }
2739                }
2740                return null;
2741            }
2742    
2743            public NamedSet getNamedSet(List<Id.Segment> segments) {
2744                if (segments.size() == 1) {
2745                    Id.Segment segment = segments.get(0);
2746                    for (Formula namedSet : namedSetList) {
2747                        if (segment.matches(namedSet.getName())) {
2748                            return namedSet.getNamedSet();
2749                        }
2750                    }
2751                }
2752                return super.getNamedSet(segments);
2753            }
2754    
2755            public List<Member> getCalculatedMembers(Hierarchy hierarchy) {
2756                ArrayList<Member> list = new ArrayList<Member>();
2757    
2758                if (getRole().getAccess(hierarchy) == Access.NONE) {
2759                    return list;
2760                }
2761    
2762                for (Member member : getCalculatedMembers()) {
2763                    if (member.getHierarchy().equals(hierarchy)) {
2764                        list.add(member);
2765                    }
2766                }
2767                return list;
2768            }
2769    
2770            public List<Member> getCalculatedMembers(Level level) {
2771                List<Member> list = new ArrayList<Member>();
2772    
2773                if (getRole().getAccess(level) == Access.NONE) {
2774                    return list;
2775                }
2776    
2777                for (Member member : getCalculatedMembers()) {
2778                    if (member.getLevel().equals(level)) {
2779                        list.add(member);
2780                    }
2781                }
2782                return list;
2783            }
2784    
2785            public List<Member> getCalculatedMembers() {
2786                List<Member> list =
2787                    roleToAccessibleCalculatedMembers.get(getRole());
2788                if (list == null) {
2789                    list = new ArrayList<Member>();
2790                    for (Formula formula : calculatedMemberList) {
2791                        Member member = formula.getMdxMember();
2792                        if (getRole().canAccess(member)) {
2793                            list.add(member);
2794                        }
2795                    }
2796                    //  calculatedMembers array may not have been initialized
2797                    if (list.size() > 0) {
2798                        roleToAccessibleCalculatedMembers.put(getRole(), list);
2799                    }
2800                }
2801                return list;
2802            }
2803    
2804            public SchemaReader withoutAccessControl() {
2805                assert getClass() == RolapCubeSchemaReader.class
2806                    : "Derived class " + getClass() + " must override method";
2807                return RolapCube.this.getSchemaReader();
2808            }
2809    
2810            public Member getMemberByUniqueName(
2811                List<Id.Segment> uniqueNameParts,
2812                boolean failIfNotFound,
2813                MatchType matchType)
2814            {
2815                Member member =
2816                    (Member) lookupCompound(
2817                        RolapCube.this, uniqueNameParts,
2818                        failIfNotFound, Category.Member,
2819                        matchType);
2820                if (!failIfNotFound && member == null) {
2821                    return null;
2822                }
2823                if (getRole().canAccess(member)) {
2824                    return member;
2825                } else {
2826                    return null;
2827                }
2828            }
2829    
2830            public Cube getCube() {
2831                return RolapCube.this;
2832            }
2833        }
2834    
2835        /**
2836         * Visitor that walks an MDX parse tree containing formulas
2837         * associated with calculated members defined in a base cube but
2838         * referenced from a virtual cube.  When walking the tree, look
2839         * for other calculated members as well as stored measures.  Keep
2840         * track of all stored measures found, and for the calculated members,
2841         * once the formula of that calculated member has been visited, resolve
2842         * the calculated member relative to the virtual cube.
2843         */
2844        private class MeasureFinder extends MdxVisitorImpl
2845        {
2846            /**
2847             * The virtual cube where the original calculated member was
2848             * referenced from
2849             */
2850            private RolapCube virtualCube;
2851    
2852            /**
2853             * The base cube where the original calculated member is defined
2854             */
2855            private RolapCube baseCube;
2856    
2857            /**
2858             * The measures level corresponding to the virtual cube
2859             */
2860            private RolapLevel measuresLevel;
2861    
2862            /**
2863             * List of measures found
2864             */
2865            private List<RolapVirtualCubeMeasure> measuresFound;
2866    
2867            /**
2868             * List of calculated members found
2869             */
2870            private List<RolapCalculatedMember> calcMembersSeen;
2871    
2872            public MeasureFinder(
2873                RolapCube virtualCube,
2874                RolapCube baseCube,
2875                RolapLevel measuresLevel)
2876            {
2877                this.virtualCube = virtualCube;
2878                this.baseCube = baseCube;
2879                this.measuresLevel = measuresLevel;
2880                this.measuresFound = new ArrayList<RolapVirtualCubeMeasure>();
2881                this.calcMembersSeen = new ArrayList<RolapCalculatedMember>();
2882            }
2883    
2884            public Object visit(MemberExpr memberExpr)
2885            {
2886                Member member = memberExpr.getMember();
2887                if (member instanceof RolapCalculatedMember) {
2888                    // ignore the calculated member if we've already processed
2889                    // it in another reference
2890                    if (calcMembersSeen.contains(member)) {
2891                        return null;
2892                    }
2893                    RolapCalculatedMember calcMember =
2894                        (RolapCalculatedMember) member;
2895                    Formula formula = calcMember.getFormula();
2896                    formula.accept(this);
2897                    calcMembersSeen.add(calcMember);
2898    
2899                    // now that we've located all measures referenced in the
2900                    // calculated member's formula, resolve the calculated
2901                    // member relative to the virtual cube
2902                    virtualCube.setMeasuresHierarchyMemberReader(
2903                        new CacheMemberReader(
2904                            new MeasureMemberSource(
2905                                virtualCube.measuresHierarchy,
2906                                Util.<RolapMember>cast(measuresFound))));
2907    
2908                    MondrianDef.CalculatedMember xmlCalcMember =
2909                        schema.lookupXmlCalculatedMember(
2910                            calcMember.getUniqueName(),
2911                            baseCube.name);
2912                    createCalcMembersAndNamedSets(
2913                        Collections.singletonList(xmlCalcMember),
2914                        Collections.<MondrianDef.NamedSet>emptyList(),
2915                        new ArrayList<RolapMember>(),
2916                        new ArrayList<Formula>(),
2917                        virtualCube,
2918                        false);
2919                    return null;
2920    
2921                } else if (member instanceof RolapBaseCubeMeasure) {
2922                    RolapBaseCubeMeasure baseMeasure =
2923                        (RolapBaseCubeMeasure) member;
2924                    RolapVirtualCubeMeasure virtualCubeMeasure =
2925                        new RolapVirtualCubeMeasure(
2926                            null,
2927                            measuresLevel,
2928                            baseMeasure,
2929                            Collections.<String, Annotation>emptyMap());
2930                    if (!measuresFound.contains(virtualCubeMeasure)) {
2931                        measuresFound.add(virtualCubeMeasure);
2932                    }
2933                }
2934    
2935                return null;
2936            }
2937    
2938            public List<RolapVirtualCubeMeasure> getMeasuresFound()
2939            {
2940                return measuresFound;
2941            }
2942        }
2943    
2944        public static class CubeComparator implements Comparator<RolapCube>
2945        {
2946            public int compare(RolapCube c1, RolapCube c2)
2947            {
2948                return c1.getName().compareTo(c2.getName());
2949            }
2950        }
2951    
2952        /**
2953         * Creates an expression that compiles to a given compiled expression.
2954         *
2955         * <p>Use this for synthetic expressions that do not correspond to anything
2956         * in an MDX parse tree, and just need to compile to a particular compiled
2957         * expression. The expression has minimal amounts of metadata, for example
2958         * type information, but the function has no name or description.
2959         *
2960         * @see mondrian.calc.DummyExp
2961         */
2962        static Exp createDummyExp(final Calc calc) {
2963            return new ResolvedFunCall(
2964                new FunDefBase("dummy", null, "fn") {
2965                    public Calc compileCall(
2966                        ResolvedFunCall call, ExpCompiler compiler)
2967                    {
2968                        return calc;
2969                    }
2970                },
2971                new Exp[0],
2972                calc.getType());
2973        }
2974    }
2975    
2976    // End RolapCube.java