KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > mondrian > rolap > RolapCube


1 /*
2 // $Id: //open/mondrian/src/main/mondrian/rolap/RolapCube.java#92 $
3 // This software is subject to the terms of the Common Public License
4 // Agreement, available at the following URL:
5 // http://www.opensource.org/licenses/cpl.html.
6 // Copyright (C) 2001-2002 Kana Software, Inc.
7 // Copyright (C) 2001-2007 Julian Hyde and others
8 // All Rights Reserved.
9 // You must accept the terms of that agreement to use this software.
10 //
11 // jhyde, 10 August, 2001
12 */

13
14 package mondrian.rolap;
15 import mondrian.olap.*;
16 import mondrian.rolap.agg.AggregationManager;
17 import mondrian.rolap.aggmatcher.ExplicitRules;
18 import mondrian.resource.MondrianResource;
19 import mondrian.mdx.*;
20
21 import org.apache.log4j.Logger;
22 import org.eigenbase.xom.*;
23 import org.eigenbase.xom.Parser;
24
25 import java.lang.reflect.Constructor JavaDoc;
26 import java.util.*;
27 import java.util.Set JavaDoc;
28
29 /**
30  * <code>RolapCube</code> implements {@link Cube} for a ROLAP database.
31  *
32  * @author jhyde
33  * @since 10 August, 2001
34  * @version $Id: //open/mondrian/src/main/mondrian/rolap/RolapCube.java#92 $
35  */

36 public class RolapCube extends CubeBase {
37
38     private static final Logger LOGGER = Logger.getLogger(RolapCube.class);
39
40     private final RolapSchema schema;
41     private final RolapHierarchy measuresHierarchy;
42     /** For SQL generator. Fact table. */
43     final MondrianDef.Relation fact;
44
45     /** To access all measures stored in the fact table. */
46     private final CellReader cellReader;
47
48     /**
49      * Mapping such that
50      * <code>localDimensionOrdinals[dimension.globalOrdinal]</code> is equal to
51      * the ordinal of the dimension in this cube. See
52      * <a HREF="RolapDimension.html#topic_ordinals">dimension ordinals</a>.
53      */

54     private int[] localDimensionOrdinals;
55
56     /** Schema reader which can see this cube and nothing else. */
57     private SchemaReader schemaReader;
58
59     /**
60      * List of calculated members.
61      */

62     private Formula[] calculatedMembers;
63
64     /**
65      * List of named sets.
66      */

67     private Formula[] namedSets;
68
69     /** Contains {@link HierarchyUsage}s for this cube */
70     private final List<HierarchyUsage> hierarchyUsages;
71
72     private RolapStar star;
73     private ExplicitRules.Group aggGroup;
74
75     /**
76      * True if the cube is being created while loading the schema
77      */

78     private boolean load;
79
80     private final Map<Hierarchy, HierarchyUsage> firstUsageMap =
81         new HashMap<Hierarchy, HierarchyUsage>();
82
83     /**
84      * Private constructor used by both normal cubes and virtual cubes.
85      *
86      * @param schema
87      * @param name
88      * @param fact
89      */

90     private RolapCube(RolapSchema schema,
91                       MondrianDef.Schema xmlSchema,
92                       String JavaDoc name,
93                       boolean isCache,
94                       MondrianDef.Relation fact,
95                       MondrianDef.CubeDimension[] dimensions,
96                       boolean load) {
97         super(name, new RolapDimension[dimensions.length + 1]);
98
99         this.schema = schema;
100         this.fact = fact;
101         this.hierarchyUsages = new ArrayList<HierarchyUsage>();
102         this.cellReader = AggregationManager.instance();
103         this.calculatedMembers = new Formula[0];
104         this.namedSets = new Formula[0];
105         this.load = load;
106
107         if (! isVirtual()) {
108             this.star = schema.getRolapStarRegistry().getOrCreateStar(fact);
109             // only set if different from default (so that if two cubes share
110
// the same fact table, either can turn off caching and both are
111
// effected).
112
if (! isCache) {
113                 star.setCacheAggregations(isCache);
114             }
115         }
116
117         if (getLogger().isDebugEnabled()) {
118             if (isVirtual()) {
119                 getLogger().debug("RolapCube<init>: virtual cube=" +this.name);
120             } else {
121                 getLogger().debug("RolapCube<init>: cube=" +this.name);
122             }
123         }
124
125         RolapDimension measuresDimension = new RolapDimension(
126                 schema,
127                 Dimension.MEASURES_NAME,
128                 0,
129                 DimensionType.StandardDimension);
130
131         this.dimensions[0] = measuresDimension;
132
133         this.measuresHierarchy = measuresDimension.newHierarchy(null, false);
134
135         if (!Util.isEmpty(xmlSchema.measuresCaption)) {
136             measuresDimension.setCaption(xmlSchema.measuresCaption);
137             this.measuresHierarchy.setCaption(xmlSchema.measuresCaption);
138         }
139
140         for (int i = 0; i < dimensions.length; i++) {
141             MondrianDef.CubeDimension xmlCubeDimension = dimensions[i];
142             // Look up usages of shared dimensions in the schema before
143
// consulting the XML schema (which may be null).
144
RolapDimension dimension =
145                 getOrCreateDimension(xmlCubeDimension, schema, xmlSchema);
146             if (getLogger().isDebugEnabled()) {
147                 getLogger().debug("RolapCube<init>: dimension="
148                     +dimension.getName());
149             }
150             this.dimensions[i + 1] = dimension;
151
152             if (! isVirtual()) {
153                 createUsages(dimension, xmlCubeDimension);
154             }
155
156         }
157
158         schema.addCube(this);
159     }
160
161     /**
162      * Creates a <code>RolapCube</code> from a regular cube.
163      */

164     RolapCube(RolapSchema schema,
165               MondrianDef.Schema xmlSchema,
166               MondrianDef.Cube xmlCube,
167               boolean load) {
168         this(schema, xmlSchema, xmlCube.name, xmlCube.cache,
169             xmlCube.fact, xmlCube.dimensions, load);
170
171         if (fact.getAlias() == null) {
172             throw Util.newError(
173                     "Must specify alias for fact table of cube " +
174                     getUniqueName());
175         }
176
177
178         // since MondrianDef.Measure and MondrianDef.VirtualCubeMeasure
179
// can not be treated as the same, measure creation can not be
180
// done in a common constructor.
181
RolapLevel measuresLevel =
182             this.measuresHierarchy.newLevel("MeasuresLevel", 0);
183
184         RolapMember measures[] = new RolapMember[xmlCube.measures.length];
185         for (int i = 0; i < xmlCube.measures.length; i++) {
186             MondrianDef.Measure xmlMeasure = xmlCube.measures[i];
187             MondrianDef.Expression measureExp;
188             if (xmlMeasure.column != null) {
189                 if (xmlMeasure.measureExp != null) {
190                     throw MondrianResource.instance().
191                     BadMeasureSource.ex(
192                         xmlCube.name, xmlMeasure.name);
193                 }
194                 measureExp = new MondrianDef.Column(
195                     fact.getAlias(), xmlMeasure.column);
196             } else if (xmlMeasure.measureExp != null) {
197                 measureExp = xmlMeasure.measureExp;
198             } else {
199                 throw MondrianResource.instance().
200                 BadMeasureSource.ex(
201                         xmlCube.name, xmlMeasure.name);
202             }
203
204             // Validate aggregator name. Substitute deprecated "distinct count"
205
// with modern "distinct-count".
206
String JavaDoc aggregator = xmlMeasure.aggregator;
207             if (aggregator.equals("distinct count")) {
208                 aggregator = RolapAggregator.DistinctCount.getName();
209             }
210             final RolapBaseCubeMeasure measure = new RolapBaseCubeMeasure(
211                     this, null, measuresLevel, xmlMeasure.name,
212                     xmlMeasure.formatString, measureExp,
213                 aggregator, xmlMeasure.datatype);
214             measures[i] = measure;
215
216             if (!Util.isEmpty(xmlMeasure.formatter)) {
217                 // there is a special cell formatter class
218
try {
219                     Class JavaDoc<CellFormatter> clazz =
220                         (Class JavaDoc<CellFormatter>)
221                             Class.forName(xmlMeasure.formatter);
222                     Constructor JavaDoc<CellFormatter> ctor = clazz.getConstructor();
223                     CellFormatter cellFormatter = ctor.newInstance();
224                     measure.setFormatter(cellFormatter);
225                 } catch (Exception JavaDoc e) {
226                     e.printStackTrace();
227                 }
228             }
229
230             // Set member's caption, if present.
231
if (!Util.isEmpty(xmlMeasure.caption)) {
232                 // there is a special caption string
233
measure.setProperty(
234                         Property.CAPTION.name,
235                         xmlMeasure.caption);
236             }
237
238             // Set member's visibility, default true.
239
Boolean JavaDoc visible = xmlMeasure.visible;
240             if (visible == null) {
241                 visible = Boolean.TRUE;
242             }
243             measure.setProperty(Property.VISIBLE.name, visible);
244
245             List<String JavaDoc> propNames = new ArrayList<String JavaDoc>();
246             List<String JavaDoc> propExprs = new ArrayList<String JavaDoc>();
247             validateMemberProps(xmlMeasure.memberProperties, propNames,
248                     propExprs, xmlMeasure.name);
249             for (int j = 0; j < propNames.size(); j++) {
250                 String JavaDoc propName = propNames.get(j);
251                 final Object JavaDoc propExpr = propExprs.get(j);
252                 measure.setProperty(propName, propExpr);
253             }
254         }
255
256         this.measuresHierarchy.setMemberReader(new CacheMemberReader(
257                 new MeasureMemberSource(this.measuresHierarchy, measures)));
258         init(xmlCube.dimensions);
259         init(xmlCube);
260
261         checkOrdinals(xmlCube.name, measures, xmlCube.calculatedMembers);
262         loadAggGroup(xmlCube);
263     }
264
265     /**
266      * Creates a <code>RolapCube</code> from a virtual cube.
267      */

268     RolapCube(RolapSchema schema,
269               MondrianDef.Schema xmlSchema,
270               MondrianDef.VirtualCube xmlVirtualCube,
271               boolean load) {
272         this(schema, xmlSchema, xmlVirtualCube.name, true,
273             null, xmlVirtualCube.dimensions, load);
274
275
276         // Since MondrianDef.Measure and MondrianDef.VirtualCubeMeasure cannot
277
// be treated as the same, measure creation cannot be done in a common
278
// constructor.
279
RolapLevel measuresLevel =
280             this.measuresHierarchy.newLevel("MeasuresLevel", 0);
281
282         // Recreate CalculatedMembers, as the original members point to
283
// incorrect dimensional ordinals for the virtual cube.
284
List<RolapVirtualCubeMeasure> origMeasureList =
285             new ArrayList<RolapVirtualCubeMeasure>();
286         List<MondrianDef.CalculatedMember> origCalcMeasureList =
287             new ArrayList<MondrianDef.CalculatedMember>();
288         CubeComparator cubeComparator = new CubeComparator();
289         Map<RolapCube, List<MondrianDef.CalculatedMember>> calculatedMembersMap =
290             new TreeMap<RolapCube, List<MondrianDef.CalculatedMember>>(
291                 cubeComparator);
292         for (MondrianDef.VirtualCubeMeasure xmlMeasure : xmlVirtualCube.measures) {
293             // Lookup a measure in an existing cube.
294
RolapCube cube = schema.lookupCube(xmlMeasure.cubeName);
295             Member[] cubeMeasures = cube.getMeasures();
296             boolean found = false;
297             for (Member cubeMeasure : cubeMeasures) {
298                 if (cubeMeasure.getUniqueName().equals(xmlMeasure.name)) {
299                     found = true;
300                     if (cubeMeasure instanceof RolapCalculatedMember) {
301                         // We have a calulated member! Keep track of which
302
// base cube each calculated member is associated
303
// with, so we can resolve the calculated member
304
// relative to its base cube. We're using a treeMap
305
// to store the mapping to ensure a deterministic
306
// order for the members.
307
MondrianDef.CalculatedMember calcMember =
308                             schema.lookupXmlCalculatedMember(
309                                 xmlMeasure.name, xmlMeasure.cubeName);
310                         if (calcMember == null) {
311                             throw Util.newInternal(
312                                 "Could not find XML Calculated Member '" +
313                                     xmlMeasure.name + "' in XML cube '" +
314                                     xmlMeasure.cubeName + "'");
315                         }
316                         List<MondrianDef.CalculatedMember> memberList =
317                             calculatedMembersMap.get(cube);
318                         if (memberList == null) {
319                             memberList =
320                                 new ArrayList<MondrianDef.CalculatedMember>();
321                         }
322                         memberList.add(calcMember);
323                         origCalcMeasureList.add(calcMember);
324                         calculatedMembersMap.put(cube, memberList);
325                     } else {
326                         // This is the a standard measure. (Don't know
327
// whether it will confuse things that this
328
// measure still points to its 'real' cube.)
329
RolapVirtualCubeMeasure virtualCubeMeasure =
330                             new RolapVirtualCubeMeasure(
331                                 null,
332                                 measuresLevel,
333                                 (RolapStoredMeasure) cubeMeasure);
334
335                         // Set member's visibility, default true.
336
Boolean JavaDoc visible = xmlMeasure.visible;
337                         if (visible == null) {
338                             visible = Boolean.TRUE;
339                         }
340                         virtualCubeMeasure.setProperty(Property.VISIBLE.name,
341                             visible);
342                         origMeasureList.add(virtualCubeMeasure);
343                     }
344                     break;
345                 }
346             }
347             if (!found) {
348                 throw Util.newInternal(
349                     "could not find measure '" + xmlMeasure.name +
350                         "' in cube '" + xmlMeasure.cubeName + "'");
351             }
352         }
353
354         // Must init the dimensions before dealing with calculated members
355
init(xmlVirtualCube.dimensions);
356
357         // Loop through the base cubes containing calculated members
358
// referenced by this virtual cube. Resolve those members relative
359
// to their base cubes first, then resolve them relative to this
360
// cube so the correct dimension ordinals are used
361
List<RolapVirtualCubeMeasure> modifiedMeasureList = new ArrayList<RolapVirtualCubeMeasure>(origMeasureList);
362         for (Object JavaDoc o : calculatedMembersMap.keySet()) {
363             RolapCube baseCube = (RolapCube) o;
364             List<MondrianDef.CalculatedMember> calculatedMemberList =
365                 calculatedMembersMap.get(baseCube);
366             Query queryExp = resolveCalcMembers(
367                 calculatedMemberList.toArray(
368                     new MondrianDef.CalculatedMember[
369                         calculatedMemberList.size()]),
370                 new MondrianDef.NamedSet[0],
371                 baseCube,
372                 false);
373             MeasureFinder measureFinder =
374                 new MeasureFinder(this, baseCube, measuresLevel);
375             queryExp.accept(measureFinder);
376             modifiedMeasureList.addAll(measureFinder.getMeasuresFound());
377         }
378
379         // Add the original calculated members from the base cubes to our
380
// list of calculated members
381
List<MondrianDef.CalculatedMember> calculatedMemberList = new ArrayList<MondrianDef.CalculatedMember>();
382         for (Object JavaDoc o : calculatedMembersMap.keySet()) {
383             RolapCube baseCube = (RolapCube) o;
384             calculatedMemberList.addAll(
385                 calculatedMembersMap.get(baseCube));
386         }
387         calculatedMemberList.addAll(
388             Arrays.asList(xmlVirtualCube.calculatedMembers));
389
390         // Resolve all calculated members relative to this virtual cube,
391
// whose measureHierarchy member reader now contains all base
392
// measures referenced in those calculated members
393
RolapMember[] measures =
394             modifiedMeasureList.toArray(
395                 new RolapMember[modifiedMeasureList.size()]);
396         this.measuresHierarchy.setMemberReader(
397             new CacheMemberReader(
398                 new MeasureMemberSource(this.measuresHierarchy, measures)));
399         createCalcMembersAndNamedSets(
400             calculatedMemberList.toArray(
401                 new MondrianDef.CalculatedMember[
402                     calculatedMemberList.size()]),
403                 new MondrianDef.NamedSet[0],
404                 new ArrayList<Member>(),
405                 new ArrayList<Formula>(),
406                 this,
407                 false);
408
409         // reset the measureHierarchy member reader back to the list of
410
// measures that are only defined on this virtual cube
411
measures =
412             origMeasureList.toArray(
413                 new RolapMember[origMeasureList.size()]);
414         this.measuresHierarchy.setMemberReader(
415             new CacheMemberReader(
416                 new MeasureMemberSource(this.measuresHierarchy, measures)));
417
418         // remove from the calculated members array those members that weren't
419
// originally defined on this virtual cube
420
List<Formula> finalCalcMemberList = new ArrayList<Formula>();
421         for (Formula calculatedMember : calculatedMembers) {
422             if (findOriginalMembers(
423                 calculatedMember,
424                 origCalcMeasureList,
425                 finalCalcMemberList)) {
426                 continue;
427             }
428             findOriginalMembers(
429                 calculatedMember,
430                 Arrays.asList(xmlVirtualCube.calculatedMembers),
431                 finalCalcMemberList);
432         }
433         calculatedMembers =
434             finalCalcMemberList.toArray(
435                 new Formula[finalCalcMemberList.size()]);
436
437         // Note: virtual cubes do not get aggregate
438
}
439
440     private boolean findOriginalMembers(
441         Formula formula,
442         List<MondrianDef.CalculatedMember> calcMemberList,
443         List<Formula> finalCalcMemberList)
444     {
445         for (MondrianDef.CalculatedMember xmlCalcMember : calcMemberList) {
446             Dimension dimension =
447                 (Dimension) lookupDimension(xmlCalcMember.dimension);
448             if (formula.getName().equals(xmlCalcMember.name) &&
449                 formula.getMdxMember().getDimension().getName().equals(
450                     dimension.getName())) {
451                 finalCalcMemberList.add(formula);
452                 return true;
453             }
454         }
455         return false;
456     }
457
458     protected void validate() {
459         // Make sure that tables referenced in dimensions in this cube have
460
// distinct aliases.
461
Map<String JavaDoc, List<Object JavaDoc>> aliases = new HashMap<String JavaDoc, List<Object JavaDoc>>();
462         List<Object JavaDoc> joinPath = new ArrayList<Object JavaDoc>();
463         joinPath.add(this);
464         for (Dimension dimension : dimensions) {
465             final Hierarchy[] hierarchies = dimension.getHierarchies();
466             for (Hierarchy hierarchy : hierarchies) {
467                 MondrianDef.Relation relation =
468                     ((RolapHierarchy) hierarchy).getRelation();
469                 if (relation != null) {
470                     final HierarchyUsage hierarchyUsage =
471                         getFirstUsage(hierarchy);
472                     joinPath.add(hierarchyUsage.getForeignKey());
473                     validateRelation(
474                         aliases,
475                         relation,
476                         joinPath);
477                     joinPath.remove(joinPath.size() - 1);
478                 }
479             }
480         }
481     }
482
483     private void validateRelation(
484         Map<String JavaDoc, List<Object JavaDoc>> aliases,
485         MondrianDef.Relation relation,
486         List<Object JavaDoc> joinPath) {
487         if (relation instanceof MondrianDef.Join) {
488             MondrianDef.Join join = (MondrianDef.Join) relation;
489
490             validateRelation(aliases, join.left, joinPath);
491
492             int saveSize = joinPath.size();
493             flatten(relation, joinPath);
494             joinPath.add(join.leftKey);
495             joinPath.add(join.rightKey);
496             validateRelation(aliases, join.right, joinPath);
497             while (joinPath.size() > saveSize) {
498                 joinPath.remove(joinPath.size() - 1);
499             }
500         } else {
501             String JavaDoc alias;
502             if (relation instanceof MondrianDef.Table) {
503                 MondrianDef.Table table = (MondrianDef.Table) relation;
504                 alias = table.alias;
505                 if (alias == null) {
506                     alias = table.name;
507                 }
508             } else if (relation instanceof MondrianDef.InlineTable) {
509                 MondrianDef.InlineTable table = (MondrianDef.InlineTable) relation;
510                 alias = table.alias;
511             } else {
512                 final MondrianDef.View view = (MondrianDef.View) relation;
513                 alias = view.alias;
514             }
515             joinPath.add(relation);
516             joinPath.add(alias);
517             List joinPath2 = aliases.get(alias);
518             if (joinPath2 == null) {
519                 aliases.put(alias, new ArrayList<Object JavaDoc>(joinPath));
520             } else {
521                 if (!joinPath.equals(joinPath2)) {
522                     throw MondrianResource.instance().DuplicateAliasInCube.ex(
523                         alias, this.getUniqueName());
524                 }
525             }
526             joinPath.remove(joinPath.size() - 1);
527             joinPath.remove(joinPath.size() - 1);
528         }
529     }
530
531     private void flatten(MondrianDef.Relation relation, List<Object JavaDoc> joinPath) {
532         if (relation instanceof MondrianDef.Join) {
533             MondrianDef.Join join = (MondrianDef.Join) relation;
534             flatten(join.left, joinPath);
535             flatten(join.right, joinPath);
536         } else {
537             joinPath.add(relation);
538         }
539     }
540
541     protected Logger getLogger() {
542         return LOGGER;
543     }
544
545     public boolean hasAggGroup() {
546         return (aggGroup != null);
547     }
548     public ExplicitRules.Group getAggGroup() {
549         return aggGroup;
550     }
551     void loadAggGroup(MondrianDef.Cube xmlCube) {
552         aggGroup = ExplicitRules.Group.make(this, xmlCube);
553     }
554
555     /**
556      * Creates a dimension from its XML definition. If the XML definition is
557      * a &lt;DimensionUsage&gt;, and the shared dimension is cached in the
558      * schema, returns that.
559      *
560      * @param xmlCubeDimension XML Dimension or DimensionUsage
561      * @param schema Schema
562      * @param xmlSchema XML Schema
563      * @return A dimension
564      */

565     private RolapDimension getOrCreateDimension(
566         MondrianDef.CubeDimension xmlCubeDimension,
567         RolapSchema schema,
568         MondrianDef.Schema xmlSchema) {
569
570         if (xmlCubeDimension instanceof MondrianDef.DimensionUsage) {
571             MondrianDef.DimensionUsage usage =
572                 (MondrianDef.DimensionUsage) xmlCubeDimension;
573             final RolapHierarchy sharedHierarchy =
574                 schema.getSharedHierarchy(usage.source);
575             if (sharedHierarchy != null) {
576 /*
577 System.out.println("RolapCube.getOrCreateDimension: " +
578 " cube="+getName()+
579 " sharedHierarchy.dimension=" + sharedHierarchy.getDimension().getName());
580 */

581                 return (RolapDimension) sharedHierarchy.getDimension();
582             }
583         }
584         MondrianDef.Dimension xmlDimension =
585             xmlCubeDimension.getDimension(xmlSchema);
586 /*
587         RolapDimension dim = new RolapDimension(schema, this, xmlDimension,
588             xmlCubeDimension);
589 System.out.println("RolapCube.getOrCreateDimension: " +
590 " cube="+getName()+
591 " new dimension=" + dim.getName());
592 return dim;
593 */

594         return new RolapDimension(schema, this, xmlDimension,
595             xmlCubeDimension);
596     }
597
598     /**
599      * Post-initialization, doing things which cannot be done in the
600      * constructor.
601      */

602     private void init(MondrianDef.Cube xmlCube) {
603         // Load calculated members and named sets.
604
// (We cannot do this in the constructor, because
605
// cannot parse the generated query, because the schema has not been
606
// set in the cube at this point.)
607
List<Member> memberList = new ArrayList<Member>();
608         List<Formula> formulaList = new ArrayList<Formula>();
609         createCalcMembersAndNamedSets(
610                 xmlCube.calculatedMembers, xmlCube.namedSets,
611                 memberList, formulaList, this, true);
612     }
613
614
615     /**
616      * Checks whether the ordinals of Measures and calculated measures are
617      * unique.
618      *
619      * @param cubeName the name of the cube (required for error-messages only)
620      * @param measures the stored measures
621      * @param xmlCalcMembers the calculate members (only members of dimension Measures are checked)
622      */

623     private void checkOrdinals(
624             String JavaDoc cubeName,
625             RolapMember measures[],
626             MondrianDef.CalculatedMember[] xmlCalcMembers)
627     {
628         Map<Integer JavaDoc, String JavaDoc> ordinals = new HashMap<Integer JavaDoc, String JavaDoc>();
629
630         // step 1: check the stored measures
631
for (RolapMember measure : measures) {
632             Integer JavaDoc ordinal = measure.getOrdinal();
633             if (!ordinals.containsKey(ordinal)) {
634                 ordinals.put(ordinal, measure.getUniqueName());
635             } else {
636                 throw MondrianResource.instance().MeasureOrdinalsNotUnique.ex(
637                     cubeName,
638                     ordinal.toString(),
639                     ordinals.get(ordinal),
640                     measure.getUniqueName());
641             }
642         }
643
644         // step 2: check the calculated measures
645
for (MondrianDef.CalculatedMember xmlCalcMember : xmlCalcMembers) {
646             if (xmlCalcMember.dimension.equalsIgnoreCase("Measures")) {
647                 MondrianDef.CalculatedMemberProperty[] properties =
648                     xmlCalcMember.memberProperties;
649
650                 for (int j = 0; j < properties.length; j++) {
651                     if (properties[j].name.equals(
652                         Property.MEMBER_ORDINAL.getName())) {
653                         Integer JavaDoc ordinal = new Integer JavaDoc(properties[j].value);
654
655                         final String JavaDoc uname =
656                             "[Measures].[" + xmlCalcMember.name + "]";
657                         if (!ordinals.containsKey(ordinal)) {
658                             ordinals.put(ordinal, uname);
659                         } else {
660                             throw MondrianResource.instance().
661                                 MeasureOrdinalsNotUnique.ex(
662                                 cubeName,
663                                 ordinal.toString(),
664                                 ordinals.get(ordinal),
665                                 uname);
666                         }
667                     }
668                 }
669             }
670         }
671     }
672
673     /**
674      * Adds a collection of calculated members and named sets to this cube.
675      * The members and sets can refer to each other.
676      *
677      * @param xmlCalcMembers XML objects representing members
678      * @param xmlNamedSets Array of XML definition of named set
679      * @param memberList Output list of {@link Member} objects
680      * @param formulaList Output list of {@link Formula} objects
681      * @param cube the cube that the calculated members originate from
682      * @param errOnDups throws an error if a duplicate member is found
683      */

684     private void createCalcMembersAndNamedSets(
685             MondrianDef.CalculatedMember[] xmlCalcMembers,
686             MondrianDef.NamedSet[] xmlNamedSets,
687             List<Member> memberList,
688             List<Formula> formulaList,
689             RolapCube cube,
690             boolean errOnDups) {
691
692         final Query queryExp =
693             resolveCalcMembers(
694                 xmlCalcMembers,
695                 xmlNamedSets,
696                 cube,
697                 errOnDups);
698         if (queryExp == null) {
699             return;
700         }
701
702         // Now pick through the formulas.
703
Util.assertTrue(queryExp.formulas.length ==
704                 xmlCalcMembers.length + xmlNamedSets.length);
705         for (int i = 0; i < xmlCalcMembers.length; i++) {
706             postCalcMember(xmlCalcMembers, i, queryExp, memberList);
707         }
708         for (int i = 0; i < xmlNamedSets.length; i++) {
709             postNamedSet(xmlNamedSets, xmlCalcMembers.length, i, queryExp, formulaList);
710         }
711     }
712
713     private Query resolveCalcMembers(
714         MondrianDef.CalculatedMember[] xmlCalcMembers,
715         MondrianDef.NamedSet[] xmlNamedSets,
716         RolapCube cube,
717         boolean errOnDups)
718     {
719         // If there are no objects to create, our generated SQL will be so
720
// silly, the parser will laugh.
721
if (xmlCalcMembers.length == 0 && xmlNamedSets.length == 0) {
722             return null;
723         }
724
725         StringBuilder JavaDoc buf = new StringBuilder JavaDoc(256);
726         buf.append("WITH").append(Util.nl);
727
728         // Check the members individually, and generate SQL.
729
for (int i = 0; i < xmlCalcMembers.length; i++) {
730             preCalcMember(xmlCalcMembers, i, buf, cube, errOnDups);
731         }
732
733         // Check the named sets individually (for uniqueness) and generate SQL.
734
Set JavaDoc<String JavaDoc> nameSet = new HashSet<String JavaDoc>();
735         for (Formula namedSet : namedSets) {
736             nameSet.add(namedSet.getName());
737         }
738         for (MondrianDef.NamedSet xmlNamedSet : xmlNamedSets) {
739             preNamedSet(xmlNamedSet, nameSet, buf);
740         }
741
742         buf.append("SELECT FROM ")
743             .append(Util.quoteMdxIdentifier(cube.getUniqueName()));
744
745         // Parse and validate this huge MDX query we've created.
746
final String JavaDoc queryString = buf.toString();
747         final Query queryExp;
748         try {
749             RolapConnection conn = schema.getInternalConnection();
750             queryExp = conn.parseQuery(queryString, load);
751         } catch (Exception JavaDoc e) {
752             throw MondrianResource.instance().UnknownNamedSetHasBadFormula.ex(
753                 getUniqueName(), e);
754         }
755         queryExp.resolve();
756         return queryExp;
757     }
758
759     private void postNamedSet(
760             MondrianDef.NamedSet[] xmlNamedSets,
761             final int offset, int i,
762             final Query queryExp,
763             List<Formula> formulaList) {
764         MondrianDef.NamedSet xmlNamedSet = xmlNamedSets[i];
765         Util.discard(xmlNamedSet);
766         Formula formula = queryExp.formulas[offset + i];
767         namedSets = (Formula[]) RolapUtil.addElement(namedSets, formula);
768         formulaList.add(formula);
769     }
770
771     private void preNamedSet(
772             MondrianDef.NamedSet xmlNamedSet,
773             Set JavaDoc<String JavaDoc> nameSet,
774             StringBuilder JavaDoc buf) {
775         if (!nameSet.add(xmlNamedSet.name)) {
776             throw MondrianResource.instance().NamedSetNotUnique.ex(
777                     xmlNamedSet.name, getUniqueName());
778         }
779
780         buf.append("SET ")
781                 .append(Util.makeFqName(xmlNamedSet.name))
782                 .append(Util.nl)
783                 .append(" AS ");
784         Util.singleQuoteString(xmlNamedSet.getFormula(), buf);
785         buf.append(Util.nl);
786     }
787
788     private void postCalcMember(
789             MondrianDef.CalculatedMember[] xmlCalcMembers,
790             int i,
791             final Query queryExp,
792             List<Member> memberList) {
793         MondrianDef.CalculatedMember xmlCalcMember = xmlCalcMembers[i];
794         final Formula formula = queryExp.formulas[i];
795
796         calculatedMembers = (Formula[])
797                 RolapUtil.addElement(calculatedMembers, formula);
798
799         Member member = formula.getMdxMember();
800
801         Boolean JavaDoc visible = xmlCalcMember.visible;
802         if (visible == null) {
803             visible = Boolean.TRUE;
804         }
805         member.setProperty(Property.VISIBLE.name, visible);
806
807         if ((xmlCalcMember.caption != null) &&
808                 xmlCalcMember.caption.length() > 0) {
809             member.setProperty(
810                     Property.CAPTION.name,
811                     xmlCalcMember.caption);
812         }
813
814         memberList.add(formula.getMdxMember());
815     }
816
817     private void preCalcMember(
818             MondrianDef.CalculatedMember[] xmlCalcMembers,
819             int j,
820             StringBuilder JavaDoc buf,
821             RolapCube cube,
822             boolean errOnDup) {
823         MondrianDef.CalculatedMember xmlCalcMember = xmlCalcMembers[j];
824
825         // Lookup dimension
826
final Dimension dimension =
827                 (Dimension) lookupDimension(xmlCalcMember.dimension);
828         if (dimension == null) {
829             throw MondrianResource.instance().CalcMemberHasBadDimension.ex(
830                     xmlCalcMember.dimension, xmlCalcMember.name,
831                     getUniqueName());
832         }
833
834         // If we're processing a virtual cube, it's possible that we've
835
// already processed this calculated member because it's
836
// referenced in another measure; in that case, remove it from the
837
// list, since we'll add it back in later; otherwise, in the
838
// non-virtual cube case, throw an exception
839
List<Formula> newCalcMemberList = new ArrayList<Formula>();
840         for (Formula formula : calculatedMembers) {
841             if (formula.getName().equals(xmlCalcMember.name) &&
842                 formula.getMdxMember().getDimension().getName().equals(
843                     dimension.getName())) {
844                 if (errOnDup) {
845                     throw MondrianResource.instance().CalcMemberNotUnique.ex(
846                         Util.makeFqName(dimension, xmlCalcMember.name),
847                         getUniqueName());
848                 }
849                 continue;
850             } else {
851                 newCalcMemberList.add(formula);
852             }
853         }
854         calculatedMembers =
855             newCalcMemberList.toArray(new Formula[newCalcMemberList.size()]);
856
857         // Check this calc member doesn't clash with one earlier in this
858
// batch.
859
for (int k = 0; k < j; k++) {
860             MondrianDef.CalculatedMember xmlCalcMember2 = xmlCalcMembers[k];
861             if (xmlCalcMember2.name.equals(xmlCalcMember.name) &&
862                     xmlCalcMember2.dimension.equals(xmlCalcMember.dimension)) {
863                 throw MondrianResource.instance().CalcMemberNotUnique.ex(
864                         Util.makeFqName(dimension, xmlCalcMember.name),
865                         getUniqueName());
866             }
867         }
868
869         final String JavaDoc memberUniqueName = Util.makeFqName(
870                 dimension.getUniqueName(), xmlCalcMember.name);
871         final MondrianDef.CalculatedMemberProperty[] xmlProperties =
872                 xmlCalcMember.memberProperties;
873         List<String JavaDoc> propNames = new ArrayList<String JavaDoc>();
874         List<String JavaDoc> propExprs = new ArrayList<String JavaDoc>();
875         validateMemberProps(xmlProperties, propNames, propExprs,
876                 xmlCalcMember.name);
877
878         final int measureCount =
879                 cube.measuresHierarchy.getMemberReader().getMemberCount();
880
881         // Generate SQL.
882
assert memberUniqueName.startsWith("[");
883         buf.append("MEMBER ").append(memberUniqueName)
884                 .append(Util.nl)
885                 .append(" AS ");
886         Util.singleQuoteString(xmlCalcMember.getFormula(), buf);
887
888         assert propNames.size() == propExprs.size();
889         processFormatStringAttribute(xmlCalcMember, buf);
890
891         for (int i = 0; i < propNames.size(); i++) {
892             String JavaDoc name = propNames.get(i);
893             String JavaDoc expr = propExprs.get(i);
894             buf.append(",").append(Util.nl)
895                     .append(name).append(" = ").append(expr);
896         }
897         // Flag that the calc members are defined against a cube; will
898
// determine the value of Member.isCalculatedInQuery
899
buf.append(",").append(Util.nl).
900                 append(Util.quoteMdxIdentifier(Property.MEMBER_SCOPE.name)).
901                 append(" = 'CUBE'");
902
903         // Assign the member an ordinal higher than all of the stored measures.
904
if (!propNames.contains(Property.MEMBER_ORDINAL.getName())) {
905             buf.append(",").append(Util.nl).
906                     append(Property.MEMBER_ORDINAL).append(" = ").
907                     append(measureCount + j);
908         }
909         buf.append(Util.nl);
910     }
911
912     void processFormatStringAttribute(MondrianDef.CalculatedMember xmlCalcMember, StringBuilder JavaDoc buf) {
913         if (xmlCalcMember.formatString != null) {
914             buf.append(",").append(Util.nl)
915                     .append(Property.FORMAT_STRING.name).append(" = ").append(Util.quoteForMdx(xmlCalcMember.formatString));
916         }
917     }
918
919     /**
920      * Validates an array of member properties, and populates a list of names
921      * and expressions, one for each property.
922      *
923      * @param xmlProperties Array of property definitions.
924      * @param propNames Output array of property names.
925      * @param propExprs Output array of property expressions.
926      * @param memberName Name of member which the properties belong to.
927      */

928     private void validateMemberProps(
929             final MondrianDef.CalculatedMemberProperty[] xmlProperties,
930             List<String JavaDoc> propNames,
931             List<String JavaDoc> propExprs,
932             String JavaDoc memberName) {
933
934         MemberProperty[] properties = new MemberProperty[xmlProperties.length];
935         for (int i = 0; i < properties.length; i++) {
936             final MondrianDef.CalculatedMemberProperty xmlProperty =
937                     xmlProperties[i];
938             if (xmlProperty.expression == null &&
939                 xmlProperty.value == null) {
940
941                 throw MondrianResource.instance()
942                     .NeitherExprNorValueForCalcMemberProperty.ex(
943                         xmlProperty.name,
944                         memberName,
945                         getUniqueName());
946             }
947             if (xmlProperty.expression != null &&
948                 xmlProperty.value != null) {
949
950                 throw MondrianResource.instance()
951                     .ExprAndValueForMemberProperty.ex(
952                         xmlProperty.name,
953                         memberName,
954                         getUniqueName());
955             }
956             propNames.add(xmlProperty.name);
957             if (xmlProperty.expression != null) {
958                 propExprs.add(xmlProperty.expression);
959             } else {
960                 propExprs.add(Util.quoteForMdx(xmlProperty.value));
961             }
962         }
963     }
964
965
966     public Schema getSchema() {
967         return schema;
968     }
969
970     /**
971      * Returns the schema reader which enforces the appropriate access-control
972      * context.
973      *
974      * @post return != null
975      * @see #getSchemaReader(Role)
976      */

977     public synchronized SchemaReader getSchemaReader() {
978         if (schemaReader == null) {
979             schemaReader = getSchemaReader(null);
980         }
981         return schemaReader;
982     }
983
984     public SchemaReader getSchemaReader(Role role) {
985         if (role == null) {
986             role = schema.getDefaultRole().makeMutableClone();
987             role.grant(this, Access.ALL);
988         }
989         return new RolapCubeSchemaReader(role);
990     }
991
992     MondrianDef.CubeDimension lookup(
993             MondrianDef.CubeDimension[] xmlDimensions,
994             String JavaDoc name) {
995         for (MondrianDef.CubeDimension cd : xmlDimensions) {
996             if (name.equals(cd.name)) {
997                 return cd;
998             }
999         }
1000        // TODO: this ought to be a fatal error.
1001
return null;
1002    }
1003
1004    private void init(MondrianDef.CubeDimension[] xmlDimensions) {
1005        int max = -1;
1006        for (Dimension dimension1 : dimensions) {
1007            final RolapDimension dimension = (RolapDimension) dimension1;
1008/*
1009System.out.println("RolapCube.init: "+
1010"cube=" +getName() +
1011", dimension=" +dimension.getName());
1012*/

1013            dimension.init(this, lookup(xmlDimensions, dimension.getName()));
1014            max = Math.max(max, dimension.getGlobalOrdinal());
1015        }
1016        this.localDimensionOrdinals = new int[max + 1];
1017        Arrays.fill(localDimensionOrdinals, -1);
1018        for (int i = 0; i < dimensions.length; i++) {
1019            final RolapDimension dimension = (RolapDimension) dimensions[i];
1020            final int globalOrdinal = dimension.getGlobalOrdinal();
1021/*
1022When the same Dimension is in two or more DimensionUsages, then this
1023assert is not true.
1024            Util.assertTrue(
1025                    localDimensionOrdinals[globalOrdinal] == -1,
1026                    "duplicate dimension globalOrdinal " + globalOrdinal);
1027*/

1028            localDimensionOrdinals[globalOrdinal] = i;
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        // create dimension tables
1056
Dimension[] dimensions = this.getDimensions();
1057        for (Dimension dimension : dimensions) {
1058            registerDimension(dimension);
1059        }
1060    }
1061
1062    int getOrdinal(int globalOrdinal) {
1063        return this.localDimensionOrdinals[globalOrdinal];
1064    }
1065
1066    CellReader getCellReader() {
1067        return this.cellReader;
1068    }
1069
1070    /**
1071     * Returns true if this Cube is either virtual or if the Cube's
1072     * RolapStar is caching aggregates.
1073     *
1074     * @return Whether this Cube's RolapStar should cache aggregations
1075     */

1076    public boolean isCacheAggregations() {
1077        return isVirtual() || star.isCacheAggregations();
1078    }
1079
1080    /**
1081     * Set if this (non-virtual) Cube's RolapStar should cache
1082     * aggregations.
1083     *
1084     * @param cache Whether this Cube's RolapStar should cache aggregations
1085     */

1086    public void setCacheAggregations(boolean cache) {
1087        if (! isVirtual()) {
1088            star.setCacheAggregations(cache);
1089        }
1090    }
1091
1092    /**
1093     * Clear the in memory aggregate cache associated with this Cube, but
1094     * only if Disabling Caching has been enabled.
1095     */

1096    public void clearCachedAggregations() {
1097        if (isVirtual()) {
1098            // TODO:
1099
// Currently a virtual cube does not keep a list of all of its
1100
// base cubes, so we need to iterate through each and flush
1101
// the ones that should be flushed
1102
schema.flushRolapStarCaches(false);
1103        } else {
1104            star.clearCachedAggregations(false);
1105        }
1106    }
1107
1108    /**
1109     * Check if there are modifications in the aggregations cache
1110     */

1111    public void checkAggregateModifications() {
1112        if (isVirtual()) {
1113            // TODO:
1114
// Currently a virtual cube does not keep a list of all of its
1115
// base cubes, so we need to iterate through each and flush
1116
// the ones that should be flushed
1117
schema.checkAggregateModifications();
1118        } else {
1119            star.checkAggregateModifications();
1120        }
1121    }
1122    /**
1123     * Push all modifications of the aggregations to global cache,
1124     * so other queries can start using the new cache
1125     */

1126    public void pushAggregateModificationsToGlobalCache() {
1127        if (isVirtual()) {
1128            // TODO:
1129
// Currently a virtual cube does not keep a list of all of its
1130
// base cubes, so we need to iterate through each and flush
1131
// the ones that should be flushed
1132
schema.pushAggregateModificationsToGlobalCache();
1133        } else {
1134            star.pushAggregateModificationsToGlobalCache();
1135        }
1136    }
1137
1138
1139
1140    /**
1141     * Returns this cube's underlying star schema.
1142     */

1143    public RolapStar getStar() {
1144        return star;
1145    }
1146
1147    private void createUsages(RolapDimension dimension,
1148            MondrianDef.CubeDimension xmlCubeDimension) {
1149        // RME level may not be in all hierarchies
1150
// If one uses the DimensionUsage attribute "level", which level
1151
// in a hierarchy to join on, and there is more than one hierarchy,
1152
// then a HierarchyUsage can not be created for the hierarchies
1153
// that do not have the level defined.
1154
RolapHierarchy[] hierarchies =
1155            (RolapHierarchy[]) dimension.getHierarchies();
1156
1157        if (hierarchies.length == 1) {
1158            // Only one, so let lower level error checking handle problems
1159
createUsage(hierarchies[0], xmlCubeDimension);
1160
1161        } else if ((xmlCubeDimension instanceof MondrianDef.DimensionUsage) &&
1162            (((MondrianDef.DimensionUsage) xmlCubeDimension).level != null)) {
1163            // More than one, make sure if we are joining by level, that
1164
// at least one hierarchy can and those that can not are
1165
// not registered
1166
MondrianDef.DimensionUsage du =
1167                (MondrianDef.DimensionUsage) xmlCubeDimension;
1168
1169            int cnt = 0;
1170
1171            for (RolapHierarchy hierarchy : hierarchies) {
1172                if (getLogger().isDebugEnabled()) {
1173                    getLogger().debug("RolapCube<init>: hierarchy="
1174                        + hierarchy.getName());
1175                }
1176                RolapLevel joinLevel = (RolapLevel)
1177                    Util.lookupHierarchyLevel(hierarchy, du.level);
1178                if (joinLevel == null) {
1179                    continue;
1180                }
1181                createUsage(hierarchy, xmlCubeDimension);
1182                cnt++;
1183            }
1184
1185            if (cnt == 0) {
1186                // None of the hierarchies had the level, let lower level
1187
// detect and throw error
1188
createUsage(hierarchies[0], xmlCubeDimension);
1189            }
1190
1191        } else {
1192            // just do it
1193
for (RolapHierarchy hierarchy : hierarchies) {
1194                if (getLogger().isDebugEnabled()) {
1195                    getLogger().debug("RolapCube<init>: hierarchy="
1196                        + hierarchy.getName());
1197                }
1198                createUsage(hierarchy, xmlCubeDimension);
1199            }
1200        }
1201    }
1202
1203    synchronized void createUsage(
1204            RolapHierarchy hierarchy,
1205            MondrianDef.CubeDimension cubeDim) {
1206
1207        HierarchyUsage usage = new HierarchyUsage(this, hierarchy, cubeDim);
1208        if (LOGGER.isDebugEnabled()) {
1209            LOGGER.debug("RolapCube.createUsage: "+
1210                "cube=" +getName()+
1211                ", hierarchy=" +hierarchy.getName() +
1212                ", usage=" +usage);
1213        }
1214        for (HierarchyUsage hierUsage : hierarchyUsages) {
1215            if (hierUsage.equals(usage)) {
1216                getLogger().warn(
1217                    "RolapCube.createUsage: duplicate " + hierUsage);
1218                return;
1219            }
1220        }
1221        if (getLogger().isDebugEnabled()) {
1222            getLogger().debug("RolapCube.createUsage: register " +usage);
1223        }
1224        this.hierarchyUsages.add(usage);
1225    }
1226
1227    private synchronized HierarchyUsage getUsageByName(String JavaDoc name) {
1228        for (HierarchyUsage hierUsage : hierarchyUsages) {
1229            if (hierUsage.getFullName().equals(name)) {
1230                return hierUsage;
1231            }
1232        }
1233        return null;
1234    }
1235
1236    /**
1237     * A Hierarchy may have one or more HierarchyUsages. This method returns
1238     * an array holding the one or more usages associated with a Hierarchy.
1239     * The HierarchyUsages hierarchyName attribute always equals the name
1240     * attribute of the Hierarchy.
1241     *
1242     * @param hierarchy Hierarchy
1243     * @return an HierarchyUsages array with 0 or more members.
1244     */

1245    public synchronized HierarchyUsage[] getUsages(Hierarchy hierarchy) {
1246        String JavaDoc name = hierarchy.getName();
1247        if (getLogger().isDebugEnabled()) {
1248            getLogger().debug("RolapCube.getUsages: name="+name);
1249        }
1250
1251        HierarchyUsage hierUsage = null;
1252        List<HierarchyUsage> list = null;
1253
1254        for (HierarchyUsage hu : hierarchyUsages) {
1255            if (hu.getHierarchyName().equals(name)) {
1256                if (list != null) {
1257                    if (getLogger().isDebugEnabled()) {
1258                        getLogger().debug("RolapCube.getUsages: "
1259                            + "add list HierarchyUsage.name=" + hu.getName());
1260                    }
1261                    list.add(hu);
1262                } else if (hierUsage == null) {
1263                    hierUsage = hu;
1264                } else {
1265                    list = new ArrayList<HierarchyUsage>();
1266                    if (getLogger().isDebugEnabled()) {
1267                        getLogger().debug("RolapCube.getUsages: "
1268                            + "add list hierUsage.name="
1269                            + hierUsage.getName()
1270                            + ", hu.name="
1271                            + hu.getName());
1272                    }
1273                    list.add(hierUsage);
1274                    list.add(hu);
1275                    hierUsage = null;
1276                }
1277            }
1278        }
1279        if (hierUsage != null) {
1280            return new HierarchyUsage[] { hierUsage };
1281        } else if (list != null) {
1282            if (getLogger().isDebugEnabled()) {
1283                getLogger().debug("RolapCube.getUsages: return list");
1284            }
1285            return list.toArray(new HierarchyUsage[list.size()]);
1286        } else {
1287            return new HierarchyUsage[0];
1288        }
1289    }
1290
1291    synchronized HierarchyUsage getFirstUsage(Hierarchy hier) {
1292        HierarchyUsage hierarchyUsage = firstUsageMap.get(hier);
1293        if (hierarchyUsage == null) {
1294            HierarchyUsage[] hierarchyUsages = getUsages(hier);
1295            if (hierarchyUsages.length != 0) {
1296                hierarchyUsage = hierarchyUsages[0];
1297                firstUsageMap.put(hier, hierarchyUsage);
1298            }
1299        }
1300        return hierarchyUsage;
1301    }
1302
1303    /**
1304     * Looks up all of the HierarchyUsages with the same "source" returning
1305     * an array of HierarchyUsage of length 0 or more.
1306     *
1307     * @param source
1308     * @return array of HierarchyUsage (HierarchyUsage[]) - never null.
1309     */

1310    synchronized HierarchyUsage[] getUsagesBySource(String JavaDoc source) {
1311        if (getLogger().isDebugEnabled()) {
1312            getLogger().debug("RolapCube.getUsagesBySource: source="+source);
1313        }
1314
1315        HierarchyUsage hierUsage = null;
1316        List<HierarchyUsage> list = null;
1317
1318        for (HierarchyUsage hu : hierarchyUsages) {
1319            String JavaDoc s = hu.getSource();
1320            if ((s != null) && s.equals(source)) {
1321                if (list != null) {
1322                    if (getLogger().isDebugEnabled()) {
1323                        getLogger().debug("RolapCube.getUsagesBySource: "
1324                            + "add list HierarchyUsage.name="
1325                            + hu.getName());
1326                    }
1327                    list.add(hu);
1328                } else if (hierUsage == null) {
1329                    hierUsage = hu;
1330                } else {
1331                    list = new ArrayList<HierarchyUsage>();
1332                    if (getLogger().isDebugEnabled()) {
1333                        getLogger().debug("RolapCube.getUsagesBySource: "
1334                            + "add list hierUsage.name="
1335                            + hierUsage.getName()
1336                            + ", hu.name="
1337                            + hu.getName());
1338                    }
1339                    list.add(hierUsage);
1340                    list.add(hu);
1341                    hierUsage = null;
1342                }
1343            }
1344        }
1345        if (hierUsage != null) {
1346            return new HierarchyUsage[] { hierUsage };
1347        } else if (list != null) {
1348            if (getLogger().isDebugEnabled()) {
1349                getLogger().debug("RolapCube.getUsagesBySource: return list");
1350            }
1351            return list.toArray(new HierarchyUsage[list.size()]);
1352        } else {
1353            return new HierarchyUsage[0];
1354        }
1355    }
1356
1357
1358    /**
1359     * Understand this and you are no longer a novice.
1360     *
1361     * @param dimension
1362     */

1363    void registerDimension(Dimension dimension) {
1364        RolapStar star = getStar();
1365
1366        Hierarchy[] hierarchies = dimension.getHierarchies();
1367
1368        for (Hierarchy hierarchy1 : hierarchies) {
1369            RolapHierarchy hierarchy = (RolapHierarchy) hierarchy1;
1370
1371            MondrianDef.Relation relation = hierarchy.getRelation();
1372            if (relation == null) {
1373                continue; // e.g. [Measures] hierarchy
1374
}
1375            RolapLevel[] levels = (RolapLevel[]) hierarchy.getLevels();
1376
1377            HierarchyUsage[] hierarchyUsages = getUsages(hierarchy);
1378            if (hierarchyUsages.length == 0) {
1379                if (getLogger().isDebugEnabled()) {
1380                    StringBuilder JavaDoc buf = new StringBuilder JavaDoc(64);
1381                    buf.append("RolapCube.registerDimension: ");
1382                    buf.append("hierarchyUsages == null for cube=\"");
1383                    buf.append(this.name);
1384                    buf.append("\", hierarchy=\"");
1385                    buf.append(hierarchy.getName());
1386                    buf.append("\"");
1387                    getLogger().debug(buf.toString());
1388                }
1389                continue;
1390            }
1391
1392            for (HierarchyUsage hierarchyUsage : hierarchyUsages) {
1393                String JavaDoc usagePrefix = hierarchyUsage.getUsagePrefix();
1394                RolapStar.Table table = star.getFactTable();
1395
1396                String JavaDoc levelName = hierarchyUsage.getLevelName();
1397
1398                // RME
1399
// If a DimensionUsage has its level attribute set, then
1400
// one wants joins to occur at that level and not below (not
1401
// at a finer level), i.e., if you have levels: Year, Quarter,
1402
// Month, and Day, and the level attribute is set to Month, the
1403
// you do not want aggregate joins to include the Day level.
1404
// By default, it is the lowest level that the fact table
1405
// joins to, the Day level.
1406
// To accomplish this, we reorganize the relation and then
1407
// copy it (so that elsewhere the original relation can
1408
// still be used), and finally, clip off those levels below
1409
// the DimensionUsage level attribute.
1410
// Note also, if the relation (MondrianDef.Relation) is not
1411
// a MondrianDef.Join, i.e., the dimension is not a snowflake,
1412
// there is a single dimension table, then this is currently
1413
// an unsupported configuation and all bets are off.
1414
if (relation instanceof MondrianDef.Join) {
1415
1416                    // RME
1417
// take out after things seem to be working
1418
MondrianDef.Relation relationTmp1 = relation;
1419
1420                    relation = reorder(relation, levels);
1421
1422                    if (relation == null && getLogger().isDebugEnabled()) {
1423                        getLogger().debug(
1424                            "RolapCube.registerDimension: after reorder relation==null");
1425                        getLogger().debug(
1426                            "RolapCube.registerDimension: reorder relationTmp1="
1427                                + format(relationTmp1));
1428                    }
1429                }
1430
1431                MondrianDef.Relation relationTmp2 = relation;
1432
1433                if (levelName != null) {
1434                    //System.out.println("RolapCube.registerDimension: levelName=" +levelName);
1435
// When relation is a table, this does nothing. Otherwise
1436
// it tries to arrange the joins so that the fact table
1437
// in the RolapStar will be joining at the lowest level.
1438
//
1439

1440                    // Make sure the level exists
1441
RolapLevel level =
1442                        RolapLevel.lookupLevel(levels, levelName);
1443                    if (level == null) {
1444                        StringBuilder JavaDoc buf = new StringBuilder JavaDoc(64);
1445                        buf.append("For cube \"");
1446                        buf.append(getName());
1447                        buf.append("\" and HierarchyUsage [");
1448                        buf.append(hierarchyUsage);
1449                        buf.append("], there is no level with given");
1450                        buf.append(" level name \"");
1451                        buf.append(levelName);
1452                        buf.append("\"");
1453                        throw Util.newInternal(buf.toString());
1454                    }
1455
1456                    // If level has child, not the lowest level, then snip
1457
// relation between level and its child so that
1458
// joins do not include the lower levels.
1459
// If the child level is null, then the DimensionUsage
1460
// level attribute was simply set to the default, lowest
1461
// level and we do nothing.
1462
if (relation instanceof MondrianDef.Join) {
1463                        RolapLevel childLevel =
1464                            (RolapLevel) level.getChildLevel();
1465                        if (childLevel != null) {
1466                            String JavaDoc tableName = childLevel.getTableName();
1467                            if (tableName != null) {
1468                                relation = snip(relation, tableName);
1469
1470                                if (relation == null &&
1471                                    getLogger().isDebugEnabled()) {
1472                                    getLogger().debug(
1473                                        "RolapCube.registerDimension: after snip relation==null");
1474                                    getLogger().debug(
1475                                        "RolapCube.registerDimension: snip relationTmp2="
1476                                            + format(relationTmp2));
1477                                }
1478                            }
1479                        }
1480                    }
1481
1482                }
1483
1484                // cube and dimension usage are in different tables
1485
if (!relation.equals(table.getRelation())) {
1486                    // HierarchyUsage should have checked this.
1487
if (hierarchyUsage.getForeignKey() == null) {
1488                        throw MondrianResource.instance()
1489                            .HierarchyMustHaveForeignKey.ex(
1490                            hierarchy.getName(), getName());
1491                    }
1492                    // jhyde: check is disabled until we handle <View> correctly
1493
if (false &&
1494                        !star.getFactTable()
1495                            .containsColumn(hierarchyUsage.getForeignKey())) {
1496                        throw MondrianResource.instance()
1497                            .HierarchyInvalidForeignKey.ex(
1498                            hierarchyUsage.getForeignKey(),
1499                            hierarchy.getName(),
1500                            getName());
1501                    }
1502                    // parameters:
1503
// fact table,
1504
// fact table foreign key,
1505
MondrianDef.Column column =
1506                        new MondrianDef.Column(table.getAlias(),
1507                            hierarchyUsage.getForeignKey());
1508                    // parameters:
1509
// left column
1510
// right column
1511
RolapStar.Condition joinCondition =
1512                        new RolapStar.Condition(column,
1513                            hierarchyUsage.getJoinExp());
1514
1515                    table = table.addJoin(relation, joinCondition);
1516                }
1517
1518                // The parent Column is used so that non-shared dimensions
1519
// which use the fact table (not a separate dimension table)
1520
// can keep a record of what other columns are in the
1521
// same set of levels.
1522
RolapStar.Column parentColumn = null;
1523
1524                //RME
1525
// If the level name is not null, then we need only register
1526
// those columns for that level and above.
1527
if (levelName != null) {
1528                    for (RolapLevel level : levels) {
1529                        if (level.getKeyExp() != null) {
1530                            parentColumn = makeColumns(table,
1531                                level, parentColumn, usagePrefix);
1532                        }
1533                        if (levelName.equals(level.getName())) {
1534                            break;
1535                        }
1536                    }
1537                } else {
1538                    // This is the normal case, no level attribute so register
1539
// all columns.
1540
for (RolapLevel level : levels) {
1541                        if (level.getKeyExp() != null) {
1542                            parentColumn = makeColumns(table,
1543                                level, parentColumn, usagePrefix);
1544                        }
1545                    }
1546                }
1547            }
1548        }
1549    }
1550
1551    /**
1552     * Adds a column to the appropriate table in the {@link RolapStar}.
1553     * Note that if the RolapLevel has a table attribute, then the associated
1554     * column needs to be associated with that table.
1555     */

1556    protected RolapStar.Column makeColumns(
1557            RolapStar.Table table,
1558            RolapLevel level,
1559            RolapStar.Column parentColumn,
1560            String JavaDoc usagePrefix) {
1561
1562        // If there is a table name, then first see if the table name is the
1563
// table parameter's name or alias and, if so, simply add the column
1564
// to that table. On the other hand, find the ancestor of the table
1565
// parameter and if found, then associate the new column with
1566
// that table.
1567
// Lastly, if the ancestor can not be found, i.e., there is no table
1568
// with the level's table name, what to do. Here we simply punt and
1569
// associated the new column with the table parameter which might
1570
// be an error. We do issue a warning in any case.
1571
String JavaDoc tableName = level.getTableName();
1572        if (tableName != null) {
1573            if (table.getAlias().equals(tableName)) {
1574                parentColumn = table.makeColumns(this, level,
1575                                            parentColumn, usagePrefix);
1576            } else if (table.equalsTableName(tableName)) {
1577                parentColumn = table.makeColumns(this, level,
1578                                            parentColumn, usagePrefix);
1579            } else {
1580                RolapStar.Table t = table.findAncestor(tableName);
1581                if (t != null) {
1582                    parentColumn = t.makeColumns(this, level,
1583                                            parentColumn, usagePrefix);
1584                } else {
1585                    // Issue warning and keep going.
1586
StringBuilder JavaDoc buf = new StringBuilder JavaDoc(64);
1587                    buf.append("RolapCube.makeColumns: for cube \"");
1588                    buf.append(getName());
1589                    buf.append("\" the Level \"");
1590                    buf.append(level.getName());
1591                    buf.append("\" has a table name attribute \"");
1592                    buf.append(tableName);
1593                    buf.append("\" but the associated RolapStar does not");
1594                    buf.append(" have a table with that name.");
1595                    getLogger().warn(buf.toString());
1596
1597                    parentColumn = table.makeColumns(this, level,
1598                                            parentColumn, usagePrefix);
1599                }
1600            }
1601        } else {
1602            // level's expr is not a MondrianDef.Column (this is used by tests)
1603
// or there is no table name defined
1604
parentColumn = table.makeColumns(this, level,
1605                                            parentColumn, usagePrefix);
1606        }
1607
1608        return parentColumn;
1609    }
1610
1611    ///////////////////////////////////////////////////////////////////////////
1612
//
1613
// The following code deals with handling the DimensionUsage level attribute
1614
// and snowflake dimensions only.
1615
//
1616

1617    /**
1618     * Formats a {@link MondrianDef.Relation} indenting joins for
1619     * readability.
1620     *
1621     * @param relation
1622     */

1623    private static String JavaDoc format(MondrianDef.Relation relation) {
1624        StringBuilder JavaDoc buf = new StringBuilder JavaDoc();
1625        format(relation, buf, "");
1626        return buf.toString();
1627    }
1628
1629    private static void format(
1630            MondrianDef.Relation relation,
1631            StringBuilder JavaDoc buf, String JavaDoc indent) {
1632        if (relation instanceof MondrianDef.Table) {
1633            MondrianDef.Table table = (MondrianDef.Table) relation;
1634
1635            buf.append(indent);
1636            buf.append(table.name);
1637            if (table.alias != null) {
1638                buf.append('(');
1639                buf.append(table.alias);
1640                buf.append(')');
1641            }
1642            buf.append(Util.nl);
1643        } else {
1644            MondrianDef.Join join = (MondrianDef.Join) relation;
1645            String JavaDoc subindent = indent + " ";
1646
1647            buf.append(indent);
1648            //buf.append(join.leftAlias);
1649
buf.append(join.getLeftAlias());
1650            buf.append('.');
1651            buf.append(join.leftKey);
1652            buf.append('=');
1653            buf.append(join.getRightAlias());
1654            //buf.append(join.rightAlias);
1655
buf.append('.');
1656            buf.append(join.rightKey);
1657            buf.append(Util.nl);
1658            format(join.left, buf, subindent);
1659            format(join.right, buf, indent);
1660        }
1661    }
1662
1663    /**
1664     * This class is used to associate a MondrianDef.Table with its associated
1665     * level's depth. This is used to rank tables in a snowflake so that
1666     * the table with the lowest rank, level depth, is furthest from
1667     * the base fact table in the RolapStar.
1668     *
1669     */

1670    private static class RelNode {
1671
1672        /**
1673         * Find a RelNode by table name or, if that fails, by table alias
1674         * from a map of RelNodes.
1675         *
1676         * @param table
1677         * @param map
1678         */

1679        private static RelNode lookup(MondrianDef.Table table, Map<String JavaDoc, RelNode> map) {
1680            RelNode relNode = map.get(table.name);
1681            if ((relNode == null) && (table.alias != null)) {
1682                relNode = map.get(table.alias);
1683            }
1684            return relNode;
1685        }
1686
1687        private int depth;
1688        private String JavaDoc alias;
1689        private MondrianDef.Table table;
1690        RelNode(String JavaDoc alias, int depth) {
1691            this.alias = alias;
1692            this.depth = depth;
1693        }
1694
1695    }
1696
1697    /**
1698     * Attempts to transform a {@link MondrianDef.Relation}
1699     * into the "canonical" form.
1700     *
1701     * <p>What is the canonical form? It is only relevant
1702     * when the relation is a snowflake (nested joins), not simply a table.
1703     * The canonical form has lower levels to the left of higher levels (Day
1704     * before Month before Quarter before Year) and the nested joins are always
1705     * on the right side of the parent join.
1706     *
1707     * <p>The canonical form is (using a Time dimension example):
1708     * <pre>
1709     * |
1710     * ----------------
1711     * | |
1712     * Day --------------
1713     * | |
1714     * Month ---------
1715     * | |
1716     * Quarter Year
1717     * </pre>
1718     * <p>
1719     * When the relation looks like the above, then the fact table joins to the
1720     * lowest level table (the Day table) which joins to the next level (the
1721     * Month table) which joins to the next (the Quarter table) which joins to
1722     * the top level table (the Year table).
1723     * <p>
1724     * This method supports the transformation of a subset of all possible
1725     * join/table relation trees (and anyone who whats to generalize it is
1726     * welcome to). It will take any of the following and convert them to
1727     * the canonical.
1728     * <pre>
1729     * |
1730     * ----------------
1731     * | |
1732     * Year --------------
1733     * | |
1734     * Quarter ---------
1735     * | |
1736     * Month Day
1737     *
1738     * |
1739     * ----------------
1740     * | |
1741     * -------------- Year
1742     * | |
1743     * --------- Quarter
1744     * | |
1745     * Day Month
1746     *
1747     * |
1748     * ----------------
1749     * | |
1750     * -------------- Day
1751     * | |
1752     * --------- Month
1753     * | |
1754     * Year Quarter
1755     *
1756     * |
1757     * ----------------
1758     * | |
1759     * Day --------------
1760     * | |
1761     * Month ---------
1762     * | |
1763     * Quarter Year
1764     *
1765     * </pre>
1766     * <p>
1767     * In addition, at any join node, it can exchange the left and right
1768     * child relations so that the lower level depth is to the left.
1769     * For example, it can also transform the following:
1770     * <pre>
1771     * |
1772     * ----------------
1773     * | |
1774     * -------------- Day
1775     * | |
1776     * Month ---------
1777     * | |
1778     * Year Quarter
1779     * </pre>
1780     * <p>
1781     * What it can not handle are cases where on both the left and right side of
1782     * a join there are child joins:
1783     * <pre>
1784     * |
1785     * ----------------
1786     * | |
1787     * --------- ----------
1788     * | | | |
1789     * Month Day Year Quarter
1790     *
1791     * |
1792     * ----------------
1793     * | |
1794     * --------- ----------
1795     * | | | |
1796     * Year Day Month Quarter
1797     * </pre>
1798     * <p>
1799     * When does this method do nothing? 1) when there are less than 2 levels,
1800     * 2) when any level does not have a table name, and 3) when for every table
1801     * in the relation there is not a level. In these cases, this method simply
1802     * return the original relation.
1803     *
1804     * @param relation
1805     * @param levels
1806     */

1807    private static MondrianDef.Relation reorder(
1808            MondrianDef.Relation relation,
1809            RolapLevel[] levels) {
1810        // Need at least two levels, with only one level theres nothing to do.
1811
if (levels.length < 2) {
1812            return relation;
1813        }
1814
1815        Map<String JavaDoc, RelNode> nodeMap = new HashMap<String JavaDoc, RelNode>();
1816
1817        // Create RelNode in top down order (year -> day)
1818
for (int i = 0; i < levels.length; i++) {
1819            RolapLevel level = levels[i];
1820
1821            if (level.isAll()) {
1822                continue;
1823            }
1824
1825            // this is the table alias
1826
String JavaDoc tableName = level.getTableName();
1827            if (tableName == null) {
1828                // punt, no table name
1829
return relation;
1830            }
1831            RelNode rnode = new RelNode(tableName, i);
1832            nodeMap.put(tableName, rnode);
1833        }
1834        if (! validateNodes(relation, nodeMap)) {
1835            return relation;
1836        }
1837        relation = copy(relation);
1838
1839        // Put lower levels to the left of upper levels
1840
leftToRight(relation, nodeMap);
1841
1842        // Move joins to the right side
1843
topToBottom(relation);
1844
1845        return relation;
1846    }
1847
1848    /**
1849     * The map has to be validated against the relation because there are
1850     * certain cases where we do not want to (read: can not) do reordering, for
1851     * instance, when closures are involved.
1852     *
1853     * @param relation
1854     * @param map
1855     */

1856    private static boolean validateNodes(
1857        MondrianDef.Relation relation,
1858        Map<String JavaDoc, RelNode> map)
1859    {
1860        if (relation instanceof MondrianDef.Table) {
1861            MondrianDef.Table table = (MondrianDef.Table) relation;
1862
1863            RelNode relNode = RelNode.lookup(table, map);
1864            return (relNode != null);
1865
1866        } else if (relation instanceof MondrianDef.Join) {
1867            MondrianDef.Join join = (MondrianDef.Join) relation;
1868
1869            return validateNodes(join.left, map) &&
1870                validateNodes(join.right, map);
1871
1872        } else {
1873            throw Util.newInternal("bad relation type " + relation);
1874        }
1875
1876    }
1877
1878    /**
1879     * Transforms the Relation moving the tables associated with
1880     * lower levels (greater level depth, i.e., Day is lower than Month) to the
1881     * left of tables with high levels.
1882     *
1883     * @param relation
1884     * @param map
1885     */

1886    private static int leftToRight(
1887        MondrianDef.Relation relation,
1888        Map<String JavaDoc, RelNode> map)
1889    {
1890        if (relation instanceof MondrianDef.Table) {
1891            MondrianDef.Table table = (MondrianDef.Table) relation;
1892
1893            RelNode relNode = RelNode.lookup(table, map);
1894            // Associate the table with its RelNode!!!! This is where this
1895
// happens.
1896
relNode.table = table;
1897
1898            return relNode.depth;
1899
1900        } else if (relation instanceof MondrianDef.Join) {
1901            MondrianDef.Join join = (MondrianDef.Join) relation;
1902            int leftDepth = leftToRight(join.left, map);
1903            int rightDepth = leftToRight(join.right, map);
1904
1905            // we want the right side to be less than the left
1906
if (rightDepth > leftDepth) {
1907                // switch
1908
String JavaDoc leftAlias = join.leftAlias;
1909                String JavaDoc leftKey = join.leftKey;
1910                MondrianDef.Relation left = join.left;
1911                join.leftAlias = join.rightAlias;
1912                join.leftKey = join.rightKey;
1913                join.left = join.right;
1914                join.rightAlias = leftAlias;
1915                join.rightKey = leftKey;
1916                join.right = left;
1917            }
1918            // Does not currently matter which is returned because currently we
1919
// only support structures where the left and right depth values
1920
// form an inclusive subset of depth values, that is, any
1921
// node with a depth value between the left or right values is
1922
// a child of this current join.
1923
return leftDepth;
1924
1925        } else {
1926            throw Util.newInternal("bad relation type " + relation);
1927        }
1928
1929    }
1930
1931    /**
1932     * Transforms so that all joins have a table as their left child and either
1933     * a table of child join on the right.
1934     *
1935     * @param relation
1936     */

1937    private static void topToBottom(MondrianDef.Relation relation) {
1938        if (relation instanceof MondrianDef.Table) {
1939            // nothing
1940

1941        } else if (relation instanceof MondrianDef.Join) {
1942            MondrianDef.Join join = (MondrianDef.Join) relation;
1943
1944            while (join.left instanceof MondrianDef.Join) {
1945                MondrianDef.Join jleft = (MondrianDef.Join) join.left;
1946
1947                join.right = new MondrianDef.Join(
1948                        join.leftAlias,
1949                        join.leftKey,
1950                        jleft.right,
1951                        join.rightAlias,
1952                        join.rightKey,
1953                        join.right);
1954
1955                join.left = jleft.left;
1956
1957                join.rightAlias = jleft.rightAlias;
1958                join.rightKey = jleft.rightKey;
1959                join.leftAlias = jleft.leftAlias;
1960                join.leftKey = jleft.leftKey;
1961            }
1962        }
1963
1964    }
1965
1966    /**
1967     * Copies a {@link MondrianDef.Relation}.
1968     *
1969     * @param relation
1970     */

1971    private static MondrianDef.Relation copy(MondrianDef.Relation relation) {
1972        if (relation instanceof MondrianDef.Table) {
1973            MondrianDef.Table table = (MondrianDef.Table) relation;
1974            return new MondrianDef.Table(table);
1975
1976        } else if (relation instanceof MondrianDef.Join) {
1977            MondrianDef.Join join = (MondrianDef.Join) relation;
1978
1979            MondrianDef.Relation left = copy(join.left);
1980            MondrianDef.Relation right = copy(join.right);
1981
1982            return new MondrianDef.Join(join.leftAlias, join.leftKey, left,
1983                        join.rightAlias, join.rightKey, right);
1984
1985        } else {
1986            throw Util.newInternal("bad relation type " + relation);
1987        }
1988    }
1989
1990    /**
1991     * Takes a relation in canonical form and snips off the
1992     * the tables with the given tableName (or table alias). The matching table
1993     * only appears once in the relation.
1994     *
1995     * @param relation
1996     * @param tableName
1997     */

1998    private static MondrianDef.Relation snip(
1999            MondrianDef.Relation relation,
2000            String JavaDoc tableName) {
2001        if (relation instanceof MondrianDef.Table) {
2002            MondrianDef.Table table = (MondrianDef.Table) relation;
2003            // Return null if the table's name or alias matches tableName
2004
return ((table.alias != null) && table.alias.equals(tableName))
2005                ? null
2006                : (table.name.equals(tableName) ? null : table);
2007
2008        } else if (relation instanceof MondrianDef.Join) {
2009            MondrianDef.Join join = (MondrianDef.Join) relation;
2010
2011            // snip left
2012
MondrianDef.Relation left = snip(join.left, tableName);
2013            if (left == null) {
2014                // left got snipped so return the right
2015
// (the join is no longer a join).
2016
return join.right;
2017
2018            } else {
2019                // whatever happened on the left, save it
2020
join.left = left;
2021
2022                // snip right
2023
MondrianDef.Relation right = snip(join.right, tableName);
2024                if (right == null) {
2025                    // right got snipped so return the left.
2026
return join.left;
2027
2028                } else {
2029                    // save the right, join still has right and left children
2030
// so return it.
2031
join.right = right;
2032                    return join;
2033                }
2034            }
2035
2036
2037        } else {
2038            throw Util.newInternal("bad relation type " + relation);
2039        }
2040
2041    }
2042    //
2043
///////////////////////////////////////////////////////////////////////////
2044

2045
2046
2047    public Member[] getMembersForQuery(String JavaDoc query, List<Member> calcMembers) {
2048        throw new UnsupportedOperationException JavaDoc();
2049    }
2050
2051    Member[] getMeasures() {
2052        Level measuresLevel = dimensions[0].getHierarchies()[0].getLevels()[0];
2053        return getSchemaReader().getLevelMembers(measuresLevel, true);
2054    }
2055
2056    /**
2057     * Returns this cube's fact table, null if the cube is virtual.
2058     */

2059    MondrianDef.Relation getFact() {
2060        return fact;
2061    }
2062
2063    /**
2064     * Returns whether this cube is virtual. We use the fact that virtual cubes
2065     * do not have fact tables.
2066     */

2067    public boolean isVirtual() {
2068        return (fact == null);
2069    }
2070
2071    RolapDimension createDimension(MondrianDef.CubeDimension xmlCubeDimension) {
2072        MondrianDef.Dimension xmlDimension;
2073        final RolapDimension dimension;
2074        if (xmlCubeDimension instanceof MondrianDef.Dimension) {
2075            xmlDimension = (MondrianDef.Dimension) xmlCubeDimension;
2076            dimension = new RolapDimension(schema, this, xmlDimension,
2077                xmlCubeDimension);
2078        } else if (xmlCubeDimension instanceof MondrianDef.DimensionUsage) {
2079            final MondrianDef.DimensionUsage usage =
2080                (MondrianDef.DimensionUsage) xmlCubeDimension;
2081            final RolapHierarchy sharedHierarchy =
2082                schema.getSharedHierarchy(usage.source);
2083            if (sharedHierarchy == null) {
2084                throw Util.newInternal("todo: Shared hierarchy '" + usage.source +
2085                                        "' not found");
2086            }
2087
2088            final RolapDimension sharedDimension =
2089                (RolapDimension) sharedHierarchy.getDimension();
2090            dimension = sharedDimension.copy(this, usage.name,
2091                xmlCubeDimension);
2092        } else {
2093            throw Util.newInternal("Unexpected subtype, " + xmlCubeDimension);
2094        }
2095
2096        dimension.init(this, xmlCubeDimension);
2097        // add to dimensions array
2098
final int localOrdinal = dimensions.length;
2099        this.dimensions = (DimensionBase[])
2100            RolapUtil.addElement(dimensions, dimension);
2101
2102        RolapHierarchy hierarchy = (RolapHierarchy) dimension.getHierarchy();
2103        createUsage(hierarchy, xmlCubeDimension);
2104
2105
2106        // add to ordinals array
2107
final int globalOrdinal = dimension.getGlobalOrdinal();
2108        if (globalOrdinal >= localDimensionOrdinals.length) {
2109            int[] newLocalDimensionOrdinals = new int[globalOrdinal + 1];
2110            System.arraycopy(localDimensionOrdinals, 0,
2111                    newLocalDimensionOrdinals, 0, localDimensionOrdinals.length);
2112            Arrays.fill(newLocalDimensionOrdinals,
2113                    localDimensionOrdinals.length,
2114                    newLocalDimensionOrdinals.length, -1);
2115            this.localDimensionOrdinals = newLocalDimensionOrdinals;
2116        }
2117        Util.assertTrue(localDimensionOrdinals[globalOrdinal] == -1);
2118        localDimensionOrdinals[globalOrdinal] = localOrdinal;
2119        registerDimension(dimension);
2120        return dimension;
2121    }
2122
2123    public OlapElement lookupChild(SchemaReader schemaReader, String JavaDoc s) {
2124        return lookupChild(schemaReader, s, MatchType.EXACT);
2125    }
2126
2127    public OlapElement lookupChild(
2128        SchemaReader schemaReader, String JavaDoc s, MatchType matchType)
2129    {
2130        // Note that non-exact matches aren't supported at this level,
2131
// so the matchType is ignored
2132
OlapElement oe;
2133        String JavaDoc status = null;
2134        if (s.equals("Measures")) {
2135            // A Measure is never aliased, so just get it
2136
// Note if one calls getUsageByName with "Measures" as the value
2137
// it will return null so one must either do this or check for
2138
// a null HierarchyUsage below and disambiguate.
2139
oe = super.lookupChild(schemaReader, s, MatchType.EXACT);
2140
2141        } else {
2142            // At this point everything ought to have a HierarchyUsage.
2143
//
2144
// If one does not exist (null), then odds are one is using
2145
// the base/source hierarchy name in a calculated measure
2146
// for a shared hierarchy - which is not legal.
2147
// But there are cases where its OK here not to have a
2148
// usage, i.e., the child is a measure, member or level
2149
// (not a RolapDimension), or the cube is virtual.
2150
// (Are those the only cases??)
2151
//
2152
// On the other hand, if the HierarchyUsage is shared, then
2153
// use the source name.
2154
//
2155
// Lastly if the HierarchyUsage is not shared, then there is
2156
// no aliasing so just use the value.
2157

2158            HierarchyUsage hierUsage = getUsageByName(s);
2159
2160            if (hierUsage == null) {
2161                oe = super.lookupChild(schemaReader, s, MatchType.EXACT);
2162                status = "hierUsage == null";
2163
2164                // Let us see if one is using the source name of a
2165
// usage rather than the alias name.
2166
if (oe instanceof RolapDimension) {
2167                    HierarchyUsage[] usages = getUsagesBySource(s);
2168
2169                    if (usages.length > 0) {
2170                        StringBuilder JavaDoc buf = new StringBuilder JavaDoc(64);
2171                        buf.append("RolapCube.lookupChild: ");
2172                        buf.append("In cube \"");
2173                        buf.append(getName());
2174                        buf.append("\" use of unaliased Dimension name \"");
2175                        buf.append(s);
2176
2177                        if (usages.length == 1) {
2178                            // ERROR: this will work but is bad coding
2179
buf.append("\" rather than the alias name ");
2180                            buf.append("\"");
2181                            buf.append(usages[0].getName());
2182                            buf.append("\" ");
2183                            getLogger().error(buf.toString());
2184                            throw new MondrianException(buf.toString());
2185                        } else {
2186                            // ERROR: this is not allowed
2187
buf.append("\" rather than one of the alias names ");
2188                            for (HierarchyUsage usage : usages) {
2189                                buf.append("\"");
2190                                buf.append(usage.getName());
2191                                buf.append("\" ");
2192                            }
2193                            getLogger().error(buf.toString());
2194                            throw new MondrianException(buf.toString());
2195                        }
2196                    }
2197                }
2198            } else if (hierUsage.isShared()) {
2199                status = "hierUsage == shared";
2200                // Shared, use source
2201
String JavaDoc source = hierUsage.getSource();
2202                oe = super.lookupChild(schemaReader, source, MatchType.EXACT);
2203
2204            } else {
2205                status = "hierUsage == not shared";
2206                // Not shared, cool man
2207
oe = super.lookupChild(schemaReader, s, MatchType.EXACT);
2208            }
2209        }
2210        if (getLogger().isDebugEnabled()) {
2211            StringBuilder JavaDoc buf = new StringBuilder JavaDoc(64);
2212            buf.append("RolapCube.lookupChild: ");
2213            buf.append("name=");
2214            buf.append(getName());
2215            buf.append(", childname=");
2216            buf.append(s);
2217            if (status != null) {
2218                buf.append(", status=");
2219                buf.append(status);
2220            }
2221            if (oe == null) {
2222                buf.append(" returning null");
2223            } else {
2224                buf.append(" returning elementname=").append(oe.getName());
2225            }
2226            getLogger().debug(buf.toString());
2227        }
2228
2229        return oe;
2230    }
2231
2232    /**
2233     * Returns the the measures hierarchy.
2234     */

2235    public Hierarchy getMeasuresHierarchy(){
2236        return measuresHierarchy;
2237    }
2238
2239    // RME
2240
public RolapMember[] getMeasuresMembers(){
2241        return measuresHierarchy.getMemberReader().getMembers();
2242    }
2243
2244    public Member createCalculatedMember(String JavaDoc xml) {
2245        MondrianDef.CalculatedMember xmlCalcMember;
2246        try {
2247            final Parser xmlParser = XOMUtil.createDefaultParser();
2248            final DOMWrapper def = xmlParser.parse(xml);
2249            final String JavaDoc tagName = def.getTagName();
2250            if (tagName.equals("CalculatedMember")) {
2251                xmlCalcMember = new MondrianDef.CalculatedMember(def);
2252            } else {
2253                throw new XOMException("Got <" + tagName +
2254                    "> when expecting <CalculatedMember>");
2255            }
2256        } catch (XOMException e) {
2257            throw Util.newError(e,
2258                "Error while creating calculated member from XML [" +
2259                xml + "]");
2260        }
2261
2262        final ArrayList<Member> memberList = new ArrayList<Member>();
2263        createCalcMembersAndNamedSets(
2264                new MondrianDef.CalculatedMember[] {xmlCalcMember},
2265                new MondrianDef.NamedSet[0],
2266                memberList,
2267                new ArrayList<Formula>(),
2268                this,
2269                true);
2270        assert memberList.size() == 1;
2271        return memberList.get(0);
2272    }
2273
2274    /**
2275     * Schema reader which works from the perspective of a particular cube
2276     * (and hence includes calculated members defined in that cube) and also
2277     * applies the access-rights of a given role.
2278     */

2279    private class RolapCubeSchemaReader extends RolapSchemaReader {
2280        public RolapCubeSchemaReader(Role role) {
2281            super(role, schema);
2282            assert role != null : "precondition: role != null";
2283        }
2284
2285        public Member[] getLevelMembers(
2286                Level level, boolean includeCalculated) {
2287            Member[] members = super.getLevelMembers(level, false);
2288            if (includeCalculated) {
2289                members = Util.addLevelCalculatedMembers(this, level, members);
2290            }
2291            return members;
2292        }
2293
2294
2295        public Member getCalculatedMember(String JavaDoc[] nameParts) {
2296            final String JavaDoc uniqueName = Util.implode(nameParts);
2297            for (Formula formula : calculatedMembers) {
2298                final String JavaDoc formulaUniqueName =
2299                    formula.getMdxMember().getUniqueName();
2300                if (formulaUniqueName.equals(uniqueName) &&
2301                    getRole().canAccess(formula.getMdxMember()))
2302                {
2303                    return formula.getMdxMember();
2304                }
2305            }
2306            return null;
2307        }
2308
2309        public NamedSet getNamedSet(String JavaDoc[] nameParts) {
2310            if (nameParts.length == 1) {
2311                String JavaDoc name = nameParts[0];
2312                for (Formula namedSet : namedSets) {
2313                    if (namedSet.getName().equals(name)) {
2314                        return namedSet.getNamedSet();
2315                    }
2316                }
2317            }
2318            return super.getNamedSet(nameParts);
2319        }
2320
2321        public List<Member> getCalculatedMembers(Hierarchy hierarchy) {
2322            ArrayList<Member> list = new ArrayList<Member>();
2323
2324            if (getRole().getAccess(hierarchy) == Access.NONE) {
2325                return list;
2326            }
2327
2328            for (Formula formula : calculatedMembers) {
2329                Member member = formula.getMdxMember();
2330                if (member.getHierarchy().equals(hierarchy) &&
2331                    getRole().canAccess(member)) {
2332                    list.add(member);
2333                }
2334            }
2335            return list;
2336        }
2337
2338        public List<Member> getCalculatedMembers(Level level) {
2339            List<Member> list = new ArrayList<Member>();
2340
2341            if (getRole().getAccess(level) == Access.NONE) {
2342                return list;
2343            }
2344
2345            for (Member member : getCalculatedMembers(level.getHierarchy())) {
2346                if (member.getLevel().equals(level) &&
2347                    getRole().canAccess(member)) {
2348                    list.add(member);
2349                }
2350            }
2351            return list;
2352        }
2353
2354        public List<Member> getCalculatedMembers() {
2355            List<Member> list = new ArrayList<Member>();
2356            for (Formula formula : calculatedMembers) {
2357                Member member = formula.getMdxMember();
2358                if (getRole().canAccess(member)) {
2359                    list.add(member);
2360                }
2361            }
2362            return list;
2363        }
2364
2365        public Member getMemberByUniqueName(
2366            String JavaDoc[] uniqueNameParts, boolean failIfNotFound)
2367        {
2368            return getMemberByUniqueName(
2369                uniqueNameParts, failIfNotFound, MatchType.EXACT);
2370        }
2371
2372        public Member getMemberByUniqueName(
2373                String JavaDoc[] uniqueNameParts, boolean failIfNotFound,
2374                MatchType matchType)
2375        {
2376            Member member = (Member) lookupCompound(
2377                                        RolapCube.this, uniqueNameParts,
2378                                        failIfNotFound, Category.Member,
2379                                        matchType);
2380            if (!failIfNotFound && member == null) {
2381                return null;
2382            }
2383            if (getRole().canAccess(member)) {
2384                return member;
2385            } else {
2386                return null;
2387            }
2388        }
2389
2390        public Cube getCube() {
2391            return RolapCube.this;
2392        }
2393    }
2394
2395    /**
2396     * Visitor that walks an MDX parse tree containing formulas
2397     * associated with calculated members defined in a base cube but
2398     * referenced from a virtual cube. When walking the tree, look
2399     * for other calculated members as well as stored measures. Keep
2400     * track of all stored measures found, and for the calculated members,
2401     * once the formula of that calculated member has been visited, resolve
2402     * the calculated member relative to the virtual cube.
2403     */

2404    private class MeasureFinder extends MdxVisitorImpl
2405    {
2406        /**
2407         * The virtual cube where the original calculated member was
2408         * referenced from
2409         */

2410        private RolapCube virtualCube;
2411
2412        /**
2413         * The base cube where the original calculated member is defined
2414         */

2415        private RolapCube baseCube;
2416
2417        /**
2418         * The measures level corresponding to the virtual cube
2419         */

2420        private RolapLevel measuresLevel;
2421
2422        /**
2423         * List of measures found
2424         */

2425        private List<RolapVirtualCubeMeasure> measuresFound;
2426
2427        /**
2428         * List of calculated members found
2429         */

2430        private List<RolapCalculatedMember> calcMembersSeen;
2431
2432        public MeasureFinder(
2433            RolapCube virtualCube,
2434            RolapCube baseCube,
2435            RolapLevel measuresLevel)
2436        {
2437            this.virtualCube = virtualCube;
2438            this.baseCube = baseCube;
2439            this.measuresLevel = measuresLevel;
2440            this.measuresFound = new ArrayList<RolapVirtualCubeMeasure>();
2441            this.calcMembersSeen = new ArrayList<RolapCalculatedMember>();
2442        }
2443
2444        public Object JavaDoc visit(MemberExpr memberExpr)
2445        {
2446            Member member = memberExpr.getMember();
2447            if (member instanceof RolapCalculatedMember) {
2448                // ignore the calculated member if we've already processed
2449
// it in another reference
2450
if (calcMembersSeen.contains(member)) {
2451                    return null;
2452                }
2453                RolapCalculatedMember calcMember =
2454                    (RolapCalculatedMember) member;
2455                Formula formula = calcMember.getFormula();
2456                formula.accept(this);
2457                calcMembersSeen.add(calcMember);
2458
2459                // now that we've located all measures referenced in the
2460
// calculated member's formula, resolve the calculated
2461
// member relative to the virtual cube
2462
RolapMember[] measures = (RolapMember[])
2463                    measuresFound.toArray(
2464                        new RolapMember[measuresFound.size()]);
2465                virtualCube.measuresHierarchy.setMemberReader(
2466                    new CacheMemberReader(
2467                        new MeasureMemberSource(
2468                            virtualCube.measuresHierarchy, measures)));
2469                MondrianDef.CalculatedMember xmlCalcMember =
2470                    schema.lookupXmlCalculatedMember(
2471                        calcMember.getUniqueName(),
2472                        baseCube.name);
2473                createCalcMembersAndNamedSets(
2474                    new MondrianDef.CalculatedMember [] { xmlCalcMember },
2475                    new MondrianDef.NamedSet[0],
2476                    new ArrayList<Member>(),
2477                    new ArrayList<Formula>(),
2478                    virtualCube,
2479                    false);
2480                return null;
2481
2482            } else if (member instanceof RolapBaseCubeMeasure) {
2483                RolapBaseCubeMeasure baseMeasure =
2484                    (RolapBaseCubeMeasure) member;
2485                RolapVirtualCubeMeasure virtualCubeMeasure =
2486                    new RolapVirtualCubeMeasure(
2487                        null,
2488                        measuresLevel,
2489                        baseMeasure);
2490                if (!measuresFound.contains(virtualCubeMeasure)) {
2491                    measuresFound.add(virtualCubeMeasure);
2492                }
2493            }
2494
2495            return null;
2496        }
2497
2498        public List<RolapVirtualCubeMeasure> getMeasuresFound()
2499        {
2500            return measuresFound;
2501        }
2502    }
2503
2504    private class CubeComparator implements Comparator<RolapCube>
2505    {
2506        public int compare(RolapCube c1, RolapCube c2)
2507        {
2508            return c1.getName().compareTo(c2.getName());
2509         }
2510    }
2511}
2512
2513// End RolapCube.java
2514
Popular Tags