001 /*
002 // $Id: //open/mondrian-release/3.2/src/main/mondrian/rolap/RolapCubeHierarchy.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 // wgorman, 19 October 2007
012 */
013 package mondrian.rolap;
014
015 import java.sql.SQLException;
016 import java.util.ArrayList;
017 import java.util.Collections;
018 import java.util.HashMap;
019 import java.util.Iterator;
020 import java.util.List;
021 import java.util.Map;
022
023 import mondrian.olap.*;
024 import mondrian.olap.fun.VisualTotalsFunDef;
025 import mondrian.rolap.TupleReader.MemberBuilder;
026 import mondrian.rolap.sql.MemberChildrenConstraint;
027 import mondrian.rolap.sql.TupleConstraint;
028 import mondrian.util.UnsupportedList;
029
030 /**
031 * Hierarchy that is associated with a specific Cube.
032 *
033 * @author Will Gorman
034 * @version $Id: //open/mondrian-release/3.2/src/main/mondrian/rolap/RolapCubeHierarchy.java#7 $
035 */
036 public class RolapCubeHierarchy extends RolapHierarchy {
037
038 private final RolapCubeDimension cubeDimension;
039 private final RolapHierarchy rolapHierarchy;
040 private final RolapCubeLevel currentNullLevel;
041 private RolapCubeMember currentNullMember;
042 private RolapCubeMember currentAllMember;
043 private final MondrianDef.RelationOrJoin currentRelation;
044 private final RolapCubeHierarchyMemberReader reader;
045 private HierarchyUsage usage;
046 private final Map<String, String> aliases = new HashMap<String, String>();
047 private RolapCubeMember currentDefaultMember;
048 private final int ordinal;
049
050 /**
051 * True if the hierarchy is degenerate - has no dimension table of its own,
052 * just drives from the cube's fact table.
053 */
054 protected final boolean usingCubeFact;
055
056 /**
057 * Length of prefix to be removed when translating member unique names, or
058 * 0 if no translation is necessary.
059 */
060 private final int removePrefixLength;
061
062 // redundant copy of {@link #levels} with tigher type
063 private final RolapCubeLevel[] cubeLevels;
064
065 /**
066 * Creates a RolapCubeHierarchy.
067 *
068 * @param cubeDimension Dimension
069 * @param cubeDim XML dimension element
070 * @param rolapHierarchy Wrapped hierarchy
071 * @param subName Name of hierarchy within dimension
072 * @param ordinal Ordinal of hierarchy within cube
073 */
074 public RolapCubeHierarchy(
075 RolapCubeDimension cubeDimension,
076 MondrianDef.CubeDimension cubeDim,
077 RolapHierarchy rolapHierarchy,
078 String subName,
079 int ordinal)
080 {
081 super(
082 cubeDimension,
083 subName,
084 applyPrefix(cubeDim, rolapHierarchy.getCaption()),
085 applyPrefix(cubeDim, rolapHierarchy.getDescription()),
086 rolapHierarchy.hasAll(),
087 null,
088 rolapHierarchy.getAnnotationMap());
089 this.ordinal = ordinal;
090 if (!cubeDimension.getCube().isVirtual()) {
091 this.usage =
092 new HierarchyUsage(
093 cubeDimension.getCube(), rolapHierarchy, cubeDim);
094 }
095
096 this.rolapHierarchy = rolapHierarchy;
097 this.cubeDimension = cubeDimension;
098 this.xmlHierarchy = rolapHierarchy.getXmlHierarchy();
099 // this relation should equal the name of the new dimension table
100 // The null member belongs to a level with very similar properties to
101 // the 'all' level.
102 this.currentNullLevel = new RolapCubeLevel(nullLevel, this);
103
104 usingCubeFact =
105 (cubeDimension.getCube().getFact() == null
106 || cubeDimension.getCube().getFact().equals(
107 rolapHierarchy.getRelation()));
108
109 // re-alias names if necessary
110 if (!usingCubeFact) {
111 // join expressions are columns only
112 assert (usage.getJoinExp() instanceof MondrianDef.Column);
113 currentRelation =
114 this.cubeDimension.getCube().getStar().getUniqueRelation(
115 rolapHierarchy.getRelation(),
116 usage.getForeignKey(),
117 ((MondrianDef.Column)usage.getJoinExp()).getColumnName(),
118 usage.getJoinTable().getAlias());
119 } else {
120 currentRelation = rolapHierarchy.getRelation();
121 }
122 extractNewAliases(rolapHierarchy.getRelation(), currentRelation);
123 this.relation = currentRelation;
124 this.levels =
125 this.cubeLevels =
126 new RolapCubeLevel[rolapHierarchy.getLevels().length];
127 for (int i = 0; i < rolapHierarchy.getLevels().length; i++) {
128 this.cubeLevels[i] =
129 new RolapCubeLevel(
130 (RolapLevel) rolapHierarchy.getLevels()[i], this);
131 if (i == 0) {
132 if (rolapHierarchy.getAllMember() != null) {
133 RolapCubeLevel allLevel;
134 if (hasAll()) {
135 allLevel = this.cubeLevels[0];
136 } else {
137 // create an all level if one doesn't normally
138 // exist in the hierarchy
139 allLevel =
140 new RolapCubeLevel(
141 rolapHierarchy.getAllMember().getLevel(),
142 this);
143 allLevel.init(cubeDimension.xmlDimension);
144 }
145
146 this.currentAllMember =
147 new RolapAllCubeMember(
148 rolapHierarchy.getAllMember(),
149 allLevel);
150 }
151 }
152 }
153
154 // Compute whether the unique names of members of this hierarchy are
155 // different from members of the underlying hierarchy. If so, compute
156 // the length of the prefix to be removed before this hierarchy's unique
157 // name is added. For example, if this.uniqueName is "[Ship Time]" and
158 // rolapHierarchy.uniqueName is "[Time]", remove prefixLength will be
159 // length("[Ship Time]") = 11.
160 if (uniqueName.equals(rolapHierarchy.getUniqueName())) {
161 this.removePrefixLength = 0;
162 } else {
163 this.removePrefixLength = rolapHierarchy.getUniqueName().length();
164 }
165
166 if (cubeDimension.isHighCardinality()) {
167 this.reader = new NoCacheRolapCubeHierarchyMemberReader();
168 } else {
169 this.reader = new CacheRolapCubeHierarchyMemberReader();
170 }
171 }
172
173 /**
174 * Applies a prefix to a caption or description of a hierarchy in a shared
175 * dimension. Ensures that if a dimension is used more than once in the same
176 * cube then the hierarchies are distinguishable.
177 *
178 * <p>For example, if the [Time] dimension is imported as [Order Time] and
179 * [Ship Time], then the [Time].[Weekly] hierarchy would have caption
180 * "Order Time.Weekly caption" and description "Order Time.Weekly
181 * description".
182 *
183 * <p>If the dimension usage has a caption, it overrides.
184 *
185 * <p>If the dimension usage has a null name, or the name is the same
186 * as the dimension, and no caption, then no prefix is applied.
187 *
188 * @param cubeDim Cube dimension (maybe a usage of a shared dimension)
189 * @param caption Caption or description
190 * @return Caption or description, possibly prefixed by dimension role name
191 */
192 private static String applyPrefix(
193 MondrianDef.CubeDimension cubeDim,
194 String caption)
195 {
196 if (caption == null) {
197 return null;
198 }
199 if (cubeDim instanceof MondrianDef.DimensionUsage) {
200 final MondrianDef.DimensionUsage dimensionUsage =
201 (MondrianDef.DimensionUsage) cubeDim;
202 if (dimensionUsage.name != null
203 && !dimensionUsage.name.equals(dimensionUsage.source))
204 {
205 if (dimensionUsage.caption != null) {
206 return dimensionUsage.caption + "." + caption;
207 } else {
208 return dimensionUsage.name + "." + caption;
209 }
210 }
211 }
212 return caption;
213 }
214
215 @Override
216 public RolapCubeLevel[] getLevels() {
217 return cubeLevels;
218 }
219
220 public String getAllMemberName() {
221 return rolapHierarchy.getAllMemberName();
222 }
223
224 public String getSharedHierarchyName() {
225 return rolapHierarchy.getSharedHierarchyName();
226 }
227
228 public String getAllLevelName() {
229 return rolapHierarchy.getAllLevelName();
230 }
231
232 public boolean isUsingCubeFact() {
233 return usingCubeFact;
234 }
235
236 public String lookupAlias(String origTable) {
237 return aliases.get(origTable);
238 }
239
240 public final RolapHierarchy getRolapHierarchy() {
241 return rolapHierarchy;
242 }
243
244 public final int getOrdinalInCube() {
245 return ordinal;
246 }
247
248 /**
249 * Populates the alias map for the old and new relations.
250 *
251 * <p>This method may be simplified when we obsolete
252 * {@link mondrian.rolap.HierarchyUsage}.
253 *
254 * @param oldrel Original relation, as defined in the schema
255 * @param newrel New star relation, generated by RolapStar, canonical, and
256 * shared between all cubes with similar structure
257 */
258 protected void extractNewAliases(
259 MondrianDef.RelationOrJoin oldrel,
260 MondrianDef.RelationOrJoin newrel)
261 {
262 if (oldrel == null && newrel == null) {
263 return;
264 } else if (oldrel instanceof MondrianDef.Relation
265 && newrel instanceof MondrianDef.Relation)
266 {
267 aliases.put(
268 ((MondrianDef.Relation) oldrel).getAlias(),
269 ((MondrianDef.Relation) newrel).getAlias());
270 } else if (oldrel instanceof MondrianDef.Join
271 && newrel instanceof MondrianDef.Join)
272 {
273 MondrianDef.Join oldjoin = (MondrianDef.Join)oldrel;
274 MondrianDef.Join newjoin = (MondrianDef.Join)newrel;
275 extractNewAliases(oldjoin.left, newjoin.left);
276 extractNewAliases(oldjoin.right, newjoin.right);
277 } else {
278 throw new UnsupportedOperationException();
279 }
280 }
281
282 public boolean equals(Object o) {
283 if (this == o) {
284 return true;
285 }
286 if (!(o instanceof RolapCubeHierarchy)) {
287 return false;
288 }
289
290 RolapCubeHierarchy that = (RolapCubeHierarchy)o;
291 return cubeDimension.equals(that.cubeDimension)
292 && getUniqueName().equals(that.getUniqueName());
293 }
294
295 protected int computeHashCode() {
296 return Util.hash(super.computeHashCode(), this.cubeDimension.cube);
297 }
298
299 public Member createMember(
300 Member parent,
301 Level level,
302 String name,
303 Formula formula)
304 {
305 RolapLevel rolapLevel = ((RolapCubeLevel)level).getRolapLevel();
306 if (formula == null) {
307 RolapMember rolapParent = null;
308 if (parent != null) {
309 rolapParent = ((RolapCubeMember)parent).getRolapMember();
310 }
311 RolapMember member =
312 new RolapMemberBase(rolapParent, rolapLevel, name);
313 return new RolapCubeMember(
314 (RolapCubeMember) parent, member,
315 (RolapCubeLevel) level);
316 } else if (level.getDimension().isMeasures()) {
317 RolapCalculatedMeasure member =
318 new RolapCalculatedMeasure(
319 (RolapMember) parent, rolapLevel, name, formula);
320 return new RolapCubeMember(
321 (RolapCubeMember) parent, member,
322 (RolapCubeLevel) level);
323 } else {
324 RolapCalculatedMember member =
325 new RolapCalculatedMember(
326 (RolapMember) parent, rolapLevel, name, formula);
327 return new RolapCubeMember(
328 (RolapCubeMember) parent, member,
329 (RolapCubeLevel) level);
330 }
331 }
332
333
334 boolean tableExists(String tableName) {
335 return rolapHierarchy.tableExists(tableName);
336 }
337
338 /**
339 * The currentRelation object is derived from the shared relation object
340 * it is generated via the RolapStar object, and contains unique aliases
341 * for it's particular join path
342 *
343 * @return rolap cube hierarchy relation
344 */
345 public MondrianDef.RelationOrJoin getRelation() {
346 return currentRelation;
347 }
348
349 // override with stricter return type; make final, important for performance
350 public final RolapCubeMember getDefaultMember() {
351 if (currentDefaultMember == null) {
352 reader.getRootMembers();
353 currentDefaultMember =
354 bootstrapLookup(
355 (RolapMember) rolapHierarchy.getDefaultMember());
356 }
357 return currentDefaultMember;
358 }
359
360 /**
361 * Looks up a {@link RolapCubeMember} corresponding to a {@link RolapMember}
362 * of the underlying hierarchy. Safe to be called while the hierarchy is
363 * initializing.
364 *
365 * @param rolapMember Member of underlying hierarchy
366 * @return Member of this hierarchy
367 */
368 private RolapCubeMember bootstrapLookup(RolapMember rolapMember) {
369 RolapCubeMember parent =
370 rolapMember.getParentMember() == null
371 ? null
372 : rolapMember.getParentMember().isAll()
373 ? currentAllMember
374 : bootstrapLookup(rolapMember.getParentMember());
375 RolapCubeLevel level = cubeLevels[rolapMember.getLevel().getDepth()];
376 return reader.lookupCubeMember(parent, rolapMember, level);
377 }
378
379 public Member getNullMember() {
380 // use lazy initialization to get around bootstrap issues
381 if (currentNullMember == null) {
382 currentNullMember =
383 new RolapCubeMember(
384 null,
385 (RolapMember) rolapHierarchy.getNullMember(),
386 currentNullLevel);
387 }
388 return currentNullMember;
389 }
390
391 /**
392 * Returns the 'all' member.
393 */
394 public RolapCubeMember getAllMember() {
395 return currentAllMember;
396 }
397
398 void setMemberReader(MemberReader memberReader) {
399 rolapHierarchy.setMemberReader(memberReader);
400 }
401
402 MemberReader getMemberReader() {
403 return reader;
404 }
405
406 public void setDefaultMember(Member defaultMeasure) {
407 // refactor this!
408 rolapHierarchy.setDefaultMember(defaultMeasure);
409
410 RolapCubeLevel level =
411 new RolapCubeLevel(
412 (RolapLevel)rolapHierarchy.getDefaultMember().getLevel(),
413 this);
414 currentDefaultMember =
415 new RolapCubeMember(
416 null,
417 (RolapMember) rolapHierarchy.getDefaultMember(),
418 level);
419 }
420
421 void init(MondrianDef.CubeDimension xmlDimension) {
422 // first init shared hierarchy
423 rolapHierarchy.init(xmlDimension);
424 // second init cube hierarchy
425 super.init(xmlDimension);
426 }
427
428 /**
429 * Converts the unique name of a member of the underlying hierarchy to
430 * the appropriate name for this hierarchy.
431 *
432 * <p>For example, if the shared hierarchy is [Time].[Quarterly] and the
433 * hierarchy usage is [Ship Time].[Quarterly], then [Time].[1997].[Q1] would
434 * be translated to [Ship Time].[Quarerly].[1997].[Q1].
435 *
436 * @param memberUniqueName Unique name of member from underlying hierarchy
437 * @return Translated unique name
438 */
439 final String convertMemberName(String memberUniqueName) {
440 if (removePrefixLength > 0) {
441 return uniqueName + memberUniqueName.substring(removePrefixLength);
442 }
443 return memberUniqueName;
444 }
445
446 public final RolapCube getCube() {
447 return cubeDimension.cube;
448 }
449
450 /**
451 * TODO: Since this is part of a caching strategy, should be implemented
452 * as a Strategy Pattern, avoiding hirarchy.
453 */
454 public static interface RolapCubeHierarchyMemberReader
455 extends MemberReader
456 {
457 public RolapCubeMember lookupCubeMember(
458 final RolapCubeMember parent,
459 final RolapMember member,
460 final RolapCubeLevel level);
461 public MemberCacheHelper getRolapCubeMemberCacheHelper();
462 }
463
464 /******
465
466 RolapCubeMember Caching Approach:
467
468 - RolapHierarchy.SmartMemberReader.SmartCacheHelper ->
469 This is the shared cache across shared hierarchies. This
470 member cache only
471 contains members loaded by non-cube specific member lookups. This cache
472 should only contain RolapMembers, not RolapCubeMembers
473
474 - RolapCubeHierarchy.RolapCubeHierarchyMemberReader.rolapCubeCacheHelper ->
475 This cache contains the RolapCubeMember objects, which are cube specific
476 wrappers of shared members.
477
478 - RolapCubeHierarchy.RolapCubeHierarchyMemberReader.SmartCacheHelper ->
479 This is the inherited shared cache from SmartMemberReader, and
480 is used when a join with the fact table is necessary, aka a
481 SqlContextConstraint is used. This cache may be redundant with
482 rolapCubeCacheHelper.
483
484 - A Special note regarding RolapCubeHierarchyMemberReader.cubeSource -
485 This class was required for the special situation getMemberBuilder()
486 method call from RolapNativeSet. This class utilizes both the
487 rolapCubeCacheHelper class for storing RolapCubeMembers, and also the
488 RolapCubeHierarchyMemberReader's inherited SmartCacheHelper.
489
490
491 ******/
492
493
494 /**
495 * member reader wrapper - uses existing member reader,
496 * but wraps and caches all intermediate members.
497 *
498 * <p>Synchronization. Most synchronization takes place within
499 * SmartMemberReader. All synchronization is done on the cacheHelper
500 * object.
501 */
502 public class CacheRolapCubeHierarchyMemberReader
503 extends SmartMemberReader
504 implements RolapCubeHierarchyMemberReader
505 {
506 /**
507 * cubeSource is passed as our member builder
508 */
509 protected final RolapCubeSqlMemberSource cubeSource;
510
511 /**
512 * this cache caches RolapCubeMembers that are light wrappers around
513 * shared and non-shared Hierarchy RolapMembers. The inherited
514 * cacheHelper object contains non-shared hierarchy RolapMembers.
515 * non-shared hierarchy RolapMembers are created when a member lookup
516 * involves the Cube's fact table.
517 */
518 protected MemberCacheHelper rolapCubeCacheHelper;
519 private final boolean enableCache =
520 MondrianProperties.instance().EnableRolapCubeMemberCache.get();
521
522 public CacheRolapCubeHierarchyMemberReader() {
523 super(new SqlMemberSource(RolapCubeHierarchy.this));
524 rolapCubeCacheHelper =
525 new MemberCacheHelper(RolapCubeHierarchy.this);
526
527 cubeSource =
528 new RolapCubeSqlMemberSource(
529 this,
530 RolapCubeHierarchy.this,
531 rolapCubeCacheHelper,
532 cacheHelper);
533
534 cubeSource.setCache(getMemberCache());
535 }
536
537 public MemberBuilder getMemberBuilder() {
538 return this.cubeSource;
539 }
540
541 public MemberCacheHelper getRolapCubeMemberCacheHelper() {
542 return rolapCubeCacheHelper;
543 }
544
545 public List<RolapMember> getRootMembers() {
546 if (rootMembers == null) {
547 rootMembers =
548 getMembersInLevel(cubeLevels[0], 0, Integer.MAX_VALUE);
549 }
550 return rootMembers;
551 }
552
553 protected void readMemberChildren(
554 List<RolapMember> parentMembers,
555 List<RolapMember> children,
556 MemberChildrenConstraint constraint)
557 {
558 List<RolapMember> rolapChildren = new ArrayList<RolapMember>();
559 List<RolapMember> rolapParents = new ArrayList<RolapMember>();
560 Map<String, RolapCubeMember> lookup =
561 new HashMap<String, RolapCubeMember>();
562
563 // extract RolapMembers from their RolapCubeMember objects
564 // populate lookup for reconnecting parents and children
565 for (RolapMember member : parentMembers) {
566 if (member instanceof VisualTotalsFunDef.VisualTotalMember) {
567 continue;
568 }
569 final RolapCubeMember cubeMember = (RolapCubeMember) member;
570 final RolapMember rolapMember = cubeMember.getRolapMember();
571 lookup.put(rolapMember.getUniqueName(), cubeMember);
572 rolapParents.add(rolapMember);
573 }
574
575 // get member children from shared member reader if possible,
576 // if not get them from our own source
577 boolean joinReq =
578 (constraint instanceof SqlContextConstraint);
579 if (joinReq) {
580 super.readMemberChildren(
581 parentMembers, rolapChildren, constraint);
582 } else {
583 rolapHierarchy.getMemberReader().getMemberChildren(
584 rolapParents, rolapChildren, constraint);
585 }
586
587 // now lookup or create RolapCubeMember
588 for (RolapMember currMember : rolapChildren) {
589 RolapCubeMember parent =
590 lookup.get(
591 currMember.getParentMember().getUniqueName());
592 RolapCubeLevel level =
593 parent.getLevel().getChildLevel();
594 if (level == null) {
595 // most likely a parent child hierarchy
596 level = parent.getLevel();
597 }
598 RolapCubeMember newmember =
599 lookupCubeMember(
600 parent, currMember, level);
601 children.add(newmember);
602 }
603
604 // Put them in a temporary hash table first. Register them later,
605 // when we know their size (hence their 'cost' to the cache pool).
606 Map<RolapMember, List<RolapMember>> tempMap =
607 new HashMap<RolapMember, List<RolapMember>>();
608 for (RolapMember member1 : parentMembers) {
609 tempMap.put(member1, Collections.<RolapMember>emptyList());
610 }
611
612 // note that this stores RolapCubeMembers in our cache,
613 // which also stores RolapMembers.
614
615 for (RolapMember child : children) {
616 // todo: We could optimize here. If members.length is small, it's
617 // more efficient to drive from members, rather than hashing
618 // children.length times. We could also exploit the fact that the
619 // result is sorted by ordinal and therefore, unless the "members"
620 // contains members from different levels, children of the same
621 // member will be contiguous.
622 assert child != null : "child";
623 final RolapMember parentMember = child.getParentMember();
624 List<RolapMember> cacheList = tempMap.get(parentMember);
625 if (cacheList == null) {
626 // The list is null if, due to dropped constraints, we now
627 // have a children list of a member we didn't explicitly
628 // ask for it. Adding it to the cache would be viable, but
629 // let's ignore it.
630 continue;
631 } else if (cacheList == Collections.EMPTY_LIST) {
632 cacheList = new ArrayList<RolapMember>();
633 tempMap.put(parentMember, cacheList);
634 }
635 cacheList.add(child);
636 }
637
638 synchronized (cacheHelper) {
639 for (Map.Entry<RolapMember, List<RolapMember>> entry
640 : tempMap.entrySet())
641 {
642 final RolapMember member = entry.getKey();
643 if (rolapCubeCacheHelper.getChildrenFromCache(
644 member, constraint) == null)
645 {
646 final List<RolapMember> cacheList = entry.getValue();
647 if (enableCache) {
648 rolapCubeCacheHelper.putChildren(
649 member, constraint, cacheList);
650 }
651 }
652 }
653 }
654 }
655
656 public void getMemberChildren(
657 List<RolapMember> parentMembers,
658 List<RolapMember> children,
659 MemberChildrenConstraint constraint)
660 {
661 synchronized (cacheHelper) {
662 checkCacheStatus();
663
664 List<RolapMember> missed = new ArrayList<RolapMember>();
665 for (RolapMember parentMember : parentMembers) {
666 List<RolapMember> list =
667 rolapCubeCacheHelper.getChildrenFromCache(
668 parentMember, constraint);
669 if (list == null) {
670 // the null member has no children
671 if (!parentMember.isNull()) {
672 missed.add(parentMember);
673 }
674 } else {
675 children.addAll(list);
676 }
677 }
678 if (missed.size() > 0) {
679 readMemberChildren(missed, children, constraint);
680 }
681 }
682 }
683
684
685 public List<RolapMember> getMembersInLevel(
686 RolapLevel level,
687 int startOrdinal,
688 int endOrdinal,
689 TupleConstraint constraint)
690 {
691 synchronized (cacheHelper) {
692 checkCacheStatus();
693
694 List<RolapMember> members =
695 rolapCubeCacheHelper.getLevelMembersFromCache(
696 level, constraint);
697 if (members != null) {
698 return members;
699 }
700
701 // if a join is required, we need to pass in the RolapCubeLevel
702 // vs. the regular level
703 boolean joinReq =
704 (constraint instanceof SqlContextConstraint);
705 List<RolapMember> list;
706 final RolapCubeLevel cubeLevel = (RolapCubeLevel) level;
707 if (!joinReq) {
708 list =
709 rolapHierarchy.getMemberReader().getMembersInLevel(
710 cubeLevel.getRolapLevel(),
711 startOrdinal, endOrdinal, constraint);
712 } else {
713 list =
714 super.getMembersInLevel(
715 level, startOrdinal, endOrdinal, constraint);
716 }
717 List<RolapMember> newlist = new ArrayList<RolapMember>();
718 for (RolapMember member : list) {
719 // note that there is a special case for the all member
720
721 // REVIEW: disabled, to see what happens. if this code is
722 // for performance, we should check level.isAll at the top
723 // of the method; if it is for correctness, leave the code
724 // in
725 if (false && member == rolapHierarchy.getAllMember()) {
726 newlist.add(getAllMember());
727 } else {
728 RolapCubeMember cubeMember =
729 lookupCubeMemberWithParent(
730 member,
731 cubeLevel);
732 newlist.add(cubeMember);
733 }
734 }
735 rolapCubeCacheHelper.putLevelMembersInCache(
736 level, constraint, newlist);
737
738 return newlist;
739 }
740 }
741
742 private RolapCubeMember lookupCubeMemberWithParent(
743 RolapMember member,
744 RolapCubeLevel cubeLevel)
745 {
746 final RolapMember parentMember = member.getParentMember();
747 final RolapCubeMember parentCubeMember;
748 if (parentMember == null) {
749 parentCubeMember = null;
750 } else {
751 // In parent-child hierarchies, a member's parent may be in the
752 // same level.
753 final RolapCubeLevel parentLevel =
754 parentMember.getLevel() == member.getLevel()
755 ? cubeLevel
756 : cubeLevel.getParentLevel();
757 parentCubeMember =
758 lookupCubeMemberWithParent(
759 parentMember, parentLevel);
760 }
761 return lookupCubeMember(
762 parentCubeMember, member, cubeLevel);
763 }
764
765 public RolapCubeMember lookupCubeMember(
766 RolapCubeMember parent,
767 RolapMember member,
768 RolapCubeLevel level)
769 {
770 synchronized (cacheHelper) {
771 if (member.getKey() == null) {
772 if (member.isAll()) {
773 return getAllMember();
774 }
775
776 throw new NullPointerException();
777 }
778
779 RolapCubeMember cubeMember;
780 if (enableCache) {
781 Object key =
782 rolapCubeCacheHelper.makeKey(parent, member.getKey());
783 cubeMember = (RolapCubeMember)
784 rolapCubeCacheHelper.getMember(key, false);
785 if (cubeMember == null) {
786 cubeMember = new RolapCubeMember(parent, member, level);
787 rolapCubeCacheHelper.putMember(key, cubeMember);
788 }
789 } else {
790 cubeMember = new RolapCubeMember(parent, member, level);
791 }
792 return cubeMember;
793 }
794 }
795
796 public int getMemberCount() {
797 return rolapHierarchy.getMemberReader().getMemberCount();
798 }
799
800 protected void checkCacheStatus() {
801 synchronized (cacheHelper) {
802 // if necessary, flush all caches:
803 // - shared SmartMemberReader RolapMember cache
804 // - local key to cube member RolapCubeMember cache
805 // - cube source RolapCubeMember cache
806 // - local regular RolapMember cache, used when cube
807 // specific joins occur
808
809 if (cacheHelper.getChangeListener() != null) {
810 if (cacheHelper.getChangeListener().isHierarchyChanged(
811 getHierarchy()))
812 {
813 cacheHelper.flushCache();
814 rolapCubeCacheHelper.flushCache();
815
816 if (rolapHierarchy.getMemberReader()
817 instanceof SmartMemberReader)
818 {
819 SmartMemberReader smartMemberReader =
820 (SmartMemberReader)
821 rolapHierarchy.getMemberReader();
822 if (smartMemberReader.getMemberCache()
823 instanceof MemberCacheHelper)
824 {
825 MemberCacheHelper helper =
826 (MemberCacheHelper)
827 smartMemberReader.getMemberCache();
828 helper.flushCache();
829 }
830 }
831 }
832 }
833 }
834 }
835 }
836
837 /**
838 * Same as {@link RolapCubeHierarchyMemberReader} but without caching
839 * anything.
840 */
841 public class NoCacheRolapCubeHierarchyMemberReader
842 extends NoCacheMemberReader
843 implements RolapCubeHierarchyMemberReader
844 {
845 /**
846 * cubeSource is passed as our member builder
847 */
848 protected final RolapCubeSqlMemberSource cubeSource;
849
850 /**
851 * this cache caches RolapCubeMembers that are light wrappers around
852 * shared and non-shared Hierarchy RolapMembers. The inherited
853 * cacheHelper object contains non-shared hierarchy RolapMembers.
854 * non-shared hierarchy RolapMembers are created when a member lookup
855 * involves the Cube's fact table.
856 */
857 protected MemberCacheHelper rolapCubeCacheHelper;
858
859 public NoCacheRolapCubeHierarchyMemberReader() {
860 super(new SqlMemberSource(RolapCubeHierarchy.this));
861 rolapCubeCacheHelper =
862 new MemberNoCacheHelper();
863
864 cubeSource =
865 new RolapCubeSqlMemberSource(
866 this,
867 RolapCubeHierarchy.this,
868 rolapCubeCacheHelper,
869 new MemberNoCacheHelper());
870
871 cubeSource.setCache(rolapCubeCacheHelper);
872 }
873
874 public MemberBuilder getMemberBuilder() {
875 return this.cubeSource;
876 }
877
878 public MemberCacheHelper getRolapCubeMemberCacheHelper() {
879 return rolapCubeCacheHelper;
880 }
881
882 public List<RolapMember> getRootMembers() {
883 return getMembersInLevel(cubeLevels[0], 0, Integer.MAX_VALUE);
884 }
885
886 protected void readMemberChildren(
887 List<RolapMember> parentMembers,
888 List<RolapMember> children,
889 MemberChildrenConstraint constraint)
890 {
891 List<RolapMember> rolapChildren = new ArrayList<RolapMember>();
892 List<RolapMember> rolapParents = new ArrayList<RolapMember>();
893 Map<String, RolapCubeMember> lookup =
894 new HashMap<String, RolapCubeMember>();
895
896 // extract RolapMembers from their RolapCubeMember objects
897 // populate lookup for reconnecting parents and children
898 final List<RolapCubeMember> parentRolapCubeMemberList =
899 Util.cast(parentMembers);
900 for (RolapCubeMember member : parentRolapCubeMemberList) {
901 final RolapMember rolapMember = member.getRolapMember();
902 lookup.put(rolapMember.getUniqueName(), member);
903 rolapParents.add(rolapMember);
904 }
905
906 // get member children from shared member reader if possible,
907 // if not get them from our own source
908 boolean joinReq =
909 (constraint instanceof SqlContextConstraint);
910 if (joinReq) {
911 super.readMemberChildren(
912 parentMembers, rolapChildren, constraint);
913 } else {
914 rolapHierarchy.getMemberReader().getMemberChildren(
915 rolapParents, rolapChildren, constraint);
916 }
917
918 // now lookup or create RolapCubeMember
919 for (RolapMember currMember : rolapChildren) {
920 RolapCubeMember parent =
921 lookup.get(
922 currMember.getParentMember().getUniqueName());
923 RolapCubeLevel level =
924 parent.getLevel().getChildLevel();
925 if (level == null) {
926 // most likely a parent child hierarchy
927 level = parent.getLevel();
928 }
929 RolapCubeMember newmember =
930 lookupCubeMember(
931 parent, currMember, level);
932 children.add(newmember);
933 }
934
935 // Put them in a temporary hash table first. Register them later,
936 // when we know their size (hence their 'cost' to the cache pool).
937 Map<RolapMember, List<RolapMember>> tempMap =
938 new HashMap<RolapMember, List<RolapMember>>();
939 for (RolapMember member1 : parentMembers) {
940 tempMap.put(member1, Collections.<RolapMember>emptyList());
941 }
942
943 // note that this stores RolapCubeMembers in our cache,
944 // which also stores RolapMembers.
945
946 for (RolapMember child : children) {
947 // todo: We could optimize here. If members.length is small, it's
948 // more efficient to drive from members, rather than hashing
949 // children.length times. We could also exploit the fact that the
950 // result is sorted by ordinal and therefore, unless the "members"
951 // contains members from different levels, children of the same
952 // member will be contiguous.
953 assert child != null : "child";
954 final RolapMember parentMember = child.getParentMember();
955 }
956 }
957
958 public void getMemberChildren(
959 List<RolapMember> parentMembers,
960 List<RolapMember> children,
961 MemberChildrenConstraint constraint)
962 {
963 List<RolapMember> missed = new ArrayList<RolapMember>();
964 for (RolapMember parentMember : parentMembers) {
965 // the null member has no children
966 if (!parentMember.isNull()) {
967 missed.add(parentMember);
968 }
969 }
970 if (missed.size() > 0) {
971 readMemberChildren(missed, children, constraint);
972 }
973 }
974
975
976 public List<RolapMember> getMembersInLevel(
977 final RolapLevel level,
978 int startOrdinal,
979 int endOrdinal,
980 TupleConstraint constraint)
981 {
982 List<RolapMember> members = null;
983
984 // if a join is required, we need to pass in the RolapCubeLevel
985 // vs. the regular level
986 boolean joinReq =
987 (constraint instanceof SqlContextConstraint);
988 final List<RolapMember> list;
989
990 if (!joinReq) {
991 list =
992 rolapHierarchy.getMemberReader().getMembersInLevel(
993 ((RolapCubeLevel) level).getRolapLevel(),
994 startOrdinal, endOrdinal, constraint);
995 } else {
996 list =
997 super.getMembersInLevel(
998 level, startOrdinal, endOrdinal, constraint);
999 }
1000
1001 return new UnsupportedList<RolapMember>() {
1002 public RolapMember get(final int index) {
1003 return mutate(list.get(index));
1004 }
1005
1006 public int size() {
1007 return list.size();
1008 }
1009
1010 public Iterator<RolapMember> iterator() {
1011 final Iterator<RolapMember> it = list.iterator();
1012 return new Iterator<RolapMember>() {
1013 public boolean hasNext() {
1014 return it.hasNext();
1015 }
1016 public RolapMember next() {
1017 return mutate(it.next());
1018 }
1019
1020 public void remove() {
1021 throw new UnsupportedOperationException();
1022 }
1023 };
1024 }
1025
1026 private RolapMember mutate(final RolapMember member) {
1027 RolapCubeMember parent = null;
1028 if (member.getParentMember() != null) {
1029 parent =
1030 createAncestorMembers(
1031 (RolapCubeLevel) level.getParentLevel(),
1032 member.getParentMember());
1033 }
1034 return lookupCubeMember(
1035 parent, member, (RolapCubeLevel) level);
1036 }
1037 };
1038 }
1039
1040 private RolapCubeMember createAncestorMembers(
1041 RolapCubeLevel level,
1042 RolapMember member)
1043 {
1044 RolapCubeMember parent = null;
1045 if (member.getParentMember() != null) {
1046 parent =
1047 createAncestorMembers(
1048 level.getParentLevel(), member.getParentMember());
1049 }
1050 return lookupCubeMember(parent, member, level);
1051 }
1052
1053 public RolapCubeMember lookupCubeMember(
1054 RolapCubeMember parent,
1055 RolapMember member,
1056 RolapCubeLevel level)
1057 {
1058 if (member.getKey() == null) {
1059 if (member.isAll()) {
1060 return getAllMember();
1061 }
1062
1063 throw new NullPointerException();
1064 }
1065
1066 return new RolapCubeMember(parent, member, level);
1067 }
1068
1069 public int getMemberCount() {
1070 return rolapHierarchy.getMemberReader().getMemberCount();
1071 }
1072 }
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088 public static class RolapCubeSqlMemberSource extends SqlMemberSource {
1089
1090 private final RolapCubeHierarchyMemberReader memberReader;
1091 private final MemberCacheHelper memberSourceCacheHelper;
1092 private final Object memberCacheLock;
1093
1094 public RolapCubeSqlMemberSource(
1095 RolapCubeHierarchyMemberReader memberReader,
1096 RolapCubeHierarchy hierarchy,
1097 MemberCacheHelper memberSourceCacheHelper,
1098 Object memberCacheLock)
1099 {
1100 super(hierarchy);
1101 this.memberReader = memberReader;
1102 this.memberSourceCacheHelper = memberSourceCacheHelper;
1103 this.memberCacheLock = memberCacheLock;
1104 }
1105
1106 public RolapMember makeMember(
1107 RolapMember parentMember,
1108 RolapLevel childLevel,
1109 Object value,
1110 Object captionValue,
1111 boolean parentChild,
1112 SqlStatement stmt,
1113 Object key,
1114 int columnOffset)
1115 throws SQLException
1116 {
1117 final RolapCubeMember parentCubeMember =
1118 (RolapCubeMember) parentMember;
1119 final RolapCubeLevel childCubeLevel = (RolapCubeLevel) childLevel;
1120 final RolapMember parent;
1121 if (parentMember != null) {
1122 parent = parentCubeMember.getRolapMember();
1123 } else {
1124 parent = null;
1125 }
1126 RolapMember member =
1127 super.makeMember(
1128 parent,
1129 childCubeLevel.getRolapLevel(),
1130 value, captionValue, parentChild, stmt, key,
1131 columnOffset);
1132 return
1133 memberReader.lookupCubeMember(
1134 parentCubeMember,
1135 member, childCubeLevel);
1136 }
1137
1138 public MemberCache getMemberCache() {
1139 // this is a special cache used solely for rolapcubemembers
1140 return memberSourceCacheHelper;
1141 }
1142
1143 /**
1144 * use the same lock in the RolapCubeMemberSource as the
1145 * RolapCubeHiearchyMemberReader to avoid deadlocks
1146 */
1147 public Object getMemberCacheLock() {
1148 return memberCacheLock;
1149 }
1150
1151 public RolapMember allMember() {
1152 return getHierarchy().getAllMember();
1153 }
1154 }
1155 }
1156
1157 // End RolapCubeHierarchy.java