KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > mondrian > rolap > RolapMember


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

13
14 package mondrian.rolap;
15
16 import mondrian.olap.*;
17
18 import org.apache.log4j.Logger;
19 import java.util.*;
20
21 /**
22  * A <code>RolapMember</code> is a member of a {@link RolapHierarchy}. There are
23  * sub-classes for {@link RolapStoredMeasure}, {@link RolapCalculatedMember}.
24  *
25  * @author jhyde
26  * @since 10 August, 2001
27  * @version $Id: //open/mondrian/src/main/mondrian/rolap/RolapMember.java#59 $
28  */

29 public class RolapMember extends MemberBase {
30
31     private static final Logger LOGGER = Logger.getLogger(RolapMember.class);
32
33     /**
34      * For members of a level with an ordinal expression defined, the
35      * value of that expression for this member as retrieved via JDBC;
36      * otherwise null.
37      */

38     private Comparable JavaDoc orderKey;
39
40     /**
41      * This returns an array of member arrays where the first member
42      * array are the root members while the last member array are the
43      * leaf members.
44      * <p>
45      * If you know that you will need to get all or most of the members of
46      * a hierarchy, then calling this which gets all of the hierarchy's
47      * members all at once is much faster than getting members one at
48      * a time.
49      *
50      * @param schemaReader Schema reader
51      * @param hierarchy Hierarchy
52      * @return Array of arrays of members
53      */

54     public static Member[][] getAllMembers(SchemaReader schemaReader,
55             Hierarchy hierarchy) {
56
57         long start = System.currentTimeMillis();
58
59         try {
60             List<Member[]> list = new ArrayList<Member[]>(500);
61
62             // Getting the members by Level is the fastest way that I could
63
// find for getting all of a hierarchy's members.
64
Level[] levels = hierarchy.getLevels();
65             for (Level level : levels) {
66                 Member[] members = schemaReader.getLevelMembers(level, true);
67                 if (members != null) {
68                     list.add(members);
69                 }
70             }
71             return list.toArray(new Member[list.size()][]);
72         } finally {
73             if (LOGGER.isDebugEnabled()) {
74                 long end = System.currentTimeMillis();
75                 LOGGER.debug("RolapMember.getAllMembers: time=" +(end-start));
76             }
77         }
78     }
79
80     public static int getHierarchyCardinality(
81         SchemaReader schemaReader,
82         Hierarchy hierarchy)
83     {
84         int cardinality = 0;
85         Level[] levels = hierarchy.getLevels();
86         for (Level level1 : levels) {
87             cardinality += schemaReader.getLevelCardinality(level1, true, true);
88         }
89         return cardinality;
90     }
91
92     /**
93      * This is a Bottom-up/Top-down algorithm for setting member ordinal
94      * values. An array of members for each level is gotten and the
95      * array for the lowest level is traversed setting each member's
96      * parent's parent's etc. member's ordinal if not set working back
97      * down to the leaf member and then going to the next leaf member
98      * and traversing up again.
99      * <p>
100      * The above algorithm only works for hierarchies that have all of its
101      * leaf members in the same level, which is the norm. After all
102      * member ordinal values have been set, the array of members are
103      * traversed making sure that all member's ordinals have been set.
104      * If one is found that is not set, then one must to a full Top-down
105      * setting of the ordinals.
106      * <p>
107      * The Bottom-up/Top-down algorithm is MUCH faster than the Top-down
108      * algorithm.
109      * <p>
110      * The following are times for executing different set ordinals
111      * algorithms for both the FoodMart Sales cube/Store dimension
112      * and a Large Data set with a dimension with about 250,000 members.
113      * <p>
114      * Times:
115      * Original setOrdinals Top-down
116      * Foodmart: 63ms
117      * Large Data set: 651865ms
118      * Calling getAllMembers before calling original setOrdinals Top-down
119      * Foodmart: 32ms
120      * Large Data set: 73880ms
121      * Bottom-up/Top-down
122      * Foodmart: 17ms
123      * Large Data set: 4241ms
124      *
125      *
126      * @param schemaReader
127      * @param seedMember
128      */

129     public static void setOrdinals(SchemaReader schemaReader, Member seedMember) {
130         long start = System.currentTimeMillis();
131
132         try {
133             Hierarchy hierarchy = seedMember.getHierarchy();
134             //int ordinal = 1;
135
int ordinal = hierarchy.hasAll() ? 1 : 0;
136             Member[][] membersArray = getAllMembers(schemaReader, hierarchy);
137             Member[] leafMembers = membersArray[membersArray.length-1];
138
139             // Set all ordinals,
140
for (Member child : leafMembers) {
141                 ordinal = bottomUpSetParentOrdinals(ordinal, child);
142                 ordinal = setOrdinal(child, ordinal);
143             }
144
145             // Now check to see if all ordinals have been set. If the hierarchy
146
// is 'uneven', not all leaf members are at the same level, then
147
// the above setting of ordinals will have missed some.
148
boolean needsFullTopDown = false;
149             for (int i = 0; i < membersArray.length-1; i++) {
150                 Member[] members = membersArray[i];
151                 for (Member member : members) {
152                     if (member.getOrdinal() == -1) {
153                         needsFullTopDown = true;
154                         break;
155                     }
156                 }
157             }
158             // If we must to a full Top-down, then first reset all ordinal
159
// values to -1, and then call the Top-down
160
if (needsFullTopDown) {
161                 for (int i = 0; i < membersArray.length-1; i++) {
162                     Member[] members = membersArray[i];
163                     for (Member member : members) {
164                         if (member instanceof RolapMember) {
165                             ((RolapMember) member).resetOrdinal();
166                         }
167                     }
168                 }
169
170                 // call full Top-down
171
setOrdinalsTopDown(schemaReader, seedMember);
172             }
173         } finally {
174             if (LOGGER.isDebugEnabled()) {
175                 long end = System.currentTimeMillis();
176                 LOGGER.debug("RolapMember.setOrdinals: time=" +(end-start));
177             }
178         }
179     }
180     private static int bottomUpSetParentOrdinals(int ordinal, Member child) {
181         Member parent = child.getParentMember();
182         if ((parent != null) && parent.getOrdinal() == -1) {
183             ordinal = bottomUpSetParentOrdinals(ordinal, parent);
184             ordinal = setOrdinal(parent, ordinal);
185         }
186         return ordinal;
187     }
188
189     private static int setOrdinal(Member member, int ordinal) {
190         if (member instanceof RolapMember) {
191             ((RolapMember) member).setOrdinal(ordinal++);
192         } else {
193             // TODO
194
LOGGER.warn("RolapMember.setAllChildren: NOT RolapMember "+
195                 "member.name=" +member.getName()+
196                 ", member.class=" +member.getClass().getName()+
197                 ", ordinal=" +ordinal
198                 );
199             ordinal++;
200         }
201         return ordinal;
202     }
203
204     /**
205      * This does a depth first setting of the complete member hierarchy as
206      * required by the MEMBER_ORDINAL XMLA element.
207      * For big hierarchies it takes a bunch of time. SQL Server is
208      * relatively fast in comparison so it might be storing such
209      * information in the DB.
210      *
211      * @param schemaReader
212      * @param member
213      */

214     public static void setOrdinalsTopDown(SchemaReader schemaReader, Member member) {
215         long start = System.currentTimeMillis();
216
217         try {
218             Member parent = schemaReader.getMemberParent(member);
219
220             if (parent == null) {
221                 // top of the world
222
int ordinal = 0;
223
224                 Member[] siblings =
225                     schemaReader.getHierarchyRootMembers(member.getHierarchy());
226
227                 for (Member sibling : siblings) {
228                     ordinal = setAllChildren(ordinal, schemaReader, sibling);
229                 }
230
231             } else {
232                 setOrdinalsTopDown(schemaReader, parent);
233             }
234         } finally {
235             if (LOGGER.isDebugEnabled()) {
236                 long end = System.currentTimeMillis();
237                 LOGGER.debug("RolapMember.setOrdinalsTopDown: time=" +(end-start));
238             }
239         }
240     }
241     private static int setAllChildren(
242             int ordinal, SchemaReader schemaReader, Member member) {
243
244         ordinal = setOrdinal(member, ordinal);
245
246         Member[] children = schemaReader.getMemberChildren(member);
247         for (Member child : children) {
248             ordinal = setAllChildren(ordinal, schemaReader, child);
249         }
250
251         return ordinal;
252     }
253
254     /**
255      * Converts a key to a string to be used as part of the member's name
256      * and unique name.
257      *
258      * <p>Usually, it just calls {@link Object#toString}. But if the key is an
259      * integer value represented in a floating-point column, we'd prefer the
260      * integer value. For example, one member of the
261      * <code>[Sales].[Store SQFT]</code> dimension comes out "20319.0" but we'd
262      * like it to be "20319".
263      */

264     private static String JavaDoc keyToString(Object JavaDoc key) {
265         String JavaDoc name;
266         if (key == null) {
267             name = RolapUtil.mdxNullLiteral;
268         } else {
269             name = key.toString();
270         }
271         if ((key instanceof Number JavaDoc) && name.endsWith(".0")) {
272             name = name.substring(0, name.length() - 2);
273         }
274         return name;
275     }
276
277     /** Ordinal of the member within the hierarchy. Some member readers do not
278      * use this property; in which case, they should leave it as its default,
279      * -1. */

280     private int ordinal;
281     private final Object JavaDoc key;
282     /**
283      * Maps property name to property value.
284      *
285      * <p> We expect there to be a lot of members, but few of them will
286      * have properties. So to reduce memory usage, when empty, this is set to
287      * an immutable empty set.
288      */

289     private Map<String JavaDoc, Object JavaDoc> mapPropertyNameToValue;
290
291     /**
292      * Creates a RolapMember
293      *
294      * @param parentMember Parent member
295      * @param level Level this member belongs to
296      * @param key Key to this member in the underlying RDBMS
297      * @param name Name of this member
298      * @param flags Flags describing this member (see {@link #flags}
299      */

300     protected RolapMember(
301             RolapMember parentMember,
302             RolapLevel level,
303             Object JavaDoc key,
304             String JavaDoc name,
305             MemberType flags) {
306         super(parentMember, level, flags);
307
308         this.key = key;
309         this.ordinal = -1;
310         this.mapPropertyNameToValue = Collections.emptyMap();
311
312         if (name != null &&
313                 !(key != null && name.equals(key.toString()))) {
314             // Save memory by only saving the name as a property if it's
315
// different from the key.
316
setProperty(Property.NAME.name, name);
317         } else {
318             setUniqueName(key);
319         }
320     }
321
322     RolapMember(RolapMember parentMember, RolapLevel level, Object JavaDoc value) {
323         this(parentMember, level, value, null, MemberType.REGULAR);
324     }
325
326
327     protected Logger getLogger() {
328         return LOGGER;
329     }
330
331     public RolapLevel getLevel() {
332         return (RolapLevel) level;
333     }
334
335     public RolapHierarchy getHierarchy() {
336         return getLevel().getHierarchy();
337     }
338
339     public RolapMember getParentMember() {
340         return (RolapMember) super.getParentMember();
341     }
342
343     public boolean equals(Object JavaDoc o) {
344         return (o == this) ||
345                 ((o instanceof RolapMember) && equals((RolapMember) o));
346     }
347
348     public boolean equals(OlapElement o) {
349         return (o instanceof RolapMember) &&
350                 equals((RolapMember) o);
351     }
352
353     private boolean equals(RolapMember that) {
354         assert that != null; // public method should have checked
355
return this.getUniqueName().equalsIgnoreCase(that.getUniqueName());
356     }
357
358     void makeUniqueName(HierarchyUsage hierarchyUsage) {
359         if (parentMember == null && key != null) {
360             String JavaDoc n = hierarchyUsage.getName();
361             if (n != null) {
362                 String JavaDoc name = keyToString(key);
363                 n = Util.quoteMdxIdentifier(n);
364                 this.uniqueName = Util.makeFqName(n, name);
365                 if (getLogger().isDebugEnabled()) {
366                     getLogger().debug("RolapMember.makeUniqueName: uniqueName="
367                             +uniqueName);
368                 }
369             }
370         }
371     }
372
373     private void setUniqueName(Object JavaDoc key) {
374         String JavaDoc name = keyToString(key);
375         this.uniqueName = (parentMember == null)
376             ? Util.makeFqName(getHierarchy(), name)
377             : Util.makeFqName(parentMember, name);
378     }
379
380
381     public boolean isCalculatedInQuery() {
382         return false;
383     }
384
385     public String JavaDoc getName() {
386         final String JavaDoc name =
387                 (String JavaDoc) getPropertyValue(Property.NAME.name);
388         return (name != null)
389             ? name
390             : keyToString(key);
391     }
392
393     public void setName(String JavaDoc name) {
394         throw new Error JavaDoc("unsupported");
395     }
396
397     /**
398      * Sets a property of this member to a given value.
399      *
400      * <p>WARNING: Setting system properties such as "$name" may have nasty
401      * side-effects.
402      */

403     public synchronized void setProperty(String JavaDoc name, Object JavaDoc value) {
404         if (name.equals(Property.CAPTION.name)) {
405             setCaption((String JavaDoc)value);
406             return;
407         }
408
409         if (mapPropertyNameToValue.isEmpty()) {
410             // the empty map is shared and immutable; create our own
411
mapPropertyNameToValue = new HashMap<String JavaDoc, Object JavaDoc>();
412         }
413         if (name.equals(Property.NAME.name)) {
414             if (value == null) {
415                 value = RolapUtil.mdxNullLiteral;
416             }
417             setUniqueName(value);
418         }
419
420         if (name.equals(Property.MEMBER_ORDINAL.name)) {
421             String JavaDoc ordinal = (String JavaDoc) value;
422             if (ordinal.startsWith("\"") && ordinal.endsWith("\"")) {
423                 ordinal = ordinal.substring(1, ordinal.length() - 1);
424             }
425             final double d = Double.parseDouble(ordinal);
426             setOrdinal((int) d);
427         }
428
429         mapPropertyNameToValue.put(name, value);
430     }
431
432     public final Object JavaDoc getPropertyValue(String JavaDoc propertyName) {
433         return getPropertyValue(propertyName, true);
434     }
435
436     public Object JavaDoc getPropertyValue(String JavaDoc propertyName, boolean matchCase) {
437         Property property = Property.lookup(propertyName, matchCase);
438         if (property != null) {
439             Schema schema;
440             Member parentMember;
441             List<RolapMember> list;
442             switch (property.ordinal) {
443             case Property.NAME_ORDINAL:
444                 // Do NOT call getName() here. This property is internal,
445
// and must fall through to look in the property list.
446
break;
447
448             case Property.CAPTION_ORDINAL:
449                 return getCaption();
450
451             case Property.CONTRIBUTING_CHILDREN_ORDINAL:
452                 list = new ArrayList<RolapMember>();
453                 getHierarchy().getMemberReader().getMemberChildren(this, list);
454                 return list;
455
456             case Property.CATALOG_NAME_ORDINAL:
457                 // TODO: can't go from member to connection thence to
458
// Connection.getCatalogName()
459
break;
460
461             case Property.SCHEMA_NAME_ORDINAL:
462                 schema = getHierarchy().getDimension().getSchema();
463                 return schema.getName();
464
465             case Property.CUBE_NAME_ORDINAL:
466                 // TODO: can't go from member to cube cube yet
467
break;
468
469             case Property.DIMENSION_UNIQUE_NAME_ORDINAL:
470                 return getHierarchy().getDimension().getUniqueName();
471
472             case Property.HIERARCHY_UNIQUE_NAME_ORDINAL:
473                 return getHierarchy().getUniqueName();
474
475             case Property.LEVEL_UNIQUE_NAME_ORDINAL:
476                 return getLevel().getUniqueName();
477
478             case Property.LEVEL_NUMBER_ORDINAL:
479                 return getLevel().getDepth();
480
481             case Property.MEMBER_UNIQUE_NAME_ORDINAL:
482                 return getUniqueName();
483
484             case Property.MEMBER_NAME_ORDINAL:
485                 return getName();
486
487             case Property.MEMBER_TYPE_ORDINAL:
488                 return getMemberType().ordinal();
489
490             case Property.MEMBER_GUID_ORDINAL:
491                 return null;
492
493             case Property.MEMBER_CAPTION_ORDINAL:
494                 return getCaption();
495
496             case Property.MEMBER_ORDINAL_ORDINAL:
497                 return getOrdinal();
498
499             case Property.CHILDREN_CARDINALITY_ORDINAL:
500                 Integer JavaDoc cardinality;
501
502                 if (isAllMember() && childLevelHasApproxRowCount()) {
503                     cardinality = getLevel().getChildLevel().getApproxRowCount();
504                 } else {
505                     list = new ArrayList<RolapMember>();
506                     getHierarchy().getMemberReader().getMemberChildren(this, list);
507                     cardinality = list.size();
508                 }
509                 return cardinality;
510
511             case Property.PARENT_LEVEL_ORDINAL:
512                 parentMember = getParentMember();
513                 return parentMember == null ? 0 :
514                     parentMember.getLevel().getDepth();
515
516             case Property.PARENT_UNIQUE_NAME_ORDINAL:
517                 parentMember = getParentMember();
518                 return parentMember == null ? null :
519                         parentMember.getUniqueName();
520
521             case Property.PARENT_COUNT_ORDINAL:
522                 parentMember = getParentMember();
523                 return parentMember == null ? 0 : 1;
524
525             case Property.DESCRIPTION_ORDINAL:
526                 return getDescription();
527
528             case Property.VISIBLE_ORDINAL:
529                 break;
530
531             default:
532                 break;
533                 // fall through
534
}
535         }
536         synchronized (this) {
537             if (matchCase) {
538                 return mapPropertyNameToValue.get(propertyName);
539             } else {
540                 for (String JavaDoc key : mapPropertyNameToValue.keySet()) {
541                     if (key.equalsIgnoreCase(propertyName)) {
542                         return mapPropertyNameToValue.get(key);
543                     }
544                 }
545                 return null;
546             }
547         }
548     }
549
550     private boolean childLevelHasApproxRowCount() {
551         return getLevel().getChildLevel().getApproxRowCount() > Integer.MIN_VALUE;
552     }
553
554     private boolean isAllMember() {
555         return getLevel().getHierarchy().hasAll()
556                 && getLevel().getDepth() == 0;
557     }
558
559     public Property[] getProperties() {
560         return level.getInheritedProperties();
561     }
562
563
564     public int getOrdinal() {
565         return ordinal;
566     }
567
568     public Comparable JavaDoc getOrderKey() {
569         return orderKey;
570     }
571     
572     void setOrdinal(int ordinal) {
573         if (this.ordinal == -1) {
574             this.ordinal = ordinal;
575         }
576     }
577
578     void setOrderKey(Comparable JavaDoc orderKey) {
579         this.orderKey = orderKey;
580     }
581     
582     private void resetOrdinal() {
583         this.ordinal = -1;
584     }
585
586     public Object JavaDoc getKey() {
587         return this.key;
588     }
589
590     /**
591      * Compares this member to another {@link RolapMember}.
592      *
593      * <p>The method first compares on keys; null keys always collate last.
594      * If the keys are equal, it compares using unique name.
595      *
596      * <p>This method does not consider {@link #ordinal} field, because
597      * ordinal is only unique within a parent. If you want to compare
598      * members which may be at any position in the hierarchy, use
599      * {@link mondrian.olap.fun.FunUtil#compareHierarchically}.
600      *
601      * @return -1 if this is less, 0 if this is the same, 1 if this is greater
602      */

603     public int compareTo(Object JavaDoc o) {
604         RolapMember other = (RolapMember)o;
605         if (this.key != null && other.key == null) {
606             return 1; // not null is greater than null
607
}
608         if (this.key == null && other.key != null) {
609             return -1; // null is less than not null
610
}
611         // compare by unique name, if both keys are null
612
if (this.key == null && other.key == null) {
613             return this.getUniqueName().compareTo(other.getUniqueName());
614         }
615         // compare by unique name, if one ore both members are null
616
if (this.key == RolapUtil.sqlNullValue ||
617             other.key == RolapUtil.sqlNullValue) {
618             return this.getUniqueName().compareTo(other.getUniqueName());
619         }
620         // as both keys are not null, compare by key
621
// String, Double, Integer should be possible
622
// any key object should be "Comparable"
623
// anyway - keys should be of the same class
624
if (this.key.getClass().equals(other.key.getClass())) {
625             if (this.key instanceof String JavaDoc) {
626                 return Util.compareName((String JavaDoc) this.key, (String JavaDoc) other.key);
627             } else {
628                 return Util.compareKey(this.key, other.key);
629             }
630         }
631         // Compare by unique name in case of different key classes.
632
// This is possible, if a new calculated member is created
633
// in a dimension with an Integer key. The calculated member
634
// has a key of type String.
635
return this.getUniqueName().compareTo(other.getUniqueName());
636     }
637
638     public boolean isHidden() {
639         final RolapLevel rolapLevel = getLevel();
640         switch (rolapLevel.getHideMemberCondition()) {
641         case Never:
642             return false;
643
644         case IfBlankName: {
645             // If the key value in the database is null, then we use
646
// a special key value whose toString() is "null".
647
final String JavaDoc name = getName();
648             return name.equals(RolapUtil.mdxNullLiteral) || name.equals("");
649         }
650
651         case IfParentsName: {
652             final Member parentMember = getParentMember();
653             if (parentMember == null) {
654                 return false;
655             }
656             final String JavaDoc parentName = parentMember.getName();
657             final String JavaDoc name = getName();
658             return (parentName == null ? "" : parentName).equals(
659                     name == null ? "" : name);
660         }
661
662         default:
663             throw Util.badValue(rolapLevel.getHideMemberCondition());
664         }
665     }
666
667     public int getDepth() {
668         return level.getDepth();
669     }
670
671     public Object JavaDoc getSqlKey() {
672         return key;
673     }
674
675     /**
676      * Returns the formatted value of the property named
677      * <code>propertyName</code>.
678      */

679     public String JavaDoc getPropertyFormattedValue(String JavaDoc propertyName) {
680         // do we have a formatter ? if yes, use it
681
Property[] props = getLevel().getProperties();
682         Property prop = null;
683         for (Property prop1 : props) {
684             if (prop1.getName().equals(propertyName)) {
685                 prop = prop1;
686                 break;
687             }
688         }
689         PropertyFormatter pf;
690         if (prop!=null && (pf = prop.getFormatter()) != null) {
691             return pf.formatProperty(this, propertyName,
692                 getPropertyValue(propertyName));
693         }
694
695         Object JavaDoc val = getPropertyValue(propertyName);
696         return (val == null)
697             ? ""
698             : val.toString();
699     }
700
701 }
702
703 // End RolapMember.java
704
Popular Tags