001 /* 002 // $Id: //open/mondrian/src/main/mondrian/rolap/RolapLevel.java#63 $ 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-2008 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/src/main/mondrian/rolap/RolapLevel.java#63 $ 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 List<Member> levelMembers = schemaReader.getLevelMembers(this, true); 430 if (levelMembers.size() > 0) { 431 Member parent = levelMembers.get(0).getParentMember(); 432 return 433 RolapUtil.findBestMemberMatch( 434 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