001    /*
002    // $Id: //open/mondrian-release/3.0/src/main/mondrian/rolap/RolapCube.java#6 $
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-2007 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-release/3.0/src/main/mondrian/rolap/RolapCube.java#6 $
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    
135            this.dimensions[0] = measuresDimension;
136    
137            this.measuresHierarchy = measuresDimension.newHierarchy(null, false);
138    
139            if (!Util.isEmpty(xmlSchema.measuresCaption)) {
140                measuresDimension.setCaption(xmlSchema.measuresCaption);
141                this.measuresHierarchy.setCaption(xmlSchema.measuresCaption);
142            }
143    
144            for (int i = 0; i < dimensions.length; i++) {
145                MondrianDef.CubeDimension xmlCubeDimension = dimensions[i];
146                // Look up usages of shared dimensions in the schema before
147                // consulting the XML schema (which may be null).
148                RolapCubeDimension dimension =
149                    getOrCreateDimension(xmlCubeDimension, schema, xmlSchema, i + 1);
150                if (getLogger().isDebugEnabled()) {
151                    getLogger().debug("RolapCube<init>: dimension="
152                        + dimension.getName());
153                }
154                this.dimensions[i + 1] = dimension;
155    
156                if (! isVirtual()) {
157                    createUsages(dimension, xmlCubeDimension);
158                }
159    
160                // the register Dimension call was moved here
161                // to keep the RolapStar in sync with the realiasing
162                // within the RolapCubeHierarchy objects.
163                registerDimension(dimension);
164            }
165    
166            schema.addCube(this);
167        }
168    
169        /**
170         * Creates a <code>RolapCube</code> from a regular cube.
171         */
172        RolapCube(
173            RolapSchema schema,
174            MondrianDef.Schema xmlSchema,
175            MondrianDef.Cube xmlCube,
176            boolean load)
177        {
178            this(
179                schema, xmlSchema, xmlCube.name, xmlCube.caption, xmlCube.cache,
180                xmlCube.fact, xmlCube.dimensions, load);
181    
182            if (fact == null) {
183                throw Util.newError(
184                    "Must specify fact table of cube '" +
185                        getName() + "'");
186            }
187    
188            if (fact.getAlias() == null) {
189                throw Util.newError(
190                    "Must specify alias for fact table of cube '" +
191                        getName() + "'");
192            }
193    
194            // since MondrianDef.Measure and MondrianDef.VirtualCubeMeasure
195            // can not be treated as the same, measure creation can not be
196            // done in a common constructor.
197            RolapLevel measuresLevel = this.measuresHierarchy.newMeasuresLevel();
198    
199            List<RolapMember> measureList =
200                new ArrayList<RolapMember>(xmlCube.measures.length);
201            Member defaultMeasure = null;
202            for (int i = 0; i < xmlCube.measures.length; i++) {
203                MondrianDef.Measure xmlMeasure = xmlCube.measures[i];
204                MondrianDef.Expression measureExp;
205                if (xmlMeasure.column != null) {
206                    if (xmlMeasure.measureExp != null) {
207                        throw MondrianResource.instance().
208                        BadMeasureSource.ex(
209                            xmlCube.name, xmlMeasure.name);
210                    }
211                    measureExp = new MondrianDef.Column(
212                        fact.getAlias(), xmlMeasure.column);
213                } else if (xmlMeasure.measureExp != null) {
214                    measureExp = xmlMeasure.measureExp;
215                } else {
216                    throw MondrianResource.instance().
217                    BadMeasureSource.ex(
218                            xmlCube.name, xmlMeasure.name);
219                }
220    
221                // Validate aggregator name. Substitute deprecated "distinct count"
222                // with modern "distinct-count".
223                String aggregator = xmlMeasure.aggregator;
224                if (aggregator.equals("distinct count")) {
225                    aggregator = RolapAggregator.DistinctCount.getName();
226                }
227                final RolapBaseCubeMeasure measure = new RolapBaseCubeMeasure(
228                        this, null, measuresLevel, xmlMeasure.name,
229                        xmlMeasure.formatString, measureExp,
230                    aggregator, xmlMeasure.datatype);
231                measureList.add(measure);
232                if(Util.equalName(measure.getName(),xmlCube.defaultMeasure)){
233                    defaultMeasure = measure;
234                }
235    
236                try {
237                    CellFormatter cellFormatter =
238                        getCellFormatter(xmlMeasure.formatter);
239                    if (cellFormatter != null) {
240                        measure.setFormatter(cellFormatter);
241                    }
242                } catch (Exception e) {
243                    throw MondrianResource.instance().CellFormatterLoadFailed.ex(
244                        xmlMeasure.formatter, measure.getUniqueName(), e);
245                }
246    
247                // Set member's caption, if present.
248                if (!Util.isEmpty(xmlMeasure.caption)) {
249                    // there is a special caption string
250                    measure.setProperty(
251                            Property.CAPTION.name,
252                            xmlMeasure.caption);
253                }
254    
255                // Set member's visibility, default true.
256                Boolean visible = xmlMeasure.visible;
257                if (visible == null) {
258                    visible = Boolean.TRUE;
259                }
260                measure.setProperty(Property.VISIBLE.name, visible);
261    
262                List<String> propNames = new ArrayList<String>();
263                List<String> propExprs = new ArrayList<String>();
264                validateMemberProps(
265                    xmlMeasure.memberProperties, propNames, propExprs,
266                    xmlMeasure.name);
267                int ordinal = i;
268                for (int j = 0; j < propNames.size(); j++) {
269                    String propName = propNames.get(j);
270                    final Object propExpr = propExprs.get(j);
271                    measure.setProperty(propName, propExpr);
272                    if (propName.equals(Property.MEMBER_ORDINAL.name)
273                        && propExpr instanceof String) {
274                        final String expr = (String) propExpr;
275                        if (expr.startsWith("\"")
276                            && expr.endsWith("\"")) {
277                            try {
278                                ordinal =
279                                    Integer.valueOf(
280                                        expr.substring(1, expr.length() - 1));
281                            } catch (NumberFormatException e) {
282                                Util.discard(e);
283                            }
284                        }
285                    }
286                }
287                measure.setOrdinal(ordinal);
288            }
289    
290            setMeasuresHierarchyMemberReader(
291                new CacheMemberReader(
292                    new MeasureMemberSource(this.measuresHierarchy, measureList)));
293    
294            this.measuresHierarchy.setDefaultMember(defaultMeasure);
295            init(xmlCube.dimensions);
296            init(xmlCube, measureList);
297            
298            setMeasuresHierarchyMemberReader(
299                new CacheMemberReader(
300                    new MeasureMemberSource(this.measuresHierarchy, measureList)));
301    
302            checkOrdinals(xmlCube.name, measureList);
303            loadAggGroup(xmlCube);
304        }
305        
306        /**
307         * this method makes sure that the schemaReader cache is invalidated.
308         * problems can occur if the measure hierarchy member reader is out
309         * of sync with the cache.
310         * 
311         * @param memberReader new member reader for measures hierarchy
312         */
313        private void setMeasuresHierarchyMemberReader(MemberReader memberReader) {
314            this.measuresHierarchy.setMemberReader(memberReader);
315            // this invalidates any cached schema reader
316            this.schemaReader = null;
317        }
318    
319        /**
320         * Given the name of a cell formatter class, returns a cell formatter.
321         * If class name is null, returns null.
322         *
323         * @param cellFormatterClassName Name of cell formatter class
324         * @return Cell formatter or null
325         * @throws Exception if class cannot be instantiated
326         */
327        static CellFormatter getCellFormatter(
328            String cellFormatterClassName)
329            throws Exception
330        {
331            if (Util.isEmpty(cellFormatterClassName)) {
332                return null;
333            }
334            //noinspection unchecked
335            Class<CellFormatter> clazz =
336                (Class<CellFormatter>)
337                    Class.forName(cellFormatterClassName);
338            Constructor<CellFormatter> ctor = clazz.getConstructor();
339            return ctor.newInstance();
340        }
341    
342        /**
343         * Creates a <code>RolapCube</code> from a virtual cube.
344         */
345        RolapCube(
346            RolapSchema schema,
347            MondrianDef.Schema xmlSchema,
348            MondrianDef.VirtualCube xmlVirtualCube,
349            boolean load)
350        {
351            this(schema, xmlSchema, xmlVirtualCube.name, xmlVirtualCube.caption,
352                true, null, xmlVirtualCube.dimensions, load);
353    
354    
355            // Since MondrianDef.Measure and MondrianDef.VirtualCubeMeasure cannot
356            // be treated as the same, measure creation cannot be done in a common
357            // constructor.
358            RolapLevel measuresLevel = this.measuresHierarchy.newMeasuresLevel();
359    
360            // Recreate CalculatedMembers, as the original members point to
361            // incorrect dimensional ordinals for the virtual cube.
362            List<RolapVirtualCubeMeasure> origMeasureList =
363                new ArrayList<RolapVirtualCubeMeasure>();
364            List<MondrianDef.CalculatedMember> origCalcMeasureList =
365                new ArrayList<MondrianDef.CalculatedMember>();
366            CubeComparator cubeComparator = new CubeComparator();
367            Map<RolapCube, List<MondrianDef.CalculatedMember>> calculatedMembersMap =
368                new TreeMap<RolapCube, List<MondrianDef.CalculatedMember>>(
369                    cubeComparator);
370            Member defaultMeasure = null;
371    
372            this.cubeUsages = new RolapCubeUsages(xmlVirtualCube.cubeUsage);
373    
374            for (MondrianDef.VirtualCubeMeasure xmlMeasure : xmlVirtualCube.measures) {
375                // Lookup a measure in an existing cube.
376                RolapCube cube = schema.lookupCube(xmlMeasure.cubeName);
377                Member[] cubeMeasures = cube.getMeasures();
378                boolean found = false;
379                for (Member cubeMeasure : cubeMeasures) {
380                    if (cubeMeasure.getUniqueName().equals(xmlMeasure.name)) {
381                        if (cubeMeasure.getName().equalsIgnoreCase(xmlVirtualCube.defaultMeasure)){
382                            defaultMeasure = cubeMeasure;
383                        }
384                        found = true;
385                        if (cubeMeasure instanceof RolapCalculatedMember) {
386                            // We have a calulated member!  Keep track of which
387                            // base cube each calculated member is associated
388                            // with, so we can resolve the calculated member
389                            // relative to its base cube.  We're using a treeMap
390                            // to store the mapping to ensure a deterministic
391                            // order for the members.
392                            MondrianDef.CalculatedMember calcMember =
393                                schema.lookupXmlCalculatedMember(
394                                    xmlMeasure.name, xmlMeasure.cubeName);
395                            if (calcMember == null) {
396                                throw Util.newInternal(
397                                    "Could not find XML Calculated Member '" +
398                                        xmlMeasure.name + "' in XML cube '" +
399                                        xmlMeasure.cubeName + "'");
400                            }
401                            List<MondrianDef.CalculatedMember> memberList =
402                                calculatedMembersMap.get(cube);
403                            if (memberList == null) {
404                                memberList =
405                                    new ArrayList<MondrianDef.CalculatedMember>();
406                            }
407                            memberList.add(calcMember);
408                            origCalcMeasureList.add(calcMember);
409                            calculatedMembersMap.put(cube, memberList);
410                        } else {
411                            // This is the a standard measure. (Don't know
412                            // whether it will confuse things that this
413                            // measure still points to its 'real' cube.)
414                            RolapVirtualCubeMeasure virtualCubeMeasure =
415                                new RolapVirtualCubeMeasure(
416                                    null,
417                                    measuresLevel,
418                                    (RolapStoredMeasure) cubeMeasure);
419    
420                            // Set member's visibility, default true.
421                            Boolean visible = xmlMeasure.visible;
422                            if (visible == null) {
423                                visible = Boolean.TRUE;
424                            }
425                            virtualCubeMeasure.setProperty(Property.VISIBLE.name,
426                                visible);
427                            // Inherit caption from the "real" measure
428                            virtualCubeMeasure.setProperty(Property.CAPTION.name,
429                                cubeMeasure.getCaption());
430                            origMeasureList.add(virtualCubeMeasure);
431                        }
432                        break;
433                    }
434                }
435                if (!found) {
436                    throw Util.newInternal(
437                        "could not find measure '" + xmlMeasure.name +
438                            "' in cube '" + xmlMeasure.cubeName + "'");
439                }
440            }
441    
442            // Must init the dimensions before dealing with calculated members
443            init(xmlVirtualCube.dimensions);
444    
445            // Loop through the base cubes containing calculated members
446            // referenced by this virtual cube.  Resolve those members relative
447            // to their base cubes first, then resolve them relative to this
448            // cube so the correct dimension ordinals are used
449            List<RolapVirtualCubeMeasure> modifiedMeasureList =
450                new ArrayList<RolapVirtualCubeMeasure>(origMeasureList);
451            for (Object o : calculatedMembersMap.keySet()) {
452                RolapCube baseCube = (RolapCube) o;
453                List<MondrianDef.CalculatedMember> calculatedMemberList =
454                    calculatedMembersMap.get(baseCube);
455                Query queryExp = resolveCalcMembers(
456                    calculatedMemberList.toArray(
457                        new MondrianDef.CalculatedMember[
458                            calculatedMemberList.size()]),
459                    new MondrianDef.NamedSet[0],
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.toArray(
492                    new MondrianDef.CalculatedMember[
493                        calculatedMemberList.size()]),
494                    xmlVirtualCube.namedSets,
495                    new ArrayList<RolapMember>(),
496                    new ArrayList<Formula>(),
497                    this,
498                    false);
499    
500            // reset the measureHierarchy member reader back to the list of
501            // measures that are only defined on this virtual cube
502            setMeasuresHierarchyMemberReader(
503                new CacheMemberReader(
504                    new MeasureMemberSource(
505                        this.measuresHierarchy,
506                        Util.<RolapMember>cast(origMeasureList))));
507            
508            this.measuresHierarchy.setDefaultMember(defaultMeasure);
509    
510    
511            // remove from the calculated members array those members that weren't
512            // originally defined on this virtual cube
513            List<Formula> finalCalcMemberList = new ArrayList<Formula>();
514            for (Formula calculatedMember : calculatedMembers) {
515                if (findOriginalMembers(
516                    calculatedMember,
517                    origCalcMeasureList,
518                    finalCalcMemberList)) {
519                    continue;
520                }
521                findOriginalMembers(
522                    calculatedMember,
523                    Arrays.asList(xmlVirtualCube.calculatedMembers),
524                    finalCalcMemberList);
525            }
526            calculatedMembers =
527                finalCalcMemberList.toArray(
528                    new Formula[finalCalcMemberList.size()]);
529    
530            for (Formula calcMember : finalCalcMemberList) {
531                  if (calcMember.getName().
532                          equalsIgnoreCase(xmlVirtualCube.defaultMeasure)){
533                          this.measuresHierarchy.setDefaultMember(calcMember.getMdxMember());
534                          break;
535                  }
536            }
537    
538            // Note: virtual cubes do not get aggregate
539        }
540    
541        private boolean findOriginalMembers(
542            Formula formula,
543            List<MondrianDef.CalculatedMember> calcMemberList,
544            List<Formula> finalCalcMemberList)
545        {
546            for (MondrianDef.CalculatedMember xmlCalcMember : calcMemberList) {
547                Dimension dimension =
548                    (Dimension) lookupDimension(
549                            new Id.Segment(
550                                xmlCalcMember.dimension,
551                                Id.Quoting.UNQUOTED));
552                if (formula.getName().equals(xmlCalcMember.name) &&
553                    formula.getMdxMember().getDimension().getName().equals(
554                        dimension.getName())) {
555                    finalCalcMemberList.add(formula);
556                    return true;
557                }
558            }
559            return false;
560        }
561    
562        protected Logger getLogger() {
563            return LOGGER;
564        }
565    
566        public boolean hasAggGroup() {
567            return (aggGroup != null);
568        }
569        public ExplicitRules.Group getAggGroup() {
570            return aggGroup;
571        }
572        void loadAggGroup(MondrianDef.Cube xmlCube) {
573            aggGroup = ExplicitRules.Group.make(this, xmlCube);
574        }
575    
576        /**
577         * Creates a dimension from its XML definition. If the XML definition is
578         * a &lt;DimensionUsage&gt;, and the shared dimension is cached in the
579         * schema, returns that.
580         *
581         * @param xmlCubeDimension XML Dimension or DimensionUsage
582         * @param schema Schema
583         * @param xmlSchema XML Schema
584         * @param dimensionOrdinal Ordinal of dimension
585         * @return A dimension
586         */
587        private RolapCubeDimension getOrCreateDimension(
588            MondrianDef.CubeDimension xmlCubeDimension,
589            RolapSchema schema,
590            MondrianDef.Schema xmlSchema,
591            int dimensionOrdinal)
592        {
593            RolapDimension dimension = null;
594            if (xmlCubeDimension instanceof MondrianDef.DimensionUsage) {
595                MondrianDef.DimensionUsage usage =
596                    (MondrianDef.DimensionUsage) xmlCubeDimension;
597                final RolapHierarchy sharedHierarchy =
598                    schema.getSharedHierarchy(usage.source);
599                if (sharedHierarchy != null) {
600                    dimension =
601                        (RolapDimension) sharedHierarchy.getDimension();
602                }
603            }
604    
605            if (dimension == null) {
606                MondrianDef.Dimension xmlDimension =
607                    xmlCubeDimension.getDimension(xmlSchema);
608                dimension =
609                    new RolapDimension(
610                            schema, this, xmlDimension, xmlCubeDimension);
611            }
612    
613            // wrap the shared or regular dimension with a
614            // rolap cube dimension object
615            return new RolapCubeDimension(
616                    this, dimension, xmlCubeDimension, 
617                    xmlCubeDimension.name, dimensionOrdinal);
618        }
619    
620        /**
621         * Post-initialization, doing things which cannot be done in the
622         * constructor.
623         */
624        private void init(
625            MondrianDef.Cube xmlCube,
626            final List<RolapMember> memberList)
627        {
628            // Load calculated members and named sets.
629            // (We cannot do this in the constructor, because
630            // cannot parse the generated query, because the schema has not been
631            // set in the cube at this point.)
632            List<Formula> formulaList = new ArrayList<Formula>();
633            createCalcMembersAndNamedSets(
634                xmlCube.calculatedMembers, xmlCube.namedSets,
635                memberList, formulaList, this, true);
636        }
637    
638        /**
639         * Checks that the ordinals of measures (including calculated measures)
640         * are unique.
641         *
642         * @param cubeName        name of the cube (required for error messages)
643         * @param measures        measure list
644         */
645        private void checkOrdinals(
646            String cubeName,
647            List<RolapMember> measures)
648        {
649            Map<Integer, String> ordinals = new HashMap<Integer, String>();
650            for (RolapMember measure : measures) {
651                Integer ordinal = measure.getOrdinal();
652                if (!ordinals.containsKey(ordinal)) {
653                    ordinals.put(ordinal, measure.getUniqueName());
654                } else {
655                    throw MondrianResource.instance().MeasureOrdinalsNotUnique.ex(
656                        cubeName,
657                        ordinal.toString(),
658                        ordinals.get(ordinal),
659                        measure.getUniqueName());
660                }
661            }
662        }
663    
664        /**
665         * Adds a collection of calculated members and named sets to this cube.
666         * The members and sets can refer to each other.
667         *
668         * @param xmlCalcMembers XML objects representing members
669         * @param xmlNamedSets Array of XML definition of named set
670         * @param memberList Output list of {@link Member} objects
671         * @param formulaList Output list of {@link Formula} objects
672         * @param cube the cube that the calculated members originate from
673         * @param errOnDups throws an error if a duplicate member is found
674         */
675        private void createCalcMembersAndNamedSets(
676                MondrianDef.CalculatedMember[] xmlCalcMembers,
677                MondrianDef.NamedSet[] xmlNamedSets,
678                List<RolapMember> memberList,
679                List<Formula> formulaList,
680                RolapCube cube,
681                boolean errOnDups) {
682    
683            final Query queryExp =
684                resolveCalcMembers(
685                    xmlCalcMembers,
686                    xmlNamedSets,
687                    cube,
688                    errOnDups);
689            if (queryExp == null) {
690                return;
691            }
692    
693            // Now pick through the formulas.
694            Util.assertTrue(queryExp.formulas.length ==
695                    xmlCalcMembers.length + xmlNamedSets.length);
696            for (int i = 0; i < xmlCalcMembers.length; i++) {
697                postCalcMember(xmlCalcMembers, i, queryExp, memberList);
698            }
699            for (int i = 0; i < xmlNamedSets.length; i++) {
700                postNamedSet(xmlNamedSets, xmlCalcMembers.length, i, queryExp, formulaList);
701            }
702        }
703    
704        private Query resolveCalcMembers(
705            MondrianDef.CalculatedMember[] xmlCalcMembers,
706            MondrianDef.NamedSet[] xmlNamedSets,
707            RolapCube cube,
708            boolean errOnDups)
709        {
710            // If there are no objects to create, our generated SQL will be so
711            // silly, the parser will laugh.
712            if (xmlCalcMembers.length == 0 && xmlNamedSets.length == 0) {
713                return null;
714            }
715    
716            StringBuilder buf = new StringBuilder(256);
717            buf.append("WITH").append(Util.nl);
718    
719            // Check the members individually, and generate SQL.
720            for (int i = 0; i < xmlCalcMembers.length; i++) {
721                preCalcMember(xmlCalcMembers, i, buf, cube, errOnDups);
722            }
723    
724            // Check the named sets individually (for uniqueness) and generate SQL.
725            Set<String> nameSet = new HashSet<String>();
726            for (Formula namedSet : namedSets) {
727                nameSet.add(namedSet.getName());
728            }
729            for (MondrianDef.NamedSet xmlNamedSet : xmlNamedSets) {
730                preNamedSet(xmlNamedSet, nameSet, buf);
731            }
732    
733            buf.append("SELECT FROM ").append(cube.getUniqueName());
734    
735            // Parse and validate this huge MDX query we've created.
736            final String queryString = buf.toString();
737            final Query queryExp;
738            try {
739                RolapConnection conn = schema.getInternalConnection();
740                queryExp = conn.parseQuery(queryString, load);
741            } catch (Exception e) {
742                throw MondrianResource.instance().UnknownNamedSetHasBadFormula.ex(
743                    getName(), e);
744            }
745            queryExp.resolve();
746            return queryExp;
747        }
748    
749        private void postNamedSet(
750                MondrianDef.NamedSet[] xmlNamedSets,
751                final int offset, int i,
752                final Query queryExp,
753                List<Formula> formulaList) {
754            MondrianDef.NamedSet xmlNamedSet = xmlNamedSets[i];
755            Util.discard(xmlNamedSet);
756            Formula formula = queryExp.formulas[offset + i];
757            namedSets = RolapUtil.addElement(namedSets, formula);
758            formulaList.add(formula);
759        }
760    
761        private void preNamedSet(
762                MondrianDef.NamedSet xmlNamedSet,
763                Set<String> nameSet,
764                StringBuilder buf) {
765            if (!nameSet.add(xmlNamedSet.name)) {
766                throw MondrianResource.instance().NamedSetNotUnique.ex(
767                    xmlNamedSet.name, getName());
768            }
769    
770            buf.append("SET ")
771                    .append(Util.makeFqName(xmlNamedSet.name))
772                    .append(Util.nl)
773                    .append(" AS ");
774            Util.singleQuoteString(xmlNamedSet.getFormula(), buf);
775            buf.append(Util.nl);
776        }
777    
778        private void postCalcMember(
779            MondrianDef.CalculatedMember[] xmlCalcMembers,
780            int i,
781            final Query queryExp,
782            List<RolapMember> memberList)
783        {
784            MondrianDef.CalculatedMember xmlCalcMember = xmlCalcMembers[i];
785            final Formula formula = queryExp.formulas[i];
786    
787            calculatedMembers = RolapUtil.addElement(calculatedMembers, formula);
788    
789            Member member = formula.getMdxMember();
790    
791            Boolean visible = xmlCalcMember.visible;
792            if (visible == null) {
793                visible = Boolean.TRUE;
794            }
795            member.setProperty(Property.VISIBLE.name, visible);
796    
797            if ((xmlCalcMember.caption != null) &&
798                    xmlCalcMember.caption.length() > 0) {
799                member.setProperty(
800                        Property.CAPTION.name,
801                        xmlCalcMember.caption);
802            }
803    
804            memberList.add((RolapMember) formula.getMdxMember());
805        }
806    
807        private void preCalcMember(
808                MondrianDef.CalculatedMember[] xmlCalcMembers,
809                int j,
810                StringBuilder buf,
811                RolapCube cube,
812                boolean errOnDup) {
813            MondrianDef.CalculatedMember xmlCalcMember = xmlCalcMembers[j];
814    
815            // Lookup dimension
816            final Dimension dimension =
817                    (Dimension) lookupDimension(
818                        new Id.Segment(
819                            xmlCalcMember.dimension,
820                            Id.Quoting.UNQUOTED));
821            if (dimension == null) {
822                throw MondrianResource.instance().CalcMemberHasBadDimension.ex(
823                    xmlCalcMember.dimension, xmlCalcMember.name, getName());
824            }
825    
826            // If we're processing a virtual cube, it's possible that we've
827            // already processed this calculated member because it's
828            // referenced in another measure; in that case, remove it from the
829            // list, since we'll add it back in later; otherwise, in the
830            // non-virtual cube case, throw an exception
831            List<Formula> newCalcMemberList = new ArrayList<Formula>();
832            for (Formula formula : calculatedMembers) {
833                if (formula.getName().equals(xmlCalcMember.name) &&
834                    formula.getMdxMember().getDimension().getName().equals(
835                        dimension.getName())) {
836                    if (errOnDup) {
837                        throw MondrianResource.instance().CalcMemberNotUnique.ex(
838                            Util.makeFqName(dimension, xmlCalcMember.name),
839                            getName());
840                    }
841                    continue;
842                } else {
843                    newCalcMemberList.add(formula);
844                }
845            }
846            calculatedMembers =
847                newCalcMemberList.toArray(new Formula[newCalcMemberList.size()]);
848    
849            // Check this calc member doesn't clash with one earlier in this
850            // batch.
851            for (int k = 0; k < j; k++) {
852                MondrianDef.CalculatedMember xmlCalcMember2 = xmlCalcMembers[k];
853                if (xmlCalcMember2.name.equals(xmlCalcMember.name) &&
854                        xmlCalcMember2.dimension.equals(xmlCalcMember.dimension)) {
855                    throw MondrianResource.instance().CalcMemberNotUnique.ex(
856                        Util.makeFqName(dimension, xmlCalcMember.name),
857                        getName());
858                }
859            }
860    
861            final String memberUniqueName = Util.makeFqName(
862                    dimension.getUniqueName(), xmlCalcMember.name);
863            final MondrianDef.CalculatedMemberProperty[] xmlProperties =
864                    xmlCalcMember.memberProperties;
865            List<String> propNames = new ArrayList<String>();
866            List<String> propExprs = new ArrayList<String>();
867            validateMemberProps(
868                xmlProperties, propNames, propExprs, xmlCalcMember.name);
869    
870            final int measureCount =
871                    cube.measuresHierarchy.getMemberReader().getMemberCount();
872    
873            // Generate SQL.
874            assert memberUniqueName.startsWith("[");
875            buf.append("MEMBER ").append(memberUniqueName)
876                    .append(Util.nl)
877                    .append("  AS ");
878            Util.singleQuoteString(xmlCalcMember.getFormula(), buf);
879    
880            assert propNames.size() == propExprs.size();
881            processFormatStringAttribute(xmlCalcMember, buf);
882    
883            for (int i = 0; i < propNames.size(); i++) {
884                String name = propNames.get(i);
885                String expr = propExprs.get(i);
886                buf.append(",").append(Util.nl);
887                expr = removeSurroundingQuotesIfNumericProperty(name, expr);
888                buf.append(name).append(" = ").append(expr);
889            }
890            // Flag that the calc members are defined against a cube; will
891            // determine the value of Member.isCalculatedInQuery
892            buf.append(",").append(Util.nl).
893                    append(Util.quoteMdxIdentifier(Property.MEMBER_SCOPE.name)).
894                    append(" = 'CUBE'");
895    
896            // Assign the member an ordinal higher than all of the stored measures.
897            if (!propNames.contains(Property.MEMBER_ORDINAL.getName())) {
898                buf.append(",").append(Util.nl).
899                        append(Property.MEMBER_ORDINAL).append(" = ").
900                        append(measureCount + j);
901            }
902            buf.append(Util.nl);
903        }
904    
905        private String removeSurroundingQuotesIfNumericProperty(String name, String expr) {
906            Property prop = Property.lookup(name, false);
907            if (prop != null && prop.getType() == Property.Datatype.TYPE_NUMERIC &&
908                    isSurroundedWithQuotes(expr) && expr.length() > 2) {
909                return expr.substring(1, expr.length() - 1);
910            }
911            return expr;
912        }
913    
914        private boolean isSurroundedWithQuotes(String expr) {
915            return expr.startsWith("\"") && expr.endsWith("\"");
916        }
917    
918        void processFormatStringAttribute(MondrianDef.CalculatedMember xmlCalcMember, StringBuilder buf) {
919            if (xmlCalcMember.formatString != null) {
920                buf.append(",").append(Util.nl)
921                        .append(Property.FORMAT_STRING.name).append(" = ").append(Util.quoteForMdx(xmlCalcMember.formatString));
922            }
923        }
924    
925        /**
926         * Validates an array of member properties, and populates a list of names
927         * and expressions, one for each property.
928         *
929         * @param xmlProperties Array of property definitions.
930         * @param propNames Output array of property names.
931         * @param propExprs Output array of property expressions.
932         * @param memberName Name of member which the properties belong to.
933         */
934        private void validateMemberProps(
935                final MondrianDef.CalculatedMemberProperty[] xmlProperties,
936                List<String> propNames,
937                List<String> propExprs,
938                String memberName) {
939    
940            MemberProperty[] properties = new MemberProperty[xmlProperties.length];
941            for (int i = 0; i < properties.length; i++) {
942                final MondrianDef.CalculatedMemberProperty xmlProperty =
943                        xmlProperties[i];
944                if (xmlProperty.expression == null &&
945                    xmlProperty.value == null)
946                {
947                    throw MondrianResource.instance()
948                        .NeitherExprNorValueForCalcMemberProperty.ex(
949                            xmlProperty.name,
950                            memberName,
951                            getName());
952                }
953                if (xmlProperty.expression != null &&
954                    xmlProperty.value != null)
955                {
956                    throw MondrianResource.instance()
957                        .ExprAndValueForMemberProperty.ex(
958                            xmlProperty.name,
959                            memberName,
960                            getName());
961                }
962                propNames.add(xmlProperty.name);
963                if (xmlProperty.expression != null) {
964                    propExprs.add(xmlProperty.expression);
965                } else {
966                    propExprs.add(Util.quoteForMdx(xmlProperty.value));
967                }
968            }
969        }
970    
971        public RolapSchema getSchema() {
972            return schema;
973        }
974    
975        /**
976         * Returns the named sets of this cube.
977         */
978        public NamedSet[] getNamedSets() {
979            NamedSet[] namedSetsArray = new NamedSet[namedSets.length];
980            for (int i=0; i < namedSets.length; i++) {
981                namedSetsArray[i] = namedSets[i].getNamedSet();
982            }
983            return namedSetsArray;
984        }
985    
986        /**
987         * Returns the schema reader which enforces the appropriate access-control
988         * context. schemaReader is cached, and needs to stay in sync with
989         * any changes to the cube.
990         *
991         * @post return != null
992         * @see #getSchemaReader(Role)
993         */
994        public synchronized SchemaReader getSchemaReader() {
995            if (schemaReader == null) {
996                RoleImpl schemaDefaultRoleImpl = schema.getDefaultRole();
997                RoleImpl roleImpl = schemaDefaultRoleImpl.makeMutableClone();
998                roleImpl.grant(this, Access.ALL);
999                Role role = roleImpl;
1000                schemaReader = new RolapCubeSchemaReader(role);
1001            }
1002            return schemaReader;
1003        }
1004    
1005        public SchemaReader getSchemaReader(Role role) {
1006            if (role == null) {
1007                return getSchemaReader();
1008            } else {
1009                return new RolapCubeSchemaReader(role);
1010            }
1011        }
1012    
1013        MondrianDef.CubeDimension lookup(
1014                MondrianDef.CubeDimension[] xmlDimensions,
1015                String name) {
1016            for (MondrianDef.CubeDimension cd : xmlDimensions) {
1017                if (name.equals(cd.name)) {
1018                    return cd;
1019                }
1020            }
1021            // TODO: this ought to be a fatal error.
1022            return null;
1023        }
1024    
1025        private void init(MondrianDef.CubeDimension[] xmlDimensions) {
1026            for (Dimension dimension1 : dimensions) {
1027                final RolapDimension dimension = (RolapDimension) dimension1;
1028                dimension.init(lookup(xmlDimensions, dimension.getName()));
1029            }
1030            register();
1031        }
1032    
1033        private void register() {
1034            if (isVirtual()) {
1035                return;
1036            }
1037            List<Member> list = new ArrayList<Member>();
1038            Member[] measures = getMeasures();
1039            for (Member measure : measures) {
1040                if (measure instanceof RolapBaseCubeMeasure) {
1041                    list.add(measure);
1042                }
1043            }
1044            RolapBaseCubeMeasure[] storedMeasures =
1045                list.toArray(new RolapBaseCubeMeasure[list.size()]);
1046    
1047            RolapStar star = getStar();
1048            RolapStar.Table table = star.getFactTable();
1049    
1050            // create measures (and stars for them, if necessary)
1051            for (RolapBaseCubeMeasure storedMeasure : storedMeasures) {
1052                table.makeMeasure(storedMeasure);
1053            }
1054        }
1055    
1056        /**
1057         * Returns true if this Cube is either virtual or if the Cube's
1058         * RolapStar is caching aggregates.
1059         *
1060         * @return Whether this Cube's RolapStar should cache aggregations
1061         */
1062        public boolean isCacheAggregations() {
1063            return isVirtual() || star.isCacheAggregations();
1064        }
1065    
1066        /**
1067         * Set if this (non-virtual) Cube's RolapStar should cache
1068         * aggregations.
1069         *
1070         * @param cache Whether this Cube's RolapStar should cache aggregations
1071         */
1072        public void setCacheAggregations(boolean cache) {
1073            if (! isVirtual()) {
1074                star.setCacheAggregations(cache);
1075            }
1076        }
1077    
1078        /**
1079         * Clear the in memory aggregate cache associated with this Cube, but
1080         * only if Disabling Caching has been enabled.
1081         */
1082        public void clearCachedAggregations() {
1083            clearCachedAggregations(false);
1084        }
1085    
1086        /**
1087         * Clear the in memory aggregate cache associated with this Cube.
1088         */
1089        public void clearCachedAggregations(boolean forced) {
1090            if (isVirtual()) {
1091                // TODO:
1092                // Currently a virtual cube does not keep a list of all of its
1093                // base cubes, so we need to iterate through each and flush
1094                // the ones that should be flushed. Could use a CacheControl
1095                // method here.
1096                for (RolapStar star1 : schema.getStars()) {
1097                    // this will only flush the star's aggregate cache if
1098                    // 1) DisableCaching is true or 2) the star's cube has
1099                    // cacheAggregations set to false in the schema.
1100                    star1.clearCachedAggregations(forced);
1101                }
1102            } else {
1103                star.clearCachedAggregations(forced);
1104            }
1105        }
1106    
1107        /**
1108         * Check if there are modifications in the aggregations cache
1109         */
1110        public void checkAggregateModifications() {
1111            if (isVirtual()) {
1112                // TODO:
1113                // Currently a virtual cube does not keep a list of all of its
1114                // base cubes, so we need to iterate through each and flush
1115                // the ones that should be flushed
1116                schema.checkAggregateModifications();
1117            } else {
1118                star.checkAggregateModifications();
1119            }
1120        }
1121        /**
1122         * Push all modifications of the aggregations to global cache,
1123         * so other queries can start using the new cache
1124         */
1125        public void pushAggregateModificationsToGlobalCache() {
1126            if (isVirtual()) {
1127                // TODO:
1128                // Currently a virtual cube does not keep a list of all of its
1129                // base cubes, so we need to iterate through each and flush
1130                // the ones that should be flushed
1131                schema.pushAggregateModificationsToGlobalCache();
1132            } else {
1133                star.pushAggregateModificationsToGlobalCache();
1134            }
1135        }
1136    
1137    
1138    
1139        /**
1140         * Returns this cube's underlying star schema.
1141         */
1142        public RolapStar getStar() {
1143            return star;
1144        }
1145    
1146        private void createUsages(RolapCubeDimension dimension,
1147                MondrianDef.CubeDimension xmlCubeDimension) {
1148            // RME level may not be in all hierarchies
1149            // If one uses the DimensionUsage attribute "level", which level
1150            // in a hierarchy to join on, and there is more than one hierarchy,
1151            // then a HierarchyUsage can not be created for the hierarchies
1152            // that do not have the level defined.
1153            RolapCubeHierarchy[] hierarchies =
1154                (RolapCubeHierarchy[]) dimension.getHierarchies();
1155    
1156            if (hierarchies.length == 1) {
1157                // Only one, so let lower level error checking handle problems
1158                createUsage(hierarchies[0], xmlCubeDimension);
1159    
1160            } else if ((xmlCubeDimension instanceof MondrianDef.DimensionUsage) &&
1161                (((MondrianDef.DimensionUsage) xmlCubeDimension).level != null)) {
1162                // More than one, make sure if we are joining by level, that
1163                // at least one hierarchy can and those that can not are
1164                // not registered
1165                MondrianDef.DimensionUsage du =
1166                    (MondrianDef.DimensionUsage) xmlCubeDimension;
1167    
1168                int cnt = 0;
1169    
1170                for (RolapCubeHierarchy hierarchy : hierarchies) {
1171                    if (getLogger().isDebugEnabled()) {
1172                        getLogger().debug("RolapCube<init>: hierarchy="
1173                            + hierarchy.getName());
1174                    }
1175                    RolapLevel joinLevel = (RolapLevel)
1176                        Util.lookupHierarchyLevel(hierarchy, du.level);
1177                    if (joinLevel == null) {
1178                        continue;
1179                    }
1180                    createUsage(hierarchy, xmlCubeDimension);
1181                    cnt++;
1182                }
1183    
1184                if (cnt == 0) {
1185                    // None of the hierarchies had the level, let lower level
1186                    // detect and throw error
1187                    createUsage(hierarchies[0], xmlCubeDimension);
1188                }
1189    
1190            } else {
1191                // just do it
1192                for (RolapCubeHierarchy hierarchy : hierarchies) {
1193                    if (getLogger().isDebugEnabled()) {
1194                        getLogger().debug("RolapCube<init>: hierarchy="
1195                            + hierarchy.getName());
1196                    }
1197                    createUsage(hierarchy, xmlCubeDimension);
1198                }
1199            }
1200        }
1201    
1202        synchronized void createUsage(
1203                RolapCubeHierarchy hierarchy,
1204                MondrianDef.CubeDimension cubeDim) {
1205    
1206            HierarchyUsage usage = new HierarchyUsage(this, hierarchy, cubeDim);
1207            if (LOGGER.isDebugEnabled()) {
1208                LOGGER.debug("RolapCube.createUsage: "+
1209                    "cube=" +getName()+
1210                    ", hierarchy=" +hierarchy.getName() +
1211                    ", usage=" +usage);
1212            }
1213            for (HierarchyUsage hierUsage : hierarchyUsages) {
1214                if (hierUsage.equals(usage)) {
1215                    getLogger().warn(
1216                        "RolapCube.createUsage: duplicate " + hierUsage);
1217                    return;
1218                }
1219            }
1220            if (getLogger().isDebugEnabled()) {
1221                getLogger().debug("RolapCube.createUsage: register " +usage);
1222            }
1223            this.hierarchyUsages.add(usage);
1224        }
1225    
1226        private synchronized HierarchyUsage getUsageByName(String name) {
1227            for (HierarchyUsage hierUsage : hierarchyUsages) {
1228                if (hierUsage.getFullName().equals(name)) {
1229                    return hierUsage;
1230                }
1231            }
1232            return null;
1233        }
1234    
1235        /**
1236         * A Hierarchy may have one or more HierarchyUsages. This method returns
1237         * an array holding the one or more usages associated with a Hierarchy.
1238         * The HierarchyUsages hierarchyName attribute always equals the name
1239         * attribute of the Hierarchy.
1240         *
1241         * @param hierarchy Hierarchy
1242         * @return an HierarchyUsages array with 0 or more members.
1243         */
1244        public synchronized HierarchyUsage[] getUsages(Hierarchy hierarchy) {
1245            String name = hierarchy.getName();
1246            if (getLogger().isDebugEnabled()) {
1247                getLogger().debug("RolapCube.getUsages: name="+name);
1248            }
1249    
1250            HierarchyUsage hierUsage = null;
1251            List<HierarchyUsage> list = null;
1252    
1253            for (HierarchyUsage hu : hierarchyUsages) {
1254                if (hu.getHierarchyName().equals(name)) {
1255                    if (list != null) {
1256                        if (getLogger().isDebugEnabled()) {
1257                            getLogger().debug("RolapCube.getUsages: "
1258                                + "add list HierarchyUsage.name=" + hu.getName());
1259                        }
1260                        list.add(hu);
1261                    } else if (hierUsage == null) {
1262                        hierUsage = hu;
1263                    } else {
1264                        list = new ArrayList<HierarchyUsage>();
1265                        if (getLogger().isDebugEnabled()) {
1266                            getLogger().debug("RolapCube.getUsages: "
1267                                + "add list hierUsage.name="
1268                                + hierUsage.getName()
1269                                + ", hu.name="
1270                                + hu.getName());
1271                        }
1272                        list.add(hierUsage);
1273                        list.add(hu);
1274                        hierUsage = null;
1275                    }
1276                }
1277            }
1278            if (hierUsage != null) {
1279                return new HierarchyUsage[] { hierUsage };
1280            } else if (list != null) {
1281                if (getLogger().isDebugEnabled()) {
1282                    getLogger().debug("RolapCube.getUsages: return list");
1283                }
1284                return list.toArray(new HierarchyUsage[list.size()]);
1285            } else {
1286                return new HierarchyUsage[0];
1287            }
1288        }
1289    
1290        synchronized HierarchyUsage getFirstUsage(Hierarchy hier) {
1291            HierarchyUsage hierarchyUsage = firstUsageMap.get(hier);
1292            if (hierarchyUsage == null) {
1293                HierarchyUsage[] hierarchyUsages = getUsages(hier);
1294                if (hierarchyUsages.length != 0) {
1295                    hierarchyUsage = hierarchyUsages[0];
1296                    firstUsageMap.put(hier, hierarchyUsage);
1297                }
1298            }
1299            return hierarchyUsage;
1300        }
1301    
1302        /**
1303         * Looks up all of the HierarchyUsages with the same "source" returning
1304         * an array of HierarchyUsage of length 0 or more.
1305         *
1306         * This method is currently only called if an error occurs in lookupChild(),
1307         * so that more information can be displayed in the error log.
1308         *
1309         * @param source Name of shared dimension
1310         * @return array of HierarchyUsage (HierarchyUsage[]) - never null.
1311         */
1312        private synchronized HierarchyUsage[] getUsagesBySource(String source) {
1313            if (getLogger().isDebugEnabled()) {
1314                getLogger().debug("RolapCube.getUsagesBySource: source="+source);
1315            }
1316    
1317            HierarchyUsage hierUsage = null;
1318            List<HierarchyUsage> list = null;
1319    
1320            for (HierarchyUsage hu : hierarchyUsages) {
1321                String s = hu.getSource();
1322                if ((s != null) && s.equals(source)) {
1323                    if (list != null) {
1324                        if (getLogger().isDebugEnabled()) {
1325                            getLogger().debug("RolapCube.getUsagesBySource: "
1326                                + "add list HierarchyUsage.name="
1327                                + hu.getName());
1328                        }
1329                        list.add(hu);
1330                    } else if (hierUsage == null) {
1331                        hierUsage = hu;
1332                    } else {
1333                        list = new ArrayList<HierarchyUsage>();
1334                        if (getLogger().isDebugEnabled()) {
1335                            getLogger().debug("RolapCube.getUsagesBySource: "
1336                                + "add list hierUsage.name="
1337                                + hierUsage.getName()
1338                                + ", hu.name="
1339                                + hu.getName());
1340                        }
1341                        list.add(hierUsage);
1342                        list.add(hu);
1343                        hierUsage = null;
1344                    }
1345                }
1346            }
1347            if (hierUsage != null) {
1348                return new HierarchyUsage[] { hierUsage };
1349            } else if (list != null) {
1350                if (getLogger().isDebugEnabled()) {
1351                    getLogger().debug("RolapCube.getUsagesBySource: return list");
1352                }
1353                return list.toArray(new HierarchyUsage[list.size()]);
1354            } else {
1355                return new HierarchyUsage[0];
1356            }
1357        }
1358    
1359    
1360        /**
1361         * Understand this and you are no longer a novice.
1362         *
1363         * @param dimension Dimension
1364         */