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