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