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) 2003-2005 Julian Hyde
008// Copyright (C) 2005-2013 Pentaho
009// All Rights Reserved.
010*/
011package mondrian.rolap;
012
013import mondrian.calc.*;
014import mondrian.calc.impl.AbstractCalc;
015import mondrian.calc.impl.GenericCalc;
016import mondrian.olap.*;
017import mondrian.olap.type.StringType;
018import mondrian.rolap.sql.MemberChildrenConstraint;
019import mondrian.rolap.sql.TupleConstraint;
020
021import org.apache.log4j.Logger;
022
023import org.eigenbase.util.property.Property;
024
025import org.olap4j.mdx.IdentifierSegment;
026
027import java.util.*;
028import java.util.concurrent.ConcurrentHashMap;
029import javax.sql.DataSource;
030
031/**
032 * A <code>RolapSchemaReader</code> allows you to read schema objects while
033 * observing the access-control profile specified by a given role.
034 *
035 * @author jhyde
036 * @since Feb 24, 2003
037 */
038public class RolapSchemaReader
039    implements SchemaReader,
040        RolapNativeSet.SchemaReaderWithMemberReaderAvailable,
041        NameResolver.Namespace
042{
043    protected final Role role;
044    private final Map<Hierarchy, MemberReader> hierarchyReaders =
045        new ConcurrentHashMap<Hierarchy, MemberReader>();
046    protected final RolapSchema schema;
047    private final SqlConstraintFactory sqlConstraintFactory =
048        SqlConstraintFactory.instance();
049    private static final Logger LOGGER =
050        Logger.getLogger(RolapSchemaReader.class);
051
052    /**
053     * Creates a RolapSchemaReader.
054     *
055     * @param role Role for access control, must not be null
056     * @param schema Schema
057     */
058    RolapSchemaReader(Role role, RolapSchema schema) {
059        assert role != null : "precondition: role != null";
060        assert schema != null;
061        this.role = role;
062        this.schema = schema;
063    }
064
065    public Role getRole() {
066        return role;
067    }
068
069    public List<Member> getHierarchyRootMembers(Hierarchy hierarchy) {
070        final Role.HierarchyAccess hierarchyAccess =
071            role.getAccessDetails(hierarchy);
072        final Level[] levels = hierarchy.getLevels();
073        final Level firstLevel;
074        if (hierarchyAccess == null) {
075            firstLevel = levels[0];
076        } else {
077            firstLevel = levels[hierarchyAccess.getTopLevelDepth()];
078        }
079        return getLevelMembers(firstLevel, true);
080    }
081
082    /**
083     * This method uses a double-checked locking idiom to avoid making the
084     * method fully synchronized, or potentially creating the same MemberReader
085     * more than once.  Double-checked locking can cause issues if
086     * a second thread accesses the field without either a shared lock in
087     * place or the field being specified as volatile.
088     * In this case, hierarchyReaders is a ConcurrentHashMap,
089     * which internally uses volatile load semantics for read operations.
090     * This assures values written by one thread will be visible when read by
091     * others.
092     * http://en.wikipedia.org/wiki/Double-checked_locking
093     */
094    public MemberReader getMemberReader(Hierarchy hierarchy) {
095        MemberReader memberReader = hierarchyReaders.get(hierarchy);
096        if (memberReader == null) {
097            synchronized (this) {
098                memberReader = hierarchyReaders.get(hierarchy);
099                if (memberReader == null) {
100                    memberReader =
101                        ((RolapHierarchy) hierarchy).createMemberReader(role);
102                    hierarchyReaders.put(hierarchy, memberReader);
103                }
104            }
105        }
106        return memberReader;
107    }
108
109
110    public Member substitute(Member member) {
111        final MemberReader memberReader =
112            getMemberReader(member.getHierarchy());
113        return memberReader.substitute((RolapMember) member);
114    }
115
116    public void getMemberRange(
117        Level level, Member startMember, Member endMember, List<Member> list)
118    {
119        getMemberReader(level.getHierarchy()).getMemberRange(
120            (RolapLevel) level, (RolapMember) startMember,
121            (RolapMember) endMember, Util.<RolapMember>cast(list));
122    }
123
124    public int compareMembersHierarchically(Member m1, Member m2) {
125        RolapMember member1 = (RolapMember) m1;
126        RolapMember member2 = (RolapMember) m2;
127        final RolapHierarchy hierarchy = member1.getHierarchy();
128        Util.assertPrecondition(hierarchy == m2.getHierarchy());
129        return getMemberReader(hierarchy).compare(member1, member2, true);
130    }
131
132    public Member getMemberParent(Member member) {
133        return getMemberReader(member.getHierarchy()).getMemberParent(
134            (RolapMember) member);
135    }
136
137    public int getMemberDepth(Member member) {
138        final Role.HierarchyAccess hierarchyAccess =
139            role.getAccessDetails(member.getHierarchy());
140        if (hierarchyAccess != null) {
141            final int memberDepth = member.getLevel().getDepth();
142            final int topLevelDepth = hierarchyAccess.getTopLevelDepth();
143            return memberDepth - topLevelDepth;
144        } else if (((RolapLevel) member.getLevel()).isParentChild()) {
145            // For members of parent-child hierarchy, members in the same level
146            // may have different depths.
147            int depth = 0;
148            for (Member m = member.getParentMember();
149                m != null;
150                m = m.getParentMember())
151            {
152                depth++;
153            }
154            return depth;
155        } else {
156            return member.getLevel().getDepth();
157        }
158    }
159
160
161    public List<Member> getMemberChildren(Member member) {
162        return getMemberChildren(member, null);
163    }
164
165    public List<Member> getMemberChildren(Member member, Evaluator context) {
166        MemberChildrenConstraint constraint =
167            sqlConstraintFactory.getMemberChildrenConstraint(context);
168        List<RolapMember> memberList =
169            internalGetMemberChildren(member, constraint);
170        return Util.cast(memberList);
171    }
172
173    /**
174     * Helper for getMemberChildren.
175     *
176     * @param member Member
177     * @param constraint Constraint
178     * @return List of children
179     */
180    private List<RolapMember> internalGetMemberChildren(
181        Member member, MemberChildrenConstraint constraint)
182    {
183        List<RolapMember> children = new ArrayList<RolapMember>();
184        final Hierarchy hierarchy = member.getHierarchy();
185        final MemberReader memberReader = getMemberReader(hierarchy);
186        memberReader.getMemberChildren(
187            (RolapMember) member, children, constraint);
188        return children;
189    }
190
191    public void getParentChildContributingChildren(
192        Member dataMember,
193        Hierarchy hierarchy,
194        List<Member> list)
195    {
196        final List<RolapMember> rolapMemberList = Util.cast(list);
197        list.add(dataMember);
198        ((RolapHierarchy) hierarchy).getMemberReader().getMemberChildren(
199            (RolapMember) dataMember, rolapMemberList);
200    }
201
202    public int getChildrenCountFromCache(Member member) {
203        final Hierarchy hierarchy = member.getHierarchy();
204        final MemberReader memberReader = getMemberReader(hierarchy);
205        if (memberReader instanceof
206            RolapCubeHierarchy.RolapCubeHierarchyMemberReader)
207        {
208            List list =
209                ((RolapCubeHierarchy.RolapCubeHierarchyMemberReader)
210                 memberReader)
211                    .getRolapCubeMemberCacheHelper()
212                    .getChildrenFromCache((RolapMember) member, null);
213            if (list == null) {
214                return -1;
215            }
216            return list.size();
217        }
218
219        if (memberReader instanceof SmartMemberReader) {
220            List list = ((SmartMemberReader) memberReader).getMemberCache()
221                .getChildrenFromCache((RolapMember) member, null);
222            if (list == null) {
223                return -1;
224            }
225            return list.size();
226        }
227        if (!(memberReader instanceof MemberCache)) {
228            return -1;
229        }
230        List list = ((MemberCache) memberReader)
231            .getChildrenFromCache((RolapMember) member, null);
232        if (list == null) {
233            return -1;
234        }
235        return list.size();
236    }
237
238    /**
239     * Returns number of members in a level,
240     * if the information can be retrieved from cache.
241     * Otherwise {@link Integer#MIN_VALUE}.
242     *
243     * @param level Level
244     * @return number of members in level
245     */
246    private int getLevelCardinalityFromCache(Level level) {
247        final Hierarchy hierarchy = level.getHierarchy();
248        final MemberReader memberReader = getMemberReader(hierarchy);
249        if (memberReader instanceof
250            RolapCubeHierarchy.RolapCubeHierarchyMemberReader)
251        {
252            final MemberCacheHelper cache =
253                ((RolapCubeHierarchy.RolapCubeHierarchyMemberReader)
254                    memberReader).getRolapCubeMemberCacheHelper();
255            if (cache == null) {
256                return Integer.MIN_VALUE;
257            }
258            final List<RolapMember> list =
259                cache.getLevelMembersFromCache(
260                    (RolapLevel) level, null);
261            if (list == null) {
262                return Integer.MIN_VALUE;
263            }
264            return list.size();
265        }
266
267        if (memberReader instanceof SmartMemberReader) {
268            List<RolapMember> list =
269                ((SmartMemberReader) memberReader)
270                    .getMemberCache()
271                    .getLevelMembersFromCache(
272                        (RolapLevel) level, null);
273            if (list == null) {
274                return Integer.MIN_VALUE;
275            }
276            return list.size();
277        }
278
279        if (memberReader instanceof MemberCache) {
280            List<RolapMember> list =
281                ((MemberCache) memberReader)
282                    .getLevelMembersFromCache(
283                        (RolapLevel) level, null);
284            if (list == null) {
285                return Integer.MIN_VALUE;
286            }
287            return list.size();
288        }
289
290        return Integer.MIN_VALUE;
291    }
292
293    public int getLevelCardinality(
294        Level level,
295        boolean approximate,
296        boolean materialize)
297    {
298        if (!this.role.canAccess(level)) {
299            return 1;
300        }
301
302        int rowCount = Integer.MIN_VALUE;
303        if (approximate) {
304            // See if the schema has an approximation.
305            rowCount = level.getApproxRowCount();
306        }
307
308        if (rowCount == Integer.MIN_VALUE) {
309            // See if the precise row count is available in cache.
310            rowCount = getLevelCardinalityFromCache(level);
311        }
312
313        if (rowCount == Integer.MIN_VALUE) {
314            if (materialize) {
315                // Either the approximate row count hasn't been set,
316                // or they want the precise row count.
317                final MemberReader memberReader =
318                    getMemberReader(level.getHierarchy());
319                rowCount =
320                    memberReader.getLevelMemberCount((RolapLevel) level);
321                // Cache it for future.
322                ((RolapLevel) level).setApproxRowCount(rowCount);
323            }
324        }
325        return rowCount;
326    }
327
328    public List<Member> getMemberChildren(List<Member> members) {
329        return getMemberChildren(members, null);
330    }
331
332    public List<Member> getMemberChildren(
333        List<Member> members,
334        Evaluator context)
335    {
336        if (members.size() == 0) {
337            return Collections.emptyList();
338        } else {
339            MemberChildrenConstraint constraint =
340                sqlConstraintFactory.getMemberChildrenConstraint(context);
341            final Hierarchy hierarchy = members.get(0).getHierarchy();
342            final MemberReader memberReader = getMemberReader(hierarchy);
343            final List<RolapMember> rolapMemberList = Util.cast(members);
344            final List<RolapMember> children = new ArrayList<RolapMember>();
345            memberReader.getMemberChildren(
346                rolapMemberList,
347                children,
348                constraint);
349            return Util.cast(children);
350        }
351    }
352
353    public void getMemberAncestors(Member member, List<Member> ancestorList) {
354        Member parentMember = getMemberParent(member);
355        while (parentMember != null) {
356            ancestorList.add(parentMember);
357            parentMember = getMemberParent(parentMember);
358        }
359    }
360
361    public Cube getCube() {
362        throw new UnsupportedOperationException();
363    }
364
365    public SchemaReader withoutAccessControl() {
366        assert this.getClass() == RolapSchemaReader.class
367            : "Subclass " + getClass() + " must override";
368        if (role == schema.getDefaultRole()) {
369            return this;
370        }
371        return new RolapSchemaReader(schema.getDefaultRole(), schema);
372    }
373
374    public OlapElement getElementChild(OlapElement parent, Id.Segment name) {
375        return getElementChild(parent, name, MatchType.EXACT);
376    }
377
378    public OlapElement getElementChild(
379        OlapElement parent, Id.Segment name, MatchType matchType)
380    {
381        return parent.lookupChild(this, name, matchType);
382    }
383
384    public final Member getMemberByUniqueName(
385        List<Id.Segment> uniqueNameParts,
386        boolean failIfNotFound)
387    {
388        return getMemberByUniqueName(
389            uniqueNameParts, failIfNotFound, MatchType.EXACT);
390    }
391
392    public Member getMemberByUniqueName(
393        List<Id.Segment> uniqueNameParts,
394        boolean failIfNotFound,
395        MatchType matchType)
396    {
397        // In general, this schema reader doesn't have a cube, so we cannot
398        // start looking up members.
399        return null;
400    }
401
402    public OlapElement lookupCompound(
403        OlapElement parent,
404        List<Id.Segment> names,
405        boolean failIfNotFound,
406        int category)
407    {
408        return lookupCompound(
409            parent, names, failIfNotFound, category, MatchType.EXACT);
410    }
411
412    public final OlapElement lookupCompound(
413        OlapElement parent,
414        List<Id.Segment> names,
415        boolean failIfNotFound,
416        int category,
417        MatchType matchType)
418    {
419        if (MondrianProperties.instance().SsasCompatibleNaming.get()) {
420            return new NameResolver().resolve(
421                parent,
422                Util.toOlap4j(names),
423                failIfNotFound,
424                category,
425                matchType,
426                getNamespaces());
427        }
428        return lookupCompoundInternal(
429            parent,
430            names,
431            failIfNotFound,
432            category,
433            matchType);
434    }
435
436    public final OlapElement lookupCompoundInternal(
437        OlapElement parent,
438        List<Id.Segment> names,
439        boolean failIfNotFound,
440        int category,
441        MatchType matchType)
442    {
443        return Util.lookupCompound(
444            this, parent, names, failIfNotFound, category, matchType);
445    }
446
447    public List<NameResolver.Namespace> getNamespaces() {
448        return Collections.<NameResolver.Namespace>singletonList(this);
449    }
450
451    public OlapElement lookupChild(
452        OlapElement parent,
453        IdentifierSegment segment)
454    {
455        return lookupChild(parent, segment, MatchType.EXACT);
456    }
457
458    public OlapElement lookupChild(
459        OlapElement parent,
460        IdentifierSegment segment,
461        MatchType matchType)
462    {
463        OlapElement element = getElementChild(
464            parent,
465            Util.convert(segment),
466            matchType);
467        if (element != null) {
468            return element;
469        }
470        if (parent instanceof Cube) {
471            // Named sets defined at the schema level do not, of course, belong
472            // to a cube. But if parent is a cube, this indicates that the name
473            // has not been qualified.
474            element = schema.getNamedSet(segment);
475        }
476        return element;
477    }
478
479    public Member lookupMemberChildByName(
480        Member parent, Id.Segment childName, MatchType matchType)
481    {
482        if (LOGGER.isDebugEnabled()) {
483            LOGGER.debug(
484                "looking for child \"" + childName + "\" of " + parent);
485        }
486        assert !(parent instanceof RolapHierarchy.LimitedRollupMember);
487        try {
488            MemberChildrenConstraint constraint;
489            if (childName instanceof Id.NameSegment
490                && matchType.isExact())
491            {
492                constraint = sqlConstraintFactory.getChildByNameConstraint(
493                    (RolapMember) parent, (Id.NameSegment) childName);
494            } else {
495                constraint =
496                    sqlConstraintFactory.getMemberChildrenConstraint(null);
497            }
498            List<RolapMember> children =
499                internalGetMemberChildren(parent, constraint);
500            if (children.size() > 0) {
501                return
502                    RolapUtil.findBestMemberMatch(
503                        children,
504                        (RolapMember) parent,
505                        children.get(0).getLevel(),
506                        childName,
507                        matchType);
508            }
509        } catch (NumberFormatException e) {
510            // this was thrown in SqlQuery#quote(boolean numeric, Object
511            // value). This happens when Mondrian searches for unqualified Olap
512            // Elements like [Month], because it tries to look up a member with
513            // that name in all dimensions. Then it generates for example
514            // "select .. from time where year = Month" which will result in a
515            // NFE because "Month" can not be parsed as a number. The real bug
516            // is probably, that Mondrian looks at members at all.
517            //
518            // @see RolapCube#lookupChild()
519            LOGGER.debug(
520                "NumberFormatException in lookupMemberChildByName "
521                + "for parent = \"" + parent
522                + "\", childName=\"" + childName
523                + "\", exception: " + e.getMessage());
524        }
525        return null;
526    }
527
528    public Member getCalculatedMember(List<Id.Segment> nameParts) {
529        // There are no calculated members defined against a schema.
530        return null;
531    }
532
533    public NamedSet getNamedSet(List<Id.Segment> nameParts) {
534        if (nameParts.size() != 1) {
535            return null;
536        }
537        if (!(nameParts.get(0) instanceof Id.NameSegment)) {
538            return null;
539        }
540        final String name = ((Id.NameSegment) nameParts.get(0)).name;
541        return schema.getNamedSet(name);
542    }
543
544    public Member getLeadMember(Member member, int n) {
545        final MemberReader memberReader =
546            getMemberReader(member.getHierarchy());
547        return memberReader.getLeadMember((RolapMember) member, n);
548    }
549
550    public List<Member> getLevelMembers(Level level, boolean includeCalculated)
551    {
552        List<Member> members = getLevelMembers(level, null);
553        if (!includeCalculated) {
554            members = SqlConstraintUtils.removeCalculatedMembers(members);
555        }
556        return members;
557    }
558
559    public List<Member> getLevelMembers(Level level, Evaluator context) {
560        TupleConstraint constraint =
561            sqlConstraintFactory.getLevelMembersConstraint(
562                context,
563                new Level[] {level});
564        final MemberReader memberReader =
565            getMemberReader(level.getHierarchy());
566        List<RolapMember> membersInLevel =
567            memberReader.getMembersInLevel(
568                (RolapLevel) level, constraint);
569        return Util.cast(membersInLevel);
570    }
571
572    public List<Dimension> getCubeDimensions(Cube cube) {
573        assert cube != null;
574        final List<Dimension> dimensions = new ArrayList<Dimension>();
575        for (Dimension dimension : cube.getDimensions()) {
576            switch (role.getAccess(dimension)) {
577            case NONE:
578                continue;
579            default:
580                dimensions.add(dimension);
581                break;
582            }
583        }
584        return dimensions;
585    }
586
587    public List<Hierarchy> getDimensionHierarchies(Dimension dimension) {
588        assert dimension != null;
589        final List<Hierarchy> hierarchies = new ArrayList<Hierarchy>();
590        for (Hierarchy hierarchy : dimension.getHierarchies()) {
591            switch (role.getAccess(hierarchy)) {
592            case NONE:
593                continue;
594            default:
595                hierarchies.add(hierarchy);
596                break;
597            }
598        }
599        return hierarchies;
600    }
601
602    public List<Level> getHierarchyLevels(Hierarchy hierarchy) {
603        assert hierarchy != null;
604        final Role.HierarchyAccess hierarchyAccess =
605            role.getAccessDetails(hierarchy);
606        final Level[] levels = hierarchy.getLevels();
607        if (hierarchyAccess == null) {
608            return Arrays.asList(levels);
609        }
610        Level topLevel = levels[hierarchyAccess.getTopLevelDepth()];
611        Level bottomLevel = levels[hierarchyAccess.getBottomLevelDepth()];
612        List<Level> restrictedLevels =
613            Arrays.asList(levels).subList(
614                topLevel.getDepth(), bottomLevel.getDepth() + 1);
615        assert restrictedLevels.size() >= 1 : "postcondition";
616        return restrictedLevels;
617    }
618
619    public Member getHierarchyDefaultMember(Hierarchy hierarchy) {
620        assert hierarchy != null;
621        // If the whole hierarchy is inaccessible, return the intrinsic default
622        // member. This is important to construct a evaluator.
623        if (role.getAccess(hierarchy) == Access.NONE) {
624            return hierarchy.getDefaultMember();
625        }
626        return getMemberReader(hierarchy).getDefaultMember();
627    }
628
629    public boolean isDrillable(Member member) {
630        final RolapLevel level = (RolapLevel) member.getLevel();
631        if (level.getParentExp() != null) {
632            // This is a parent-child level, so its children, if any, come from
633            // the same level.
634            //
635            // todo: More efficient implementation
636            return getMemberChildren(member).size() > 0;
637        } else {
638            // This is a regular level. It has children iff there is a lower
639            // level.
640            final Level childLevel = level.getChildLevel();
641            return (childLevel != null)
642                && (role.getAccess(childLevel) != Access.NONE);
643        }
644    }
645
646    public boolean isVisible(Member member) {
647        return !member.isHidden() && role.canAccess(member);
648    }
649
650    public Cube[] getCubes() {
651        List<RolapCube> cubes = schema.getCubeList();
652        List<Cube> visibleCubes = new ArrayList<Cube>(cubes.size());
653
654        for (Cube cube : cubes) {
655            if (role.canAccess(cube)) {
656                visibleCubes.add(cube);
657            }
658        }
659
660        return visibleCubes.toArray(new Cube[visibleCubes.size()]);
661    }
662
663    public List<Member> getCalculatedMembers(Hierarchy hierarchy) {
664        return Collections.emptyList();
665    }
666
667    public List<Member> getCalculatedMembers(Level level) {
668        return Collections.emptyList();
669    }
670
671    public List<Member> getCalculatedMembers() {
672        return Collections.emptyList();
673    }
674
675    public NativeEvaluator getNativeSetEvaluator(
676        FunDef fun, Exp[] args, Evaluator evaluator, Calc calc)
677    {
678        RolapEvaluator revaluator = (RolapEvaluator)
679            AbstractCalc.simplifyEvaluator(calc, evaluator);
680        if (evaluator.nativeEnabled()) {
681            return schema.getNativeRegistry().createEvaluator(
682                revaluator, fun, args);
683        }
684        return null;
685    }
686
687    public Parameter getParameter(String name) {
688        // Scan through schema parameters.
689        for (RolapSchemaParameter parameter : schema.parameterList) {
690            if (Util.equalName(parameter.getName(), name)) {
691                return parameter;
692            }
693        }
694
695        // Scan through mondrian and system properties.
696        List<Property> propertyList =
697            MondrianProperties.instance().getPropertyList();
698        for (Property property : propertyList) {
699            if (property.getPath().equals(name)) {
700                return new SystemPropertyParameter(name, false);
701            }
702        }
703        if (System.getProperty(name) != null) {
704            return new SystemPropertyParameter(name, true);
705        }
706
707        return null;
708    }
709
710    public DataSource getDataSource() {
711        return schema.getInternalConnection().getDataSource();
712    }
713
714    public RolapSchema getSchema() {
715        return schema;
716    }
717
718    public SchemaReader withLocus() {
719        return RolapUtil.locusSchemaReader(
720            schema.getInternalConnection(),
721            this);
722    }
723
724    public Map<? extends Member, Access> getMemberChildrenWithDetails(
725        Member member,
726        Evaluator evaluator)
727    {
728        MemberChildrenConstraint constraint =
729            sqlConstraintFactory.getMemberChildrenConstraint(evaluator);
730        final Hierarchy hierarchy = member.getHierarchy();
731        final MemberReader memberReader = getMemberReader(hierarchy);
732        final ArrayList<RolapMember> memberChildren =
733            new ArrayList<RolapMember>();
734
735        return memberReader.getMemberChildren(
736            (RolapMember) member,
737            memberChildren,
738            constraint);
739    }
740
741    /**
742     * Implementation of {@link Parameter} which is sourced from system
743     * propertes (see {@link System#getProperties()} or mondrian properties
744     * (see {@link MondrianProperties}.
745     * <p/>
746     * <p>The name of the property is the same as the key into the
747     * {@link java.util.Properties} object; for example "java.version" or
748     * "mondrian.trace.level".
749     */
750    private static class SystemPropertyParameter
751        extends ParameterImpl
752    {
753        /**
754         * true if source is a system property;
755         * false if source is a mondrian property.
756         */
757        private final boolean system;
758        /**
759         * Definition of mondrian property, or null if system property.
760         */
761        private final Property propertyDefinition;
762
763        public SystemPropertyParameter(String name, boolean system) {
764            super(
765                name,
766                Literal.nullValue,
767                "System property '" + name + "'",
768                new StringType());
769            this.system = system;
770            this.propertyDefinition =
771                system
772                ? null
773                : MondrianProperties.instance().getPropertyDefinition(name);
774        }
775
776        public Scope getScope() {
777            return Scope.System;
778        }
779
780        public boolean isModifiable() {
781            return false;
782        }
783
784        public Calc compile(ExpCompiler compiler) {
785            return new GenericCalc(new DummyExp(getType())) {
786                public Calc[] getCalcs() {
787                    return new Calc[0];
788                }
789
790                public Object evaluate(Evaluator evaluator) {
791                    if (system) {
792                        final String name =
793                            SystemPropertyParameter.this.getName();
794                        return System.getProperty(name);
795                    } else {
796                        return propertyDefinition.stringValue();
797                    }
798                }
799            };
800        }
801    }
802}
803
804// End RolapSchemaReader.java