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