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