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