001/* 002// This software is subject to the terms of the Eclipse Public License v1.0 003// Agreement, available at the following URL: 004// http://www.eclipse.org/legal/epl-v10.html. 005// You must accept the terms of that agreement to use this software. 006// 007// Copyright (C) 2002-2005 Julian Hyde 008// Copyright (C) 2005-2011 Pentaho and others 009// All Rights Reserved. 010// 011// jhyde, 21 March, 2002 012*/ 013package mondrian.rolap; 014 015import mondrian.olap.*; 016import mondrian.resource.MondrianResource; 017 018import org.apache.log4j.Logger; 019 020/** 021 * A <code>HierarchyUsage</code> is the usage of a hierarchy in the context 022 * of a cube. Private hierarchies can only be used in their own 023 * cube. Public hierarchies can be used in several cubes. The problem comes 024 * when several cubes which the same public hierarchy are brought together 025 * in one virtual cube. There are now several usages of the same public 026 * hierarchy. Which one to use? It depends upon what measure we are 027 * currently using. We should use the hierarchy usage for the fact table 028 * which underlies the measure. That is what determines the foreign key to 029 * join on. 030 * 031 * A <code>HierarchyUsage</code> is identified by 032 * <code>(hierarchy.sharedHierarchy, factTable)</code> if the hierarchy is 033 * shared, or <code>(hierarchy, factTable)</code> if it is private. 034 * 035 * @author jhyde 036 * @since 21 March, 2002 037 */ 038public class HierarchyUsage { 039 private static final Logger LOGGER = Logger.getLogger(HierarchyUsage.class); 040 041 enum Kind { 042 UNKNOWN, 043 SHARED, 044 VIRTUAL, 045 PRIVATE 046 } 047 048 /** 049 * Fact table (or relation) which this usage is joining to. This 050 * identifies the usage, and determines which join conditions need to be 051 * used. 052 */ 053 protected final MondrianDef.Relation fact; 054 055 /** 056 * This matches the hierarchy - may not be unique. 057 * NOT NULL. 058 */ 059 private final String hierarchyName; 060 061 /** 062 * not NULL for DimensionUsage 063 * not NULL for Dimension 064 */ 065 private final String name; 066 067 /** 068 * This is the name used to look up the hierachy usage. When the dimension 069 * has only a single hierachy, then the fullName is simply the 070 * CubeDimension name; there is no need to use the default dimension name. 071 * But, when the dimension has more than one hierachy, then the fullName 072 * is the CubeDimension dotted with the dimension hierachy name. 073 * 074 * <p>NOTE: jhyde, 2009/2/2: The only use of this field today is for 075 * {@link RolapCube#getUsageByName}, which is used only for tracing. 076 */ 077 private final String fullName; 078 079 /** 080 * The foreign key by which this {@link Hierarchy} is joined to 081 * the {@link #fact} table. 082 */ 083 private final String foreignKey; 084 085 /** 086 * not NULL for DimensionUsage 087 * NULL for Dimension 088 */ 089 private final String source; 090 091 /** 092 * May be null, this is the field that is used to disambiguate column 093 * names in aggregate tables 094 */ 095 private final String usagePrefix; 096 097 // NOT USED 098 private final String level; 099 //final String type; 100 //final String caption; 101 102 /** 103 * Dimension table which contains the primary key for the hierarchy. 104 * (Usually the table of the lowest level of the hierarchy.) 105 */ 106 private MondrianDef.Relation joinTable; 107 108 /** 109 * The expression (usually a {@link mondrian.olap.MondrianDef.Column}) by 110 * which the hierarchy which is joined to the fact table. 111 */ 112 private MondrianDef.Expression joinExp; 113 114 private final Kind kind; 115 116 /** 117 * Creates a HierarchyUsage. 118 * 119 * @param cube Cube 120 * @param hierarchy Hierarchy 121 * @param cubeDim XML definition of a dimension which belongs to a cube 122 */ 123 HierarchyUsage( 124 RolapCube cube, 125 RolapHierarchy hierarchy, 126 MondrianDef.CubeDimension cubeDim) 127 { 128 assert cubeDim != null : "precondition: cubeDim != null"; 129 130 this.fact = cube.fact; 131 132 // Attributes common to all Hierarchy kinds 133 // name 134 // foreignKey 135 this.name = cubeDim.name; 136 this.foreignKey = cubeDim.foreignKey; 137 138 if (cubeDim instanceof MondrianDef.DimensionUsage) { 139 this.kind = Kind.SHARED; 140 141 142 // Shared Hierarchy attributes 143 // source 144 // level 145 MondrianDef.DimensionUsage du = 146 (MondrianDef.DimensionUsage) cubeDim; 147 148 this.hierarchyName = deriveHierarchyName(hierarchy); 149 int index = this.hierarchyName.indexOf('.'); 150 if (index == -1) { 151 this.fullName = this.name; 152 this.source = du.source; 153 } else { 154 String hname = this.hierarchyName.substring( 155 index + 1, this.hierarchyName.length()); 156 157 StringBuilder buf = new StringBuilder(32); 158 buf.append(this.name); 159 buf.append('.'); 160 buf.append(hname); 161 this.fullName = buf.toString(); 162 163 buf.setLength(0); 164 buf.append(du.source); 165 buf.append('.'); 166 buf.append(hname); 167 this.source = buf.toString(); 168 } 169 170 this.level = du.level; 171 this.usagePrefix = du.usagePrefix; 172 173 init(cube, hierarchy, du); 174 175 } else if (cubeDim instanceof MondrianDef.Dimension) { 176 this.kind = Kind.PRIVATE; 177 178 // Private Hierarchy attributes 179 // type 180 // caption 181 MondrianDef.Dimension d = (MondrianDef.Dimension) cubeDim; 182 183 this.hierarchyName = deriveHierarchyName(hierarchy); 184 this.fullName = this.name; 185 186 this.source = null; 187 this.usagePrefix = d.usagePrefix; 188 this.level = null; 189 190 init(cube, hierarchy, null); 191 192 } else if (cubeDim instanceof MondrianDef.VirtualCubeDimension) { 193 this.kind = Kind.VIRTUAL; 194 195 // Virtual Hierarchy attributes 196 MondrianDef.VirtualCubeDimension vd = 197 (MondrianDef.VirtualCubeDimension) cubeDim; 198 199 this.hierarchyName = cubeDim.name; 200 this.fullName = this.name; 201 202 this.source = null; 203 this.usagePrefix = null; 204 this.level = null; 205 206 init(cube, hierarchy, null); 207 208 } else { 209 getLogger().warn( 210 "HierarchyUsage<init>: Unknown cubeDim=" 211 + cubeDim.getClass().getName()); 212 213 this.kind = Kind.UNKNOWN; 214 215 this.hierarchyName = cubeDim.name; 216 this.fullName = this.name; 217 218 this.source = null; 219 this.usagePrefix = null; 220 this.level = null; 221 222 init(cube, hierarchy, null); 223 } 224 if (getLogger().isDebugEnabled()) { 225 getLogger().debug( 226 toString() 227 + ", cubeDim=" 228 + cubeDim.getClass().getName()); 229 } 230 } 231 232 private String deriveHierarchyName(RolapHierarchy hierarchy) { 233 final String name = hierarchy.getName(); 234 if (!MondrianProperties.instance().SsasCompatibleNaming.get()) { 235 return name; 236 } else { 237 final String dimensionName = hierarchy.getDimension().getName(); 238 if (name == null 239 || name.equals("") 240 || name.equals(dimensionName)) 241 { 242 return name; 243 } else { 244 return dimensionName + '.' + name; 245 } 246 } 247 } 248 249 protected Logger getLogger() { 250 return LOGGER; 251 } 252 253 public String getHierarchyName() { 254 return this.hierarchyName; 255 } 256 public String getFullName() { 257 return this.fullName; 258 } 259 public String getName() { 260 return this.name; 261 } 262 public String getForeignKey() { 263 return this.foreignKey; 264 } 265 public String getSource() { 266 return this.source; 267 } 268 public String getLevelName() { 269 return this.level; 270 } 271 public String getUsagePrefix() { 272 return this.usagePrefix; 273 } 274 275 public MondrianDef.Relation getJoinTable() { 276 return this.joinTable; 277 } 278 279 public MondrianDef.Expression getJoinExp() { 280 return this.joinExp; 281 } 282 283 public Kind getKind() { 284 return this.kind; 285 } 286 public boolean isShared() { 287 return this.kind == Kind.SHARED; 288 } 289 public boolean isVirtual() { 290 return this.kind == Kind.VIRTUAL; 291 } 292 public boolean isPrivate() { 293 return this.kind == Kind.PRIVATE; 294 } 295 296 public boolean equals(Object o) { 297 if (o instanceof HierarchyUsage) { 298 HierarchyUsage other = (HierarchyUsage) o; 299 return (this.kind == other.kind) 300 && Util.equals(this.fact, other.fact) 301 && this.hierarchyName.equals(other.hierarchyName) 302 && Util.equalName(this.name, other.name) 303 && Util.equalName(this.source, other.source) 304 && Util.equalName(this.foreignKey, other.foreignKey); 305 } else { 306 return false; 307 } 308 } 309 310 public int hashCode() { 311 int h = fact.hashCode(); 312 h = Util.hash(h, hierarchyName); 313 h = Util.hash(h, name); 314 h = Util.hash(h, source); 315 h = Util.hash(h, foreignKey); 316 return h; 317 } 318 319 public String toString() { 320 StringBuilder buf = new StringBuilder(100); 321 buf.append("HierarchyUsage: "); 322 buf.append("kind="); 323 buf.append(this.kind.name()); 324 buf.append(", hierarchyName="); 325 buf.append(this.hierarchyName); 326 buf.append(", fullName="); 327 buf.append(this.fullName); 328 buf.append(", foreignKey="); 329 buf.append(this.foreignKey); 330 buf.append(", source="); 331 buf.append(this.source); 332 buf.append(", level="); 333 buf.append(this.level); 334 buf.append(", name="); 335 buf.append(this.name); 336 337 return buf.toString(); 338 } 339 340 void init( 341 RolapCube cube, 342 RolapHierarchy hierarchy, 343 MondrianDef.DimensionUsage cubeDim) 344 { 345 // Three ways that a hierarchy can be joined to the fact table. 346 if (cubeDim != null && cubeDim.level != null) { 347 // 1. Specify an explicit 'level' attribute in a <DimensionUsage>. 348 RolapLevel joinLevel = (RolapLevel) 349 Util.lookupHierarchyLevel(hierarchy, cubeDim.level); 350 if (joinLevel == null) { 351 throw MondrianResource.instance() 352 .DimensionUsageHasUnknownLevel.ex( 353 hierarchy.getUniqueName(), 354 cube.getName(), 355 cubeDim.level); 356 } 357 this.joinTable = 358 findJoinTable(hierarchy, joinLevel.getKeyExp().getTableAlias()); 359 this.joinExp = joinLevel.getKeyExp(); 360 } else if (hierarchy.getXmlHierarchy() != null 361 && hierarchy.getXmlHierarchy().primaryKey != null) 362 { 363 // 2. Specify a "primaryKey" attribute of in <Hierarchy>. You must 364 // also specify the "primaryKeyTable" attribute if the hierarchy 365 // is a join (hence has more than one table). 366 this.joinTable = 367 findJoinTable( 368 hierarchy, 369 hierarchy.getXmlHierarchy().primaryKeyTable); 370 this.joinExp = 371 new MondrianDef.Column( 372 this.joinTable.getAlias(), 373 hierarchy.getXmlHierarchy().primaryKey); 374 } else { 375 // 3. If neither of the above, the join is assumed to be to key of 376 // the last level. 377 final Level[] levels = hierarchy.getLevels(); 378 RolapLevel joinLevel = (RolapLevel) levels[levels.length - 1]; 379 this.joinTable = 380 findJoinTable( 381 hierarchy, 382 joinLevel.getKeyExp().getTableAlias()); 383 this.joinExp = joinLevel.getKeyExp(); 384 } 385 386 // Unless this hierarchy is drawing from the fact table, we need 387 // a join expresion and a foreign key. 388 final boolean inFactTable = this.joinTable.equals(cube.getFact()); 389 if (!inFactTable) { 390 if (this.joinExp == null) { 391 throw MondrianResource.instance() 392 .MustSpecifyPrimaryKeyForHierarchy.ex( 393 hierarchy.getUniqueName(), 394 cube.getName()); 395 } 396 if (foreignKey == null) { 397 throw MondrianResource.instance() 398 .MustSpecifyForeignKeyForHierarchy.ex( 399 hierarchy.getUniqueName(), 400 cube.getName()); 401 } 402 } 403 } 404 405 /** 406 * Chooses the table with which to join a hierarchy to the fact table. 407 * 408 * @param hierarchy Hierarchy to be joined 409 * @param tableName Alias of the table; may be omitted if the hierarchy 410 * has only one table 411 * @return A table, never null 412 */ 413 private MondrianDef.Relation findJoinTable( 414 RolapHierarchy hierarchy, 415 String tableName) 416 { 417 final MondrianDef.Relation table; 418 if (tableName == null) { 419 table = hierarchy.getUniqueTable(); 420 if (table == null) { 421 throw MondrianResource.instance() 422 .MustSpecifyPrimaryKeyTableForHierarchy.ex( 423 hierarchy.getUniqueName()); 424 } 425 } else { 426 table = hierarchy.getRelation().find(tableName); 427 if (table == null) { 428 // todo: i18n msg 429 throw Util.newError( 430 "no table '" + tableName 431 + "' found in hierarchy " + hierarchy.getUniqueName()); 432 } 433 } 434 return table; 435 } 436 437} 438 439// End HierarchyUsage.java