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