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