001    /*
002    // $Id: //open/mondrian/src/main/mondrian/rolap/RolapSchemaReader.java#54 $
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) 2003-2008 Julian Hyde
007    // All Rights Reserved.
008    // You must accept the terms of that agreement to use this software.
009    //
010    // jhyde, Feb 24, 2003
011    */
012    package mondrian.rolap;
013    
014    import java.util.ArrayList;
015    import java.util.Arrays;
016    import java.util.Collections;
017    import java.util.HashMap;
018    import java.util.List;
019    import java.util.Map;
020    
021    import javax.sql.DataSource;
022    
023    import mondrian.olap.*;
024    import mondrian.olap.type.StringType;
025    import mondrian.rolap.sql.TupleConstraint;
026    import mondrian.rolap.sql.MemberChildrenConstraint;
027    import mondrian.calc.Calc;
028    import mondrian.calc.ExpCompiler;
029    import mondrian.calc.DummyExp;
030    import mondrian.calc.impl.AbstractCalc;
031    import mondrian.calc.impl.GenericCalc;
032    
033    import org.apache.log4j.Logger;
034    import org.eigenbase.util.property.Property;
035    
036    /**
037     * A <code>RolapSchemaReader</code> allows you to read schema objects while
038     * observing the access-control profile specified by a given role.
039     *
040     * @author jhyde
041     * @since Feb 24, 2003
042     * @version $Id: //open/mondrian/src/main/mondrian/rolap/RolapSchemaReader.java#54 $
043     */
044    public abstract class RolapSchemaReader
045        implements SchemaReader, RolapNativeSet.SchemaReaderWithMemberReaderAvailable {
046        private final Role role;
047        private final Map<Hierarchy, MemberReader> hierarchyReaders =
048            new HashMap<Hierarchy, MemberReader>();
049        private final RolapSchema schema;
050        private final SqlConstraintFactory sqlConstraintFactory =
051            SqlConstraintFactory.instance();
052        private static final Logger LOGGER =
053            Logger.getLogger(RolapSchemaReader.class);
054    
055        RolapSchemaReader(Role role, RolapSchema schema) {
056            assert role != null : "precondition: role != null";
057            this.role = role;
058            this.schema = schema;
059        }
060    
061        public Role getRole() {
062            return role;
063        }
064    
065        public List<Member> getHierarchyRootMembers(Hierarchy hierarchy) {
066            final Role.HierarchyAccess hierarchyAccess =
067                role.getAccessDetails(hierarchy);
068            final Level[] levels = hierarchy.getLevels();
069            final Level firstLevel;
070            if (hierarchyAccess == null) {
071                firstLevel = levels[0];
072            } else {
073                firstLevel = levels[hierarchyAccess.getTopLevelDepth()];
074            }
075            return getLevelMembers(firstLevel, true);
076        }
077    
078        public synchronized MemberReader getMemberReader(Hierarchy hierarchy) {
079            MemberReader memberReader = hierarchyReaders.get(hierarchy);
080            if (memberReader == null) {
081                memberReader = ((RolapHierarchy) hierarchy).createMemberReader(role);
082                hierarchyReaders.put(hierarchy, memberReader);
083            }
084            return memberReader;
085        }
086    
087        public Member substitute(Member member) {
088            final MemberReader memberReader =
089                getMemberReader(member.getHierarchy());
090            return memberReader.substitute((RolapMember) member);
091        }
092    
093        public void getMemberRange(
094            Level level, Member startMember, Member endMember, List<Member> list)
095        {
096            getMemberReader(level.getHierarchy()).getMemberRange(
097                (RolapLevel) level, (RolapMember) startMember,
098                (RolapMember) endMember, Util.<RolapMember>cast(list));
099        }
100    
101        public int compareMembersHierarchically(Member m1, Member m2) {
102            RolapMember member1 = (RolapMember) m1;
103            RolapMember member2 = (RolapMember) m2;
104            final RolapHierarchy hierarchy = member1.getHierarchy();
105            Util.assertPrecondition(hierarchy == m2.getHierarchy());
106            return getMemberReader(hierarchy).compare(member1, member2, true);
107        }
108    
109        public Member getMemberParent(Member member) {
110            return getMemberReader(member.getHierarchy()).getMemberParent(
111                (RolapMember) member);
112        }
113    
114        public int getMemberDepth(Member member) {
115            final Role.HierarchyAccess hierarchyAccess = role.getAccessDetails(member.getHierarchy());
116            if (hierarchyAccess != null) {
117                final int memberDepth = member.getLevel().getDepth();
118                final int topLevelDepth = hierarchyAccess.getTopLevelDepth();
119                return memberDepth - topLevelDepth;
120            } else if (((RolapLevel) member.getLevel()).isParentChild()) {
121                // For members of parent-child hierarchy, members in the same level
122                // may have different depths.
123                int depth = 0;
124                for (Member m = member.getParentMember();
125                     m != null;
126                     m = m.getParentMember())
127                {
128                    depth++;
129                }
130                return depth;
131            } else {
132                return member.getLevel().getDepth();
133            }
134        }
135    
136    
137        public List<Member> getMemberChildren(Member member) {
138            return getMemberChildren(member, null);
139        }
140    
141        public List<Member> getMemberChildren(Member member, Evaluator context) {
142            MemberChildrenConstraint constraint =
143                    sqlConstraintFactory.getMemberChildrenConstraint(context);
144            List<RolapMember> memberList =
145                internalGetMemberChildren(member, constraint);
146            return Util.cast(memberList);
147        }
148    
149        private List<RolapMember> internalGetMemberChildren(
150                Member member, MemberChildrenConstraint constraint) {
151            List<RolapMember> children = new ArrayList<RolapMember>();
152            final Hierarchy hierarchy = member.getHierarchy();
153            final MemberReader memberReader = getMemberReader(hierarchy);
154            memberReader.getMemberChildren(
155                    (RolapMember) member, children, constraint);
156            return children;
157        }
158    
159        /**
160         * check, whether members children are cached, and
161         * if yes - return children count
162         * if no  - return -1
163         */
164        public int getChildrenCountFromCache(Member member) {
165            final Hierarchy hierarchy = member.getHierarchy();
166            final MemberReader memberReader = getMemberReader(hierarchy);
167            if (memberReader instanceof
168                    RolapCubeHierarchy.RolapCubeHierarchyMemberReader) {
169                List list =
170                    ((RolapCubeHierarchy.RolapCubeHierarchyMemberReader)memberReader)
171                        .getRolapCubeMemberCacheHelper()
172                            .getChildrenFromCache((RolapMember)member, null);
173                if (list == null) {
174                  return -1;
175                }
176                return list.size();
177            }
178    
179            if (memberReader instanceof SmartMemberReader) {
180                List list = ((SmartMemberReader)memberReader).getMemberCache()
181                                .getChildrenFromCache((RolapMember)member, null);
182                if (list == null) {
183                  return -1;
184                }
185                return list.size();
186            }
187            if (!(memberReader instanceof MemberCache)) {
188                return -1;
189            }
190            List list = ((MemberCache)memberReader)
191                            .getChildrenFromCache((RolapMember)member, null);
192            if (list == null) {
193              return -1;
194            }
195            return list.size();
196        }
197    
198        /**
199         * Returns number of members in a level,
200         * if the information can be retrieved from cache.
201         * Otherwise {@link Integer#MIN_VALUE}.
202         *
203         * @param level Level
204         * @return number of members in level
205         */
206        private int getLevelCardinalityFromCache(Level level) {
207            final Hierarchy hierarchy = level.getHierarchy();
208            final MemberReader memberReader = getMemberReader(hierarchy);
209            if (memberReader instanceof
210                    RolapCubeHierarchy.RolapCubeHierarchyMemberReader) {
211                final MemberCacheHelper cache =
212                    ((RolapCubeHierarchy.RolapCubeHierarchyMemberReader)
213                            memberReader).getRolapCubeMemberCacheHelper();
214                if (cache == null) {
215                    return Integer.MIN_VALUE;
216                }
217                final List<RolapMember> list =
218                    cache.getLevelMembersFromCache(
219                        (RolapLevel) level, null);
220                if (list == null) {
221                    return Integer.MIN_VALUE;
222                }
223                return list.size();
224            }
225    
226            if (memberReader instanceof SmartMemberReader) {
227                List<RolapMember> list =
228                    ((SmartMemberReader) memberReader)
229                        .getMemberCache()
230                        .getLevelMembersFromCache(
231                            (RolapLevel) level, null);
232                    if (list == null) {
233                        return Integer.MIN_VALUE;
234                    }
235                    return list.size();
236            }
237    
238            if (memberReader instanceof MemberCache) {
239                List<RolapMember> list =
240                    ((MemberCache) memberReader)
241                        .getLevelMembersFromCache(
242                            (RolapLevel) level, null);
243                if (list == null) {
244                    return Integer.MIN_VALUE;
245                }
246                return list.size();
247            }
248    
249            return Integer.MIN_VALUE;
250        }
251    
252        public int getLevelCardinality(
253            Level level,
254            boolean approximate,
255            boolean materialize)
256        {
257            if (!this.role.canAccess(level)) {
258                return 1;
259            }
260    
261            int rowCount = Integer.MIN_VALUE;
262            if (approximate) {
263                // See if the schema has an approximation.
264                rowCount = level.getApproxRowCount();
265            }
266    
267            if (rowCount == Integer.MIN_VALUE) {
268                // See if the precise row count is available in cache.
269                rowCount = getLevelCardinalityFromCache(level);
270            }
271    
272            if (rowCount == Integer.MIN_VALUE) {
273                if (materialize) {
274                    // Either the approximate row count hasn't been set,
275                    // or they want the precise row count.
276                    final MemberReader memberReader =
277                        getMemberReader(level.getHierarchy());
278                    rowCount =
279                        memberReader.getLevelMemberCount((RolapLevel) level);
280                    // Cache it for future.
281                    ((RolapLevel) level).setApproxRowCount(rowCount);
282                }
283            }
284            return rowCount;
285        }
286    
287        public List<Member> getMemberChildren(List<Member> members) {
288            return getMemberChildren(members, null);
289        }
290    
291        public List<Member> getMemberChildren(
292            List<Member> members,
293            Evaluator context)
294        {
295            if (members.size() == 0) {
296                return Collections.emptyList();
297            } else {
298                MemberChildrenConstraint constraint =
299                        sqlConstraintFactory.getMemberChildrenConstraint(context);
300                final Hierarchy hierarchy = members.get(0).getHierarchy();
301                final MemberReader memberReader = getMemberReader(hierarchy);
302                final List<RolapMember> rolapMemberList = Util.cast(members);
303                final List<RolapMember> children = new ArrayList<RolapMember>();
304                memberReader.getMemberChildren(
305                    rolapMemberList,
306                    children,
307                    constraint);
308                return Util.cast(children);
309            }
310        }
311    
312        public abstract Cube getCube();
313    
314        public OlapElement getElementChild(OlapElement parent, Id.Segment name) {
315            return getElementChild(parent, name, MatchType.EXACT);
316        }
317    
318        public OlapElement getElementChild(
319            OlapElement parent, Id.Segment name, MatchType matchType)
320        {
321            return parent.lookupChild(this, name, matchType);
322        }
323    
324        public final Member getMemberByUniqueName(
325            List<Id.Segment> uniqueNameParts,
326            boolean failIfNotFound)
327        {
328            return getMemberByUniqueName(
329                uniqueNameParts, failIfNotFound, MatchType.EXACT);
330        }
331    
332        public Member getMemberByUniqueName(
333            List<Id.Segment> uniqueNameParts,
334            boolean failIfNotFound,
335            MatchType matchType)
336        {
337            // In general, this schema reader doesn't have a cube, so we cannot
338            // start looking up members.
339            return null;
340        }
341    
342        public OlapElement lookupCompound(
343            OlapElement parent,
344            List<Id.Segment> names,
345            boolean failIfNotFound,
346            int category)
347        {
348            return lookupCompound(
349                parent, names, failIfNotFound, category, MatchType.EXACT);
350        }
351    
352        public OlapElement lookupCompound(
353            OlapElement parent,
354            List<Id.Segment> names,
355            boolean failIfNotFound,
356            int category,
357            MatchType matchType)
358        {
359            return Util.lookupCompound(
360                this, parent, names, failIfNotFound, category, matchType);
361        }
362    
363        public Member lookupMemberChildByName(Member parent, Id.Segment childName)
364        {
365            return lookupMemberChildByName(parent, childName, MatchType.EXACT);
366        }
367    
368        public Member lookupMemberChildByName(
369            Member parent, Id.Segment childName, MatchType matchType)
370        {
371            LOGGER.debug("looking for child \"" + childName + "\" of " + parent);
372            assert !(parent instanceof RolapHierarchy.LimitedRollupMember);
373            try {
374                MemberChildrenConstraint constraint;
375                if (matchType == MatchType.EXACT) {
376                    constraint = sqlConstraintFactory.getChildByNameConstraint(
377                        (RolapMember) parent, childName);
378                } else {
379                    constraint =
380                        sqlConstraintFactory.getMemberChildrenConstraint(null);
381                }
382                List<RolapMember> children =
383                    internalGetMemberChildren(parent, constraint);
384                if (children.size() > 0) {
385                    return
386                        RolapUtil.findBestMemberMatch(
387                            children,
388                            (RolapMember) parent,
389                            children.get(0).getLevel(),
390                            childName,
391                            matchType,
392                            true);
393                }
394            } catch (NumberFormatException e) {
395                // this was thrown in SqlQuery#quote(boolean numeric, Object value). This happens when
396                // Mondrian searches for unqualified Olap Elements like [Month], because it tries to look up
397                // a member with that name in all dimensions. Then it generates for example
398                // "select .. from time where year = Month" which will result in a NFE because
399                // "Month" can not be parsed as a number. The real bug is probably, that Mondrian
400                // looks at members at all.
401                //
402                // @see RolapCube#lookupChild()
403                LOGGER.debug("NumberFormatException in lookupMemberChildByName for parent = \"" + parent + "\", childName=\"" + childName + "\", exception: " + e.getMessage());
404            }
405            return null;
406        }
407    
408        public Member getCalculatedMember(List<Id.Segment> nameParts) {
409            // There are no calculated members defined against a schema.
410            return null;
411        }
412    
413        public NamedSet getNamedSet(List<Id.Segment> nameParts) {
414            if (nameParts.size() != 1) {
415                return null;
416            }
417            final String name = nameParts.get(0).name;
418            return schema.getNamedSet(name);
419        }
420    
421        public Member getLeadMember(Member member, int n) {
422            final MemberReader memberReader = getMemberReader(member.getHierarchy());
423            return memberReader.getLeadMember((RolapMember) member, n);
424        }
425    
426        public List<Member> getLevelMembers(
427            Level level,
428            boolean includeCalculated)
429        {
430            List<Member> members = getLevelMembers(level, null);
431            if (!includeCalculated) {
432                members = SqlConstraintUtils.removeCalculatedMembers(members);
433            }
434            return members;
435        }
436    
437        public List<Member> getLevelMembers(Level level, Evaluator context) {
438            boolean[] satisfied = {false};
439            TupleConstraint constraint =
440                sqlConstraintFactory.getLevelMembersConstraint(
441                    context,
442                    new Level [] { level },
443                    satisfied);
444            final MemberReader memberReader =
445                getMemberReader(level.getHierarchy());
446            List<RolapMember> membersInLevel =
447                memberReader.getMembersInLevel(
448                    (RolapLevel) level, 0, Integer.MAX_VALUE, constraint);
449            if (!satisfied[0]) {
450                // Could not satisfy the constraint by generating SQL. Apply the
451                // non-empty constraint manually.
452                final Evaluator evaluator = context.push();
453                List<RolapMember> allMembersInLevel = membersInLevel;
454                membersInLevel = new ArrayList<RolapMember>();
455                for (RolapMember member : allMembersInLevel) {
456                    evaluator.setContext(member);
457                    if (evaluator.evaluateCurrent() != null) {
458                        membersInLevel.add(member);
459                    }
460                }
461            }
462            return Util.cast(membersInLevel);
463        }
464    
465        public List<Level> getHierarchyLevels(Hierarchy hierarchy) {
466            assert hierarchy != null;
467            final Role.HierarchyAccess hierarchyAccess =
468                role.getAccessDetails(hierarchy);
469            final Level[] levels = hierarchy.getLevels();
470            if (hierarchyAccess == null) {
471                return Arrays.asList(levels);
472            }
473            Level topLevel = levels[hierarchyAccess.getTopLevelDepth()];
474            Level bottomLevel = levels[hierarchyAccess.getBottomLevelDepth()];
475            List<Level> restrictedLevels =
476                Arrays.asList(levels).subList(
477                    topLevel.getDepth(), bottomLevel.getDepth() + 1);
478            assert restrictedLevels.size() >= 1 : "postcondition";
479            return restrictedLevels;
480        }
481    
482        public Member getHierarchyDefaultMember(Hierarchy hierarchy) {
483            assert hierarchy != null;
484            // If the whole hierarchy is inaccessible, return the intrinsic default
485            // member. This is important to construct a evaluator.
486            if (role.getAccess(hierarchy) == Access.NONE) {
487                return hierarchy.getDefaultMember();
488            }
489            return getMemberReader(hierarchy).getDefaultMember();
490        }
491    
492        public boolean isDrillable(Member member) {
493            final RolapLevel level = (RolapLevel) member.getLevel();
494            if (level.getParentExp() != null) {
495                // This is a parent-child level, so its children, if any, come from
496                // the same level.
497                //
498                // todo: More efficient implementation
499                return getMemberChildren(member).size() > 0;
500            } else {
501                // This is a regular level. It has children iff there is a lower
502                // level.
503                final Level childLevel = level.getChildLevel();
504                return (childLevel != null) &&
505                        (role.getAccess(childLevel) != Access.NONE);
506            }
507        }
508    
509        public boolean isVisible(Member member) {
510            return !member.isHidden() && role.canAccess(member);
511        }
512    
513        public Cube[] getCubes() {
514            List<RolapCube> cubes = schema.getCubeList();
515            List<Cube> visibleCubes = new ArrayList<Cube>(cubes.size());
516    
517            for (Cube cube : cubes) {
518                if (role.canAccess(cube)) {
519                    visibleCubes.add(cube);
520                }
521            }
522    
523            return visibleCubes.toArray(new Cube[visibleCubes.size()]);
524        }
525    
526        public List<Member> getCalculatedMembers(Hierarchy hierarchy) {
527            return Collections.emptyList();
528        }
529    
530        public List<Member> getCalculatedMembers(Level level) {
531            return Collections.emptyList();
532        }
533    
534        public List<Member> getCalculatedMembers() {
535            return Collections.emptyList();
536        }
537    
538        public NativeEvaluator getNativeSetEvaluator(
539                FunDef fun, Exp[] args, Evaluator evaluator, Calc calc) {
540            RolapEvaluator revaluator = (RolapEvaluator)
541                    AbstractCalc.simplifyEvaluator(calc, evaluator);
542            return schema.getNativeRegistry().createEvaluator(revaluator, fun, args);
543        }
544    
545        public Parameter getParameter(String name) {
546            // Scan through schema parameters.
547            for (RolapSchemaParameter parameter : schema.parameterList) {
548                if (Util.equalName(parameter.getName(), name)) {
549                    return parameter;
550                }
551            }
552    
553            // Scan through mondrian and system properties.
554            List<Property> propertyList = MondrianProperties.instance().getPropertyList();
555            for (Property property : propertyList) {
556                if (property.getPath().equals(name)) {
557                    return new SystemPropertyParameter(name, false);
558                }
559            }
560            if (System.getProperty(name) != null) {
561                return new SystemPropertyParameter(name, true);
562            }
563    
564            return null;
565        }
566    
567        public DataSource getDataSource() {
568            return schema.getInternalConnection().getDataSource();
569        }
570    
571        RolapSchema getSchema() {
572            return schema;
573        }
574    
575        /**
576         * Implementation of {@link Parameter} which is sourced from system
577         * propertes (see {@link System#getProperties()} or mondrian properties
578         * (see {@link MondrianProperties}.
579         *
580         * <p>The name of the property is the same as the key into the
581         * {@link java.util.Properties} object; for example "java.version" or
582         * "mondrian.trace.level".
583         */
584        private static class SystemPropertyParameter
585            extends ParameterImpl
586        {
587            /**
588             * true if source is a system property;
589             * false if source is a mondrian property.
590             */
591            private final boolean system;
592            /**
593             * Definition of mondrian property, or null if system property.
594             */
595            private final Property propertyDefinition;
596    
597            public SystemPropertyParameter(String name, boolean system) {
598                super(name,
599                    Literal.nullValue,
600                    "System property '" + name + "'",
601                    new StringType());
602                this.system = system;
603                this.propertyDefinition =
604                    system ? null :
605                    MondrianProperties.instance().getPropertyDefinition(name);
606            }
607    
608            public Scope getScope() {
609                return Scope.System;
610            }
611    
612            public boolean isModifiable() {
613                return false;
614            }
615    
616            public Calc compile(ExpCompiler compiler) {
617                return new GenericCalc(new DummyExp(getType())) {
618                    public Calc[] getCalcs() {
619                        return new Calc[0];
620                    }
621    
622                    public Object evaluate(Evaluator evaluator) {
623                        if (system) {
624                            final String name = SystemPropertyParameter.this.getName();
625                            return System.getProperty(name);
626                        } else {
627                            return propertyDefinition.stringValue();
628                        }
629                    }
630                };
631            }
632        }
633    }
634    
635    // End RolapSchemaReader.java