001 /*
002 // $Id: //open/mondrian-release/3.0/src/main/mondrian/rolap/RolapLevel.java#3 $
003 // This software is subject to the terms of the Common Public License
004 // Agreement, available at the following URL:
005 // http://www.opensource.org/licenses/cpl.html.
006 // Copyright (C) 2001-2002 Kana Software, Inc.
007 // Copyright (C) 2001-2007 Julian Hyde and others
008 // All Rights Reserved.
009 // You must accept the terms of that agreement to use this software.
010 //
011 // jhyde, 10 August, 2001
012 */
013
014 package mondrian.rolap;
015 import mondrian.olap.*;
016 import mondrian.resource.MondrianResource;
017 import mondrian.rolap.sql.SqlQuery;
018
019 import org.apache.log4j.Logger;
020 import java.lang.reflect.Constructor;
021 import java.util.*;
022
023 /**
024 * <code>RolapLevel</code> implements {@link Level} for a ROLAP database.
025 *
026 * @author jhyde
027 * @since 10 August, 2001
028 * @version $Id: //open/mondrian-release/3.0/src/main/mondrian/rolap/RolapLevel.java#3 $
029 */
030 public class RolapLevel extends LevelBase {
031
032 private static final Logger LOGGER = Logger.getLogger(RolapEvaluator.class);
033
034 /**
035 * The column or expression which yields the level's key.
036 */
037 protected MondrianDef.Expression keyExp;
038
039 /**
040 * The column or expression which yields the level's ordinal.
041 */
042 protected MondrianDef.Expression ordinalExp;
043
044 /**
045 * The column or expression which yields the level members' caption.
046 */
047 protected MondrianDef.Expression captionExp;
048
049 private final SqlQuery.Datatype datatype;
050
051 private final int flags;
052
053 static final int FLAG_ALL = 0x02;
054
055 /**
056 * For SQL generator. Whether values of "column" are unique globally
057 * unique (as opposed to unique only within the context of the parent
058 * member).
059 */
060 static final int FLAG_UNIQUE = 0x04;
061
062 private RolapLevel closedPeer;
063
064 private final RolapProperty[] properties;
065 private final RolapProperty[] inheritedProperties;
066
067 /**
068 * Ths expression which gives the name of members of this level. If null,
069 * members are named using the key expression.
070 */
071 protected MondrianDef.Expression nameExp;
072 /** The expression which joins to the parent member in a parent-child
073 * hierarchy, or null if this is a regular hierarchy. */
074 protected MondrianDef.Expression parentExp;
075 /** Value which indicates a null parent in a parent-child hierarchy. */
076 private final String nullParentValue;
077
078 /** Condition under which members are hidden. */
079 private final HideMemberCondition hideMemberCondition;
080 protected final MondrianDef.Closure xmlClosure;
081
082 /**
083 * Creates a level.
084 *
085 * @pre parentExp != null || nullParentValue == null
086 * @pre properties != null
087 * @pre levelType != null
088 * @pre hideMemberCondition != null
089 */
090 RolapLevel(
091 RolapHierarchy hierarchy,
092 int depth,
093 String name,
094 MondrianDef.Expression keyExp,
095 MondrianDef.Expression nameExp,
096 MondrianDef.Expression captionExp,
097 MondrianDef.Expression ordinalExp,
098 MondrianDef.Expression parentExp,
099 String nullParentValue,
100 MondrianDef.Closure xmlClosure,
101 RolapProperty[] properties,
102 int flags,
103 SqlQuery.Datatype datatype,
104 HideMemberCondition hideMemberCondition,
105 LevelType levelType,
106 String approxRowCount)
107 {
108 super(hierarchy, name, depth, levelType);
109 Util.assertPrecondition(properties != null, "properties != null");
110 Util.assertPrecondition(hideMemberCondition != null,
111 "hideMemberCondition != null");
112 Util.assertPrecondition(levelType != null, "levelType != null");
113
114 if (keyExp instanceof MondrianDef.Column) {
115 checkColumn((MondrianDef.Column) keyExp);
116 }
117 this.approxRowCount = loadApproxRowCount(approxRowCount);
118 this.flags = flags;
119 this.datatype = datatype;
120 this.keyExp = keyExp;
121 if (nameExp != null) {
122 if (nameExp instanceof MondrianDef.Column) {
123 checkColumn((MondrianDef.Column) nameExp);
124 }
125 }
126 this.nameExp = nameExp;
127 if (captionExp != null) {
128 if (captionExp instanceof MondrianDef.Column) {
129 checkColumn((MondrianDef.Column) captionExp);
130 }
131 }
132 this.captionExp = captionExp;
133 if (ordinalExp != null) {
134 if (ordinalExp instanceof MondrianDef.Column) {
135 checkColumn((MondrianDef.Column) ordinalExp);
136 }
137 this.ordinalExp = ordinalExp;
138 } else {
139 this.ordinalExp = this.keyExp;
140 }
141 this.parentExp = parentExp;
142 if (parentExp != null) {
143 Util.assertTrue(
144 !isAll(),
145 "'All' level '" + this + "' must not be parent-child");
146 Util.assertTrue(
147 isUnique(),
148 "Parent-child level '" + this
149 + "' must have uniqueMembers=\"true\"");
150 }
151 this.nullParentValue = nullParentValue;
152 Util.assertPrecondition(
153 parentExp != null || nullParentValue == null,
154 "parentExp != null || nullParentValue == null");
155 this.xmlClosure = xmlClosure;
156 for (RolapProperty property : properties) {
157 if (property.getExp() instanceof MondrianDef.Column) {
158 checkColumn((MondrianDef.Column) property.getExp());
159 }
160 }
161 this.properties = properties;
162 List<Property> list = new ArrayList<Property>();
163 for (Level level = this; level != null;
164 level = level.getParentLevel()) {
165 final Property[] levelProperties = level.getProperties();
166 for (final Property levelProperty : levelProperties) {
167 Property existingProperty = lookupProperty(
168 list, levelProperty.getName());
169 if (existingProperty == null) {
170 list.add(levelProperty);
171 } else if (existingProperty.getType() !=
172 levelProperty.getType()) {
173 throw Util.newError(
174 "Property " + this.getName() + "." +
175 levelProperty.getName() + " overrides a " +
176 "property with the same name but different type");
177 }
178 }
179 }
180 this.inheritedProperties = list.toArray(new RolapProperty[list.size()]);
181
182 Dimension dim = hierarchy.getDimension();
183 if (dim.getDimensionType() == DimensionType.TimeDimension) {
184 if (!levelType.isTime() && !isAll()) {
185 throw MondrianResource.instance()
186 .NonTimeLevelInTimeHierarchy.ex(getUniqueName());
187 }
188 } else if (dim.getDimensionType() == null) {
189 // there was no dimension type assigned to the dimension
190 // - check later
191 } else {
192 if (levelType.isTime()) {
193 throw MondrianResource.instance()
194 .TimeLevelInNonTimeHierarchy.ex(getUniqueName());
195 }
196 }
197 this.hideMemberCondition = hideMemberCondition;
198 }
199
200 public RolapHierarchy getHierarchy() {
201 return (RolapHierarchy) hierarchy;
202 }
203
204 private int loadApproxRowCount(String approxRowCount) {
205 boolean notNullAndNumeric =
206 approxRowCount != null
207 && approxRowCount.matches("^\\d+$");
208 if (notNullAndNumeric) {
209 return Integer.parseInt(approxRowCount);
210 } else {
211 // if approxRowCount is not set, return MIN_VALUE to indicate
212 return Integer.MIN_VALUE;
213 }
214 }
215
216 protected Logger getLogger() {
217 return LOGGER;
218 }
219
220 String getTableName() {
221 String tableName = null;
222
223 MondrianDef.Expression expr = getKeyExp();
224 if (expr instanceof MondrianDef.Column) {
225 MondrianDef.Column mc = (MondrianDef.Column) expr;
226 tableName = mc.getTableAlias();
227 }
228 return tableName;
229 }
230
231 public MondrianDef.Expression getKeyExp() {
232 return keyExp;
233 }
234
235 MondrianDef.Expression getOrdinalExp() {
236 return ordinalExp;
237 }
238
239 public MondrianDef.Expression getCaptionExp() {
240 return captionExp;
241 }
242
243 public boolean hasCaptionColumn(){
244 return captionExp != null;
245 }
246
247 final int getFlags() {
248 return flags;
249 }
250
251 HideMemberCondition getHideMemberCondition() {
252 return hideMemberCondition;
253 }
254
255 public final boolean isUnique() {
256 return (flags & FLAG_UNIQUE) != 0;
257 }
258
259 final SqlQuery.Datatype getDatatype() {
260 return datatype;
261 }
262
263 final String getNullParentValue() {
264 return nullParentValue;
265 }
266
267 /**
268 * Returns whether this level is parent-child.
269 */
270 public boolean isParentChild() {
271 return parentExp != null;
272 }
273
274 MondrianDef.Expression getParentExp() {
275 return parentExp;
276 }
277
278 // RME: this has to be public for two of the DrillThroughTest test.
279 public
280 MondrianDef.Expression getNameExp() {
281 return nameExp;
282 }
283
284 private Property lookupProperty(List<Property> list, String propertyName) {
285 for (Property property : list) {
286 if (property.getName().equals(propertyName)) {
287 return property;
288 }
289 }
290 return null;
291 }
292
293 RolapLevel(RolapHierarchy hierarchy, int depth, MondrianDef.Level xmlLevel) {
294 this(
295 hierarchy, depth, xmlLevel.name, xmlLevel.getKeyExp(),
296 xmlLevel.getNameExp(), xmlLevel.getCaptionExp(), xmlLevel.getOrdinalExp(),
297 xmlLevel.getParentExp(), xmlLevel.nullParentValue,
298 xmlLevel.closure, createProperties(xmlLevel),
299 (xmlLevel.uniqueMembers ? FLAG_UNIQUE : 0),
300 xmlLevel.getDatatype(),
301 HideMemberCondition.valueOf(xmlLevel.hideMemberIf),
302 LevelType.valueOf(xmlLevel.levelType), xmlLevel.approxRowCount);
303
304 if (!Util.isEmpty(xmlLevel.caption)) {
305 setCaption(xmlLevel.caption);
306 }
307 if (!Util.isEmpty(xmlLevel.formatter)) {
308 // there is a special member formatter class
309 try {
310 Class<MemberFormatter> clazz =
311 (Class<MemberFormatter>) Class.forName(xmlLevel.formatter);
312 Constructor<MemberFormatter> ctor = clazz.getConstructor();
313 memberFormatter = ctor.newInstance();
314 } catch (Exception e) {
315 throw MondrianResource.instance().MemberFormatterLoadFailed.ex(
316 xmlLevel.formatter, getUniqueName(), e);
317 }
318 }
319 }
320
321 // helper for constructor
322 private static RolapProperty[] createProperties(
323 MondrianDef.Level xmlLevel)
324 {
325 List<RolapProperty> list = new ArrayList<RolapProperty>();
326 final MondrianDef.Expression nameExp = xmlLevel.getNameExp();
327
328 if (nameExp != null) {
329 list.add(
330 new RolapProperty(
331 Property.NAME.name, Property.Datatype.TYPE_STRING,
332 nameExp, null, null, true));
333 }
334 for (int i = 0; i < xmlLevel.properties.length; i++) {
335 MondrianDef.Property property = xmlLevel.properties[i];
336 list.add(
337 new RolapProperty(
338 property.name,
339 convertPropertyTypeNameToCode(property.type),
340 xmlLevel.getPropertyExp(i),
341 property.formatter, property.caption, false));
342 }
343 return list.toArray(new RolapProperty[list.size()]);
344 }
345
346 private static Property.Datatype convertPropertyTypeNameToCode(String type) {
347 if (type.equals("String")) {
348 return Property.Datatype.TYPE_STRING;
349 } else if (type.equals("Numeric")) {
350 return Property.Datatype.TYPE_NUMERIC;
351 } else if (type.equals("Boolean")) {
352 return Property.Datatype.TYPE_BOOLEAN;
353 } else {
354 throw Util.newError("Unknown property type '" + type + "'");
355 }
356 }
357
358 private void checkColumn(MondrianDef.Column nameColumn) {
359 final RolapHierarchy rolapHierarchy = (RolapHierarchy) hierarchy;
360 if (nameColumn.table == null) {
361 final MondrianDef.Relation table = rolapHierarchy.getUniqueTable();
362 if (table == null) {
363 throw Util.newError(
364 "must specify a table for level " +
365 getUniqueName() +
366 " because hierarchy has more than one table");
367 }
368 nameColumn.table = table.getAlias();
369 } else {
370 Util.assertTrue(rolapHierarchy.tableExists(nameColumn.table));
371 }
372 }
373
374 void init(MondrianDef.CubeDimension xmlDimension) {
375 if (xmlClosure != null) {
376 final RolapDimension dimension = ((RolapHierarchy) hierarchy)
377 .createClosedPeerDimension(this, xmlClosure, xmlDimension);
378 closedPeer =
379 (RolapLevel) dimension.getHierarchies()[0].getLevels()[1];
380 }
381 }
382
383 public final boolean isAll() {
384 return (flags & FLAG_ALL) != 0;
385 }
386
387 public boolean areMembersUnique() {
388 return (depth == 0) || (depth == 1) && hierarchy.hasAll();
389 }
390
391 public String getTableAlias() {
392 return keyExp.getTableAlias();
393 }
394
395 public RolapProperty[] getProperties() {
396 return properties;
397 }
398
399 public Property[] getInheritedProperties() {
400 return inheritedProperties;
401 }
402
403 public int getApproxRowCount() {
404 return approxRowCount;
405 }
406
407 /**
408 * Conditions under which a level's members may be hidden (thereby creating
409 * a <dfn>ragged hierarchy</dfn>).
410 */
411 public enum HideMemberCondition {
412 /** A member always appears. */
413 Never,
414
415 /** A member doesn't appear if its name is null or empty. */
416 IfBlankName,
417
418 /** A member appears unless its name matches its parent's. */
419 IfParentsName
420 }
421
422 public OlapElement lookupChild(SchemaReader schemaReader, Id.Segment name) {
423 return lookupChild(schemaReader, name, MatchType.EXACT);
424 }
425
426 public OlapElement lookupChild(
427 SchemaReader schemaReader, Id.Segment name, MatchType matchType)
428 {
429 Member[] levelMembers = schemaReader.getLevelMembers(this, true);
430 if (levelMembers.length > 0) {
431 Member parent = levelMembers[0].getParentMember();
432 return
433 RolapUtil.findBestMemberMatch(
434 Arrays.asList(levelMembers),
435 (RolapMember) parent,
436 this,
437 name,
438 matchType,
439 false);
440 }
441 return null;
442 }
443
444 /**
445 * Returns true when the level is part of a parent/child hierarchy and has
446 * an equivalent closed level.
447 */
448 boolean hasClosedPeer() {
449 return closedPeer != null;
450 }
451
452 public RolapLevel getClosedPeer() {
453 return closedPeer;
454 }
455
456 public static RolapLevel lookupLevel(
457 RolapLevel[] levels,
458 String levelName)
459 {
460 for (RolapLevel level : levels) {
461 if (level.getName().equals(levelName)) {
462 return level;
463 }
464 }
465 return null;
466 }
467 }
468
469 // End RolapLevel.java