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