001/*
002// This software is subject to the terms of the Eclipse Public License v1.0
003// Agreement, available at the following URL:
004// http://www.eclipse.org/legal/epl-v10.html.
005// You must accept the terms of that agreement to use this software.
006//
007// Copyright (C) 2002-2005 Julian Hyde
008// Copyright (C) 2005-2013 Pentaho and others
009// All Rights Reserved.
010*/
011package mondrian.olap;
012
013import mondrian.rolap.RolapCube;
014import mondrian.rolap.RolapCubeDimension;
015
016import org.apache.log4j.Logger;
017
018import java.util.*;
019
020/**
021 * Default implementation of the {@link Role} interface.
022 *
023 * @author jhyde, lucboudreau
024 * @since Oct 5, 2002
025 */
026public class RoleImpl implements Role {
027    private boolean mutable = true;
028    private final Map<Schema, Access> schemaGrants =
029        new HashMap<Schema, Access>();
030    private final Map<Cube, Access> cubeGrants =
031        new HashMap<Cube, Access>();
032    private final Map<Dimension, Access> dimensionGrants =
033        new HashMap<Dimension, Access>();
034    private final Map<Hierarchy, HierarchyAccessImpl> hierarchyGrants =
035        new HashMap<Hierarchy, HierarchyAccessImpl>();
036    private static final Logger LOGGER =
037        Logger.getLogger(RoleImpl.class);
038
039    /**
040     * Creates a role with no permissions.
041     */
042    public RoleImpl() {
043    }
044
045    protected RoleImpl clone() {
046        RoleImpl role = new RoleImpl();
047        role.mutable = mutable;
048        role.schemaGrants.putAll(schemaGrants);
049        role.cubeGrants.putAll(cubeGrants);
050        role.dimensionGrants.putAll(dimensionGrants);
051        for (Map.Entry<Hierarchy, HierarchyAccessImpl> entry
052            : hierarchyGrants.entrySet())
053        {
054            role.hierarchyGrants.put(
055                entry.getKey(),
056                (HierarchyAccessImpl) entry.getValue().clone());
057        }
058        return role;
059    }
060
061    /**
062     * Returns a copy of this <code>Role</code> which can be modified.
063     */
064    public RoleImpl makeMutableClone() {
065        RoleImpl role = clone();
066        role.mutable = true;
067        return role;
068    }
069
070    /**
071     * Prevents any further modifications.
072     * @post !isMutable()
073     */
074    public void makeImmutable() {
075        mutable = false;
076    }
077
078    /**
079     * Returns whether modifications are possible.
080     */
081    public boolean isMutable() {
082        return mutable;
083    }
084
085    /**
086     * Defines access to all cubes and dimensions in a schema.
087     *
088     * @param schema Schema whose access to grant/deny.
089     * @param access An {@link Access access code}
090     *
091     * @pre schema != null
092     * @pre access == Access.ALL || access == Access.NONE
093     * || access == Access.ALL_DIMENSIONS
094     * @pre isMutable()
095     */
096    public void grant(Schema schema, Access access) {
097        assert schema != null;
098        assert isMutable();
099        schemaGrants.put(schema, access);
100    }
101
102    public Access getAccess(Schema schema) {
103        assert schema != null;
104        final Access schemaAccess = schemaGrants.get(schema);
105        if (schemaAccess == null) {
106            // No specific rules means full access.
107            return Access.CUSTOM;
108        } else {
109            return schemaAccess;
110        }
111    }
112
113    /**
114     * Converts a null Access object to {@link Access#NONE}.
115     *
116     * @param access Access object or null
117     * @return Access object, never null
118     */
119    private static Access toAccess(Access access) {
120        return access == null ? Access.NONE : access;
121    }
122
123    /**
124     * Defines access to a cube.
125     *
126     * @param cube Cube whose access to grant/deny.
127     * @param access An {@link Access access code}
128     *
129     * @pre cube != null
130     * @pre access == Access.ALL || access == Access.NONE
131     * @pre isMutable()
132     */
133    public void grant(Cube cube, Access access) {
134        Util.assertPrecondition(cube != null, "cube != null");
135        assert access == Access.ALL
136            || access == Access.NONE
137            || access == Access.CUSTOM;
138        Util.assertPrecondition(isMutable(), "isMutable()");
139        LOGGER.debug(
140            "Grant " + access + " on cube " + cube.getName());
141        cubeGrants.put(cube, access);
142        // Set the schema's access to 'custom' if no rules already exist.
143        final Access schemaAccess =
144            getAccess(cube.getSchema());
145        if (schemaAccess == Access.NONE) {
146            LOGGER.debug(
147                "Cascading grant " + Access.CUSTOM + " on schema "
148                + cube.getSchema().getName());
149            grant(cube.getSchema(), Access.CUSTOM);
150        }
151    }
152
153    public Access getAccess(Cube cube) {
154        assert cube != null;
155        // Check for explicit rules.
156        // Both 'custom' and 'all' are good enough
157        Access access = cubeGrants.get(cube);
158        if (access != null) {
159            LOGGER.debug(
160                "Access level " + access
161                + " granted to cube " + cube.getName());
162            return access;
163        }
164        // Check for inheritance from the parent schema
165        // 'All Dimensions' and 'custom' are not good enough
166        access = schemaGrants.get(cube.getSchema());
167        if (access == Access.ALL) {
168            LOGGER.debug(
169                "Access level " + access
170                + " granted to cube " + cube.getName()
171                + " because of the grant to schema "
172                + cube.getSchema().getName());
173            return Access.ALL;
174        }
175        // Deny access
176        LOGGER.debug(
177            "Access denided to cube" + cube.getName());
178        return Access.NONE;
179    }
180
181    /**
182     * Defines access to a dimension.
183     *
184     * @param dimension Dimension whose access to grant/deny.
185     * @param access An Access instance
186     *
187     * @pre dimension != null
188     * @pre access == Access.ALL || access == Access.CUSTOM
189     * || access == Access.NONE
190     * @pre isMutable()
191     */
192    public void grant(Dimension dimension, Access access) {
193        assert dimension != null;
194        assert access == Access.ALL
195            || access == Access.NONE
196            || access == Access.CUSTOM;
197        Util.assertPrecondition(isMutable(), "isMutable()");
198        LOGGER.debug(
199            "Grant " + access + " on dimension " + dimension.getUniqueName());
200        dimensionGrants.put(dimension, access);
201        // Dimension grants do not cascade to the parent cube automatically.
202        // We always figure out the inheritance at runtime since the place
203        // where the dimension is used (either inside of a virtual cube,
204        // a shared dimension or a cube) will influence on the decision.
205    }
206
207    public Access getAccess(Dimension dimension) {
208        assert dimension != null;
209        // Check for explicit rules.
210        Access access = getDimensionGrant(dimension);
211        if (access == Access.CUSTOM) {
212            // For legacy reasons, if there are no accessible hierarchies
213            // and the dimension has an access level of custom, we deny.
214            // TODO Remove for Mondrian 4.0
215            boolean canAccess = false;
216            for (Hierarchy hierarchy : dimension.getHierarchies()) {
217                final HierarchyAccessImpl hierarchyAccess =
218                    hierarchyGrants.get(hierarchy);
219                if (hierarchyAccess != null
220                    && hierarchyAccess.access != Access.NONE)
221                {
222                    canAccess = true;
223                }
224            }
225            if (canAccess) {
226                LOGGER.debug(
227                    "Access level " + access
228                    + " granted to dimension " + dimension.getUniqueName()
229                    + " because of the grant to one of its hierarchy.");
230                return Access.CUSTOM;
231            } else {
232                LOGGER.debug(
233                    "Access denided to dimension " + dimension.getUniqueName()
234                    + " because there are no hierarchies accessible.");
235                return Access.NONE;
236            }
237        } else if (access != null) {
238            LOGGER.debug(
239                "Access level " + access
240                + " granted to dimension " + dimension.getUniqueName()
241                + " because of explicit access rights.");
242            return access;
243        }
244        // Check if this dimension inherits the cube's access rights.
245        // 'Custom' level is not good enough for inherited access.
246        access = checkDimensionAccessByCubeInheritance(dimension);
247        if (access != Access.NONE) {
248            LOGGER.debug(
249                "Access level " + access
250                + " granted to dimension " + dimension.getUniqueName()
251                + " because of the grant to its parent cube.");
252            return access;
253        }
254        // Check access at the schema level.
255        // Levels of 'custom' and 'none' are not good enough.
256        switch (getAccess(dimension.getSchema())) {
257        case ALL:
258            LOGGER.debug(
259                "Access level ALL "
260                + " granted to dimension " + dimension.getUniqueName()
261                + " because of the grant to schema "
262                + dimension.getSchema().getName());
263            return Access.ALL;
264        case ALL_DIMENSIONS:
265            // For all_dimensions to work, the cube access must be
266            // at least 'custom' level
267            if (access != Access.NONE) {
268                return Access.ALL;
269            } else {
270                return Access.NONE;
271            }
272        default:
273            LOGGER.debug(
274                "Access denided to dimension " + dimension.getUniqueName()
275                + " because of the access level of schema "
276                + dimension.getSchema().getName());
277            return Access.NONE;
278        }
279    }
280
281    private Access getDimensionGrant(final Dimension dimension) {
282        if (dimension.isMeasures()) {
283            for (Dimension key : dimensionGrants.keySet()) {
284                if (key == dimension) {
285                    return dimensionGrants.get(key);
286                }
287            }
288            return null;
289        } else {
290            return dimensionGrants.get(dimension);
291        }
292    }
293
294    /**
295     * This method is used to check if the access rights over a dimension
296     * that might be inherited from the parent cube.
297     * <p>It only checks for inherited access, and it presumes that there
298     * are no dimension grants currently given to the dimension passed as an
299     * argument.
300     */
301    private Access checkDimensionAccessByCubeInheritance(Dimension dimension) {
302        assert dimensionGrants.containsKey(dimension) == false
303               || dimension.isMeasures();
304        for (Map.Entry<Cube, Access> cubeGrant : cubeGrants.entrySet()) {
305            final Access access = toAccess(cubeGrant.getValue());
306            // The 'none' and 'custom' access level are not good enough
307            if (access == Access.NONE || access == Access.CUSTOM) {
308                continue;
309            }
310            final Dimension[] dimensions = cubeGrant.getKey().getDimensions();
311            for (Dimension dimension1 : dimensions) {
312                // If the dimensions have the same identity,
313                // we found an access rule.
314                if (dimension == dimension1) {
315                    return cubeGrant.getValue();
316                }
317                // If the passed dimension argument is of class
318                // RolapCubeDimension, we must validate the cube
319                // assignment and make sure the cubes are the same.
320                // If not, skip to the next grant.
321                if (dimension instanceof RolapCubeDimension
322                    && dimension.equals(dimension1)
323                    && !((RolapCubeDimension)dimension1)
324                        .getCube()
325                            .equals(cubeGrant.getKey()))
326                {
327                    continue;
328                }
329                // Last thing is to allow for equality correspondences
330                // to work with virtual cubes.
331                if (cubeGrant.getKey() instanceof RolapCube
332                    && ((RolapCube)cubeGrant.getKey()).isVirtual()
333                    && dimension.equals(dimension1))
334                {
335                    return cubeGrant.getValue();
336                }
337            }
338        }
339        return Access.NONE;
340    }
341
342    /**
343     * Defines access to a hierarchy.
344     *
345     * @param hierarchy Hierarchy whose access to grant/deny.
346     * @param access An {@link Access access code}
347     * @param topLevel Top-most level which can be accessed, or null if the
348     *     highest level. May only be specified if <code>access</code> is
349     *    {@link mondrian.olap.Access#CUSTOM}.
350     * @param bottomLevel Bottom-most level which can be accessed, or null if
351     *     the lowest level. May only be specified if <code>access</code> is
352     *    {@link mondrian.olap.Access#CUSTOM}.
353     *
354     * @param rollupPolicy Rollup policy
355     *
356     * @pre hierarchy != null
357     * @pre Access.instance().isValid(access)
358     * @pre (access == Access.CUSTOM)
359     *      || (topLevel == null &amp;&amp; bottomLevel == null)
360     * @pre topLevel == null || topLevel.getHierarchy() == hierarchy
361     * @pre bottomLevel == null || bottomLevel.getHierarchy() == hierarchy
362     * @pre isMutable()
363     */
364    public void grant(
365        Hierarchy hierarchy,
366        Access access,
367        Level topLevel,
368        Level bottomLevel,
369        RollupPolicy rollupPolicy)
370    {
371        assert hierarchy != null;
372        assert access != null;
373        assert (access == Access.CUSTOM)
374            || (topLevel == null && bottomLevel == null);
375        assert topLevel == null || topLevel.getHierarchy() == hierarchy;
376        assert bottomLevel == null || bottomLevel.getHierarchy() == hierarchy;
377        assert isMutable();
378        assert rollupPolicy != null;
379        LOGGER.debug(
380            "Granting access " + access + " on hierarchy "
381            + hierarchy.getUniqueName());
382        hierarchyGrants.put(
383            hierarchy,
384            new HierarchyAccessImpl(
385                this, hierarchy, access, topLevel, bottomLevel, rollupPolicy));
386        // Cascade the access right to 'custom' on the parent dim if necessary
387        final Access dimAccess =
388            toAccess(dimensionGrants.get(hierarchy.getDimension()));
389        if (dimAccess == Access.NONE) {
390            LOGGER.debug(
391                "Cascading grant CUSTOM on dimension "
392                + hierarchy.getDimension().getUniqueName()
393                + " because of the grant to hierarchy"
394                + hierarchy.getUniqueName());
395            grant(hierarchy.getDimension(), Access.CUSTOM);
396        }
397    }
398
399    public Access getAccess(Hierarchy hierarchy) {
400        assert hierarchy != null;
401        HierarchyAccessImpl hierarchyAccess = hierarchyGrants.get(hierarchy);
402        if (hierarchyAccess != null) {
403            LOGGER.debug(
404                "Access level " + hierarchyAccess.access
405                + " granted to dimension " + hierarchy.getUniqueName());
406            return hierarchyAccess.access;
407        }
408        // There was no explicit rule for this particular hierarchy.
409        // Let's check the parent dimension.
410        Access access = getAccess(hierarchy.getDimension());
411        if (access == Access.ALL) {
412            // Access levels of 'none' and 'custom' are not enough.
413            LOGGER.debug(
414                "Access level ALL "
415                + " granted to hierarchy " + hierarchy.getUniqueName()
416                + " because of the grant to dimension "
417                + hierarchy.getDimension().getUniqueName());
418            return Access.ALL;
419        }
420        // Access denied, since we know that the dimension check has
421        // checked for its parents as well.
422        LOGGER.debug(
423            "Access denided to hierarchy " + hierarchy.getUniqueName());
424        return Access.NONE;
425    }
426
427    public HierarchyAccess getAccessDetails(Hierarchy hierarchy) {
428        Util.assertPrecondition(hierarchy != null, "hierarchy != null");
429        if (hierarchyGrants.containsKey(hierarchy)) {
430            return hierarchyGrants.get(hierarchy);
431        }
432        final Access hierarchyAccess;
433        final Access schemaGrant =
434            schemaGrants.get(hierarchy.getDimension().getSchema());
435        if (schemaGrant != null) {
436            if (schemaGrant == Access.ALL) {
437                hierarchyAccess = Access.ALL;
438            } else {
439                hierarchyAccess = Access.NONE;
440            }
441        } else {
442            hierarchyAccess = Access.ALL;
443        }
444        return new HierarchyAccessImpl(
445            this,
446            hierarchy,
447            hierarchyAccess,
448            null,
449            null,
450            RollupPolicy.HIDDEN);
451    }
452
453    public Access getAccess(Level level) {
454        assert level != null;
455        HierarchyAccessImpl hierarchyAccess =
456                hierarchyGrants.get(level.getHierarchy());
457        if (hierarchyAccess != null
458            && hierarchyAccess.access != Access.NONE)
459        {
460            if (checkLevelIsOkWithRestrictions(
461                    hierarchyAccess,
462                    level))
463            {
464                // We're good. Let it through.
465                LOGGER.debug(
466                    "Access level " + hierarchyAccess.access
467                    + " granted to level " + level.getUniqueName()
468                    + " because of the grant to hierarchy "
469                    + level.getHierarchy().getUniqueName());
470                return hierarchyAccess.access;
471            }
472        }
473        // No information could be deducted from the parent hierarchy.
474        // Let's use the parent dimension.
475        Access access =
476            getAccess(level.getDimension());
477        LOGGER.debug(
478            "Access level " + access
479            + " granted to level " + level.getUniqueName()
480            + " because of the grant to dimension "
481            + level.getDimension().getUniqueName());
482        return access;
483    }
484
485    private static boolean checkLevelIsOkWithRestrictions(
486        HierarchyAccessImpl hierarchyAccess,
487        Level level)
488    {
489        // Check if this level is explicitly excluded by top/bototm
490        // level restrictions.
491        if (level.getDepth() < hierarchyAccess.topLevel.getDepth()) {
492            return false;
493        }
494        if (level.getDepth() > hierarchyAccess.bottomLevel.getDepth()) {
495            return false;
496        }
497        return true;
498    }
499
500    /**
501     * Defines access to a member in a hierarchy.
502     *
503     * <p>Notes:<ol>
504     * <li>The order of grants matters. If you grant/deny access to a
505     *     member, previous grants/denials to its descendants are ignored.</li>
506     * <li>Member grants do not supersde top/bottom levels set using
507     *     {@link #grant(Hierarchy, Access, Level, Level, mondrian.olap.Role.RollupPolicy)}.
508     * <li>If you have access to a member, then you can see its ancestors
509     *     <em>even those explicitly denied</em>, up to the top level.
510     * </ol>
511     *
512     * @pre member != null
513     * @pre isMutable()
514     * @pre getAccess(member.getHierarchy()) == Access.CUSTOM
515     */
516    public void grant(Member member, Access access) {
517        Util.assertPrecondition(member != null, "member != null");
518        assert isMutable();
519        assert getAccess(member.getHierarchy()) == Access.CUSTOM;
520        HierarchyAccessImpl hierarchyAccess =
521            hierarchyGrants.get(member.getHierarchy());
522        assert hierarchyAccess != null;
523        assert hierarchyAccess.access == Access.CUSTOM;
524        hierarchyAccess.grant(this, member, access);
525    }
526
527    public Access getAccess(Member member) {
528        assert member != null;
529        // Always allow access to calculated members.
530        if (member.isCalculatedInQuery()) {
531            return Access.ALL;
532        }
533        // Check if the parent hierarchy has any access
534        // rules for this.
535        final HierarchyAccessImpl hierarchyAccess =
536            hierarchyGrants.get(member.getHierarchy());
537        if (hierarchyAccess != null) {
538            return hierarchyAccess.getAccess(member);
539        }
540        // Then let's check ask the parent level.
541        Access access = getAccess(member.getLevel());
542        LOGGER.debug(
543            "Access level " + access
544            + " granted to level " + member.getUniqueName()
545            + " because of the grant to level "
546            + member.getLevel().getUniqueName());
547        return access;
548    }
549
550    public Access getAccess(NamedSet set) {
551        Util.assertPrecondition(set != null, "set != null");
552        // TODO Named sets cannot be secured at the moment.
553        LOGGER.debug(
554            "Access level ALL "
555            + " granted to NamedSet " + set.getUniqueName()
556            + " because I said so.");
557        return Access.ALL;
558    }
559
560    public boolean canAccess(OlapElement olapElement) {
561        Util.assertPrecondition(olapElement != null, "olapElement != null");
562        if (olapElement instanceof Member) {
563            return getAccess((Member) olapElement) != Access.NONE;
564        } else if (olapElement instanceof Level) {
565            return getAccess((Level) olapElement) != Access.NONE;
566        } else if (olapElement instanceof NamedSet) {
567            return getAccess((NamedSet) olapElement) != Access.NONE;
568        } else if (olapElement instanceof Hierarchy) {
569            return getAccess((Hierarchy) olapElement) != Access.NONE;
570        } else if (olapElement instanceof Cube) {
571            return getAccess((Cube) olapElement) != Access.NONE;
572        } else if (olapElement instanceof Dimension) {
573            return getAccess((Dimension) olapElement) != Access.NONE;
574        } else {
575            return false;
576        }
577    }
578
579    /**
580     * Creates an element which represents all access to a hierarchy.
581     *
582     * @param hierarchy Hierarchy
583     * @return element representing all access to a given hierarchy
584     */
585    public static HierarchyAccess createAllAccess(Hierarchy hierarchy) {
586        return new HierarchyAccessImpl(
587            Util.createRootRole(hierarchy.getDimension().getSchema()),
588            hierarchy, Access.ALL, null, null, Role.RollupPolicy.FULL);
589    }
590
591    /**
592     * Returns a role that is the union of the given roles.
593     *
594     * @param roleList List of roles
595     * @return Union role
596     */
597    public static Role union(final List<Role> roleList) {
598        assert roleList.size() > 0;
599        return new UnionRoleImpl(roleList);
600    }
601
602    // ~ Inner classes --------------------------------------------------------
603
604    /**
605     * Represents the access that a role has to a particular hierarchy.
606     */
607    private static class HierarchyAccessImpl implements Role.HierarchyAccess {
608        private final Hierarchy hierarchy;
609        private final Level topLevel;
610        private final Access access;
611        private final Level bottomLevel;
612        private final Map<String, MemberAccess> memberGrants =
613            new HashMap<String, MemberAccess>();
614        private final RollupPolicy rollupPolicy;
615        private final Role role;
616
617        /**
618         * Creates a <code>HierarchyAccessImpl</code>.
619         * @param role A role this access belongs to.
620         * @param hierarchy A hierarchy this object describes.
621         * @param access The access granted to this role for this hierarchy.
622         * @param topLevel The top level to restrict the role to, or null to
623         * grant access up top the top level of the hierarchy parameter.
624         * @param bottomLevel The bottom level to restrict the role to, or null
625         * to grant access down to the bottom level of the hierarchy parameter.
626         * @param rollupPolicy The rollup policy to apply.
627         */
628        HierarchyAccessImpl(
629            Role role,
630            Hierarchy hierarchy,
631            Access access,
632            Level topLevel,
633            Level bottomLevel,
634            RollupPolicy rollupPolicy)
635        {
636            assert role != null;
637            assert hierarchy != null;
638            assert access != null;
639            assert rollupPolicy != null;
640            this.role = role;
641            this.hierarchy = hierarchy;
642            this.access = access;
643            this.rollupPolicy = rollupPolicy;
644            this.topLevel = topLevel == null
645                ? hierarchy.getLevels()[0]
646                : topLevel;
647            this.bottomLevel = bottomLevel == null
648                ? hierarchy.getLevels()[hierarchy.getLevels().length - 1]
649                : bottomLevel;
650        }
651
652        public HierarchyAccess clone() {
653            HierarchyAccessImpl hierarchyAccess =
654                new HierarchyAccessImpl(
655                    role, hierarchy, access, topLevel,
656                    bottomLevel, rollupPolicy);
657            hierarchyAccess.memberGrants.putAll(memberGrants);
658            return hierarchyAccess;
659        }
660
661        /**
662         * Grants access to a member.
663         *
664         * @param member Member
665         * @param access Access
666         */
667        void grant(RoleImpl role, Member member, Access access) {
668            Util.assertTrue(member.getHierarchy() == hierarchy);
669
670            // Remove any existing grants to descendants of "member"
671            for (Iterator<MemberAccess> memberIter =
672                memberGrants.values().iterator(); memberIter.hasNext();)
673            {
674                MemberAccess mAccess = memberIter.next();
675                if (mAccess.member.isChildOrEqualTo(member)) {
676                    LOGGER.debug(
677                        "Member grant " + mAccess
678                        + " removed because a grant on "
679                        + member.getUniqueName()
680                        + " overrides it.");
681                    memberIter.remove();
682                }
683            }
684
685            LOGGER.debug(
686                "Granting access " + access + " on member "
687                + member.getUniqueName());
688            memberGrants.put(
689                member.getUniqueName(),
690                new MemberAccess(member, access));
691
692            if (access == Access.NONE) {
693                // Since we're denying access, the ancestor's
694                // access level goes from NONE to CUSTOM
695                // and from ALL to RESTRICTED.
696                for (Member m = member.getParentMember();
697                     m != null;
698                     m = m.getParentMember())
699                {
700                    MemberAccess mAccess =
701                        memberGrants.get(m.getUniqueName());
702                    final Access parentAccess =
703                        mAccess == null ? null : mAccess.access;
704                    // If no current access is allowed, upgrade to "custom"
705                    // which means nothing unless explicitly allowed.
706                    if (parentAccess == Access.NONE
707                        && checkLevelIsOkWithRestrictions(
708                            this,
709                            m.getLevel()))
710                    {
711                        LOGGER.debug(
712                            "Cascading grant CUSTOM on member "
713                            + m.getUniqueName()
714                            + " because of the grant to member "
715                            + member.getUniqueName());
716                        memberGrants.put(
717                            m.getUniqueName(),
718                            new MemberAccess(m, Access.CUSTOM));
719                    }
720                    // If the current parent's access is not defined or
721                    // 'all', we switch it to RESTRICTED, meaning
722                    // that the user has access to everything unless
723                    // explicitly denied.
724                    if ((parentAccess == null
725                            || parentAccess == Access.ALL)
726                        && checkLevelIsOkWithRestrictions(
727                            this,
728                            m.getLevel()))
729                    {
730                        LOGGER.debug(
731                            "Cascading grant RESTRICTED on member "
732                            + m.getUniqueName()
733                            + " because of the grant to member "
734                            + member.getUniqueName());
735                        memberGrants.put(
736                            m.getUniqueName(),
737                            new MemberAccess(m, Access.RESTRICTED));
738                    }
739                }
740            } else {
741                // Create 'custom' access for any ancestors of 'member' which
742                // do not have explicit access but which have at least one
743                // child visible.
744                for (Member m = member.getParentMember();
745                     m != null;
746                     m = m.getParentMember())
747                {
748                    if (checkLevelIsOkWithRestrictions(
749                            this,
750                            m.getLevel()))
751                    {
752                        MemberAccess mAccess =
753                            memberGrants.get(m.getUniqueName());
754                        final Access parentAccess =
755                            toAccess(mAccess == null ? null : mAccess.access);
756                        if (parentAccess == Access.NONE) {
757                            LOGGER.debug(
758                                "Cascading grant CUSTOM on member "
759                                + m.getUniqueName()
760                                + " because of the grant to member "
761                                + member.getUniqueName());
762                            memberGrants.put(
763                                m.getUniqueName(),
764                                new MemberAccess(m, Access.CUSTOM));
765                        }
766                    }
767                }
768                // Also set custom access for the parent hierarchy.
769                final Access hierarchyAccess =
770                    role.getAccess(member.getLevel().getHierarchy());
771                if (hierarchyAccess == Access.NONE) {
772                    LOGGER.debug(
773                        "Cascading grant CUSTOM on hierarchy "
774                        + hierarchy.getUniqueName()
775                        + " because of the grant to member "
776                        + member.getUniqueName());
777                    // Upgrade to CUSTOM level.
778                    role.grant(
779                        hierarchy,
780                        Access.CUSTOM,
781                        topLevel,
782                        bottomLevel,
783                        rollupPolicy);
784                }
785            }
786        }
787
788        public Access getAccess(Member member) {
789            if (this.access != Access.CUSTOM) {
790                return this.access;
791            }
792            MemberAccess mAccess =
793                memberGrants.get(member.getUniqueName());
794            Access access = mAccess == null ? null : mAccess.access;
795            // Check for an explicit deny.
796            if (access == Access.NONE) {
797                LOGGER.debug(
798                    "Access level " + Access.NONE
799                    + " granted to member " + member.getUniqueName()
800                    + " because it is explicitly denided.");
801                return Access.NONE;
802            }
803            // Check for explicit grant
804            if (access == Access.ALL || access == Access.CUSTOM) {
805                LOGGER.debug(
806                    "Access level " + access
807                    + " granted to member " + member.getUniqueName());
808                return access;
809            }
810            // Restricted is ok. This means an explicit grant
811            // followed by a deny of one of the children: so custom.
812            if (access == Access.RESTRICTED) {
813                LOGGER.debug(
814                    "Access level " + Access.CUSTOM
815                    + " granted to member " + member.getUniqueName()
816                    + " because it was RESTRICTED. ");
817                return Access.CUSTOM;
818            }
819            // Check if the member is out of the bounds
820            // defined by topLevel and bottomLevel
821            if (!checkLevelIsOkWithRestrictions(this, member.getLevel())) {
822                LOGGER.debug(
823                    "Access denided to member " + member.getUniqueName()
824                    + " because its level " + member.getLevel().getUniqueName()
825                    + " is out of the permitted bounds of between "
826                    + this.topLevel.getUniqueName()
827                    + " and "
828                    + this.bottomLevel.getUniqueName());
829                return Access.NONE;
830            }
831            // Nothing was explicitly defined for this member.
832            // Check for grants on its parents
833            for (Member m = member.getParentMember();
834                m != null;
835                m = m.getParentMember())
836            {
837                MemberAccess pAccess =
838                    memberGrants.get(m.getUniqueName());
839                final Access parentAccess = pAccess == null
840                    ? null
841                    : pAccess.access;
842                if (parentAccess == null) {
843                    // No explicit rules for this parent
844                    continue;
845                }
846                // Check for parent deny
847                if (parentAccess == Access.NONE
848                    || parentAccess == Access.CUSTOM)
849                {
850                    LOGGER.debug(
851                        "Access denided to member " + member.getUniqueName()
852                        + " because its parent " + m.getUniqueName()
853                        + " is of access level " + parentAccess);
854                    return Access.NONE;
855                }
856                // Both RESTRICTED and ALL are OK for parents.
857                LOGGER.debug(
858                    "Access level ALL granted to member "
859                    + member.getUniqueName()
860                    + " because its parent " + m.getUniqueName()
861                    + " is of access level " + parentAccess);
862                return Access.ALL;
863            }
864            // Check for inherited access from ancestors.
865            // "Custom" is not good enough. We are looking for "all" access.
866            access = role.getAccess(member.getLevel());
867            if (access == Access.ALL) {
868                LOGGER.debug(
869                    "Access ALL granted to member " + member.getUniqueName()
870                    + " because its level " + member.getLevel().getUniqueName()
871                    + " is of access level ALL");
872                return Access.ALL;
873            }
874            // This member might be at a level allowed by the
875            // topLevel/bottomLevel attributes. If there are no explicit
876            // member grants defined at this level but the member fits
877            // those bounds, we give access.
878            if (memberGrants.size() == 0) {
879                LOGGER.debug(
880                    "Access level ALL granted to member "
881                    + member.getUniqueName()
882                    + " because it lies between the permitted level bounds and there are no explicit member grants defined in hierarchy "
883                    + member.getHierarchy().getUniqueName());
884                return Access.ALL;
885            }
886            // No access
887            LOGGER.debug(
888                "Access denided to member " + member.getUniqueName()
889                + " because none of its parents allow access to it.");
890            return Access.NONE;
891        }
892
893        public final int getTopLevelDepth() {
894            return topLevel.getDepth();
895        }
896
897        public final int getBottomLevelDepth() {
898            return bottomLevel.getDepth();
899        }
900
901        public RollupPolicy getRollupPolicy() {
902            return rollupPolicy;
903        }
904
905        /**
906         * Tells whether a given member has some of its children being
907         * restricted by the access controls of this role instance.
908         */
909        public boolean hasInaccessibleDescendants(Member member) {
910            for (MemberAccess access : memberGrants.values()) {
911                switch (access.access) {
912                case NONE:
913                case CUSTOM:
914                    if (access.isSubGrant(member)) {
915                        // At least one of the limited member is
916                        // part of the descendants of this member.
917                        return true;
918                    }
919                }
920            }
921            // All descendants are accessible.
922            return false;
923        }
924    }
925
926    /**
927     * A MemberAccess contains information about a grant applied
928     * to a member for a given role. It is only an internal data
929     * structure and should not be exposed via the API.
930     */
931    private static class MemberAccess {
932        private final Member member;
933        private final Access access;
934        // We use a weak hash map so that it naturally clears
935        // when more memory is required by other parts.
936        // This cache is useful for optimization, but cannot be
937        // let to grow indefinitely. This would cause problems
938        // on high cardinality dimensions.
939        private final Map<String, Boolean> parentsCache =
940            new WeakHashMap<String, Boolean>();
941        public MemberAccess(
942            Member member,
943            Access access)
944        {
945                this.member = member;
946                this.access = access;
947        }
948
949        /**
950         * Tells whether the member concerned by this grant object
951         * is a children of a given member. The result of the computation
952         * is cached for faster results, since this might get called
953         * very often.
954         */
955        private boolean isSubGrant(Member parentMember) {
956            if (parentsCache.containsKey(parentMember.getUniqueName())) {
957                return parentsCache.get(parentMember.getUniqueName());
958            }
959            for (Member m = member; m != null; m = m.getParentMember()) {
960                if (m.equals(parentMember)) {
961                    // We have proved that this granted member is a
962                    // descendant of 'member'. Cache it and return.
963                    parentsCache.put(
964                        parentMember.getUniqueName(), Boolean.TRUE);
965                    return true;
966                }
967            }
968            // Not a parent. Cache it and return.
969            if (MondrianProperties.instance()
970                .EnableRolapCubeMemberCache.get())
971            {
972                parentsCache.put(
973                    parentMember.getUniqueName(), Boolean.FALSE);
974            }
975            return false;
976        }
977
978        public String toString() {
979            return
980                "MemberAccess{"
981                + member.getUniqueName()
982                + " : "
983                + access.toString()
984                + "}";
985        }
986    }
987
988    /**
989     * Implementation of {@link mondrian.olap.Role.HierarchyAccess} that
990     * delegates all methods to an underlying hierarchy access.
991     */
992    public static abstract class DelegatingHierarchyAccess
993        implements HierarchyAccess
994    {
995        protected final HierarchyAccess hierarchyAccess;
996
997        /**
998         * Creates a DelegatingHierarchyAccess.
999         *
1000         * @param hierarchyAccess Underlying hierarchy access
1001         */
1002        public DelegatingHierarchyAccess(HierarchyAccess hierarchyAccess) {
1003            assert hierarchyAccess != null;
1004            this.hierarchyAccess = hierarchyAccess;
1005        }
1006
1007        public Access getAccess(Member member) {
1008            return hierarchyAccess.getAccess(member);
1009        }
1010
1011        public int getTopLevelDepth() {
1012            return hierarchyAccess.getTopLevelDepth();
1013        }
1014
1015        public int getBottomLevelDepth() {
1016            return hierarchyAccess.getBottomLevelDepth();
1017        }
1018
1019        public RollupPolicy getRollupPolicy() {
1020            return hierarchyAccess.getRollupPolicy();
1021        }
1022
1023        public boolean hasInaccessibleDescendants(Member member) {
1024            return hierarchyAccess.hasInaccessibleDescendants(member);
1025        }
1026    }
1027
1028    /**
1029     * Implementation of {@link mondrian.olap.Role.HierarchyAccess} that caches
1030     * the access of each member and level.
1031     *
1032     * <p>This reduces the number of calls to the underlying HierarchyAccess,
1033     * which is particularly useful for a union role which is based on many
1034     * roles.
1035     *
1036     * <p>Caching uses two {@link java.util.WeakHashMap} objects, so should
1037     * release resources if memory is scarce. However, it may use up memory and
1038     * cause segments etc. to be removed from the cache when GC is triggered.
1039     * For this reason, you should only use this wrapper for a HierarchyAccess
1040     * which would otherwise have poor performance; currently used for union
1041     * roles with 5 or more member roles.
1042     */
1043    static class CachingHierarchyAccess
1044        extends DelegatingHierarchyAccess
1045    {
1046        private final Map<Member, Access> memberAccessMap =
1047            new WeakHashMap<Member, Access>();
1048        private RollupPolicy rollupPolicy;
1049        private Map<Member, Boolean> inaccessibleDescendantsMap =
1050            new WeakHashMap<Member, Boolean>();
1051        private Integer topLevelDepth;
1052        private Integer bottomLevelDepth;
1053
1054        /**
1055         * Creates a CachingHierarchyAccess.
1056         *
1057         * @param hierarchyAccess Underlying hierarchy access
1058         */
1059        public CachingHierarchyAccess(HierarchyAccess hierarchyAccess) {
1060            super(hierarchyAccess);
1061        }
1062
1063        @Override
1064        public Access getAccess(Member member) {
1065            Access access = memberAccessMap.get(member);
1066            if (access != null) {
1067                return access;
1068            }
1069            access = hierarchyAccess.getAccess(member);
1070            memberAccessMap.put(member, access);
1071            return access;
1072        }
1073
1074        @Override
1075        public int getTopLevelDepth() {
1076            if (topLevelDepth == null) {
1077                topLevelDepth = hierarchyAccess.getTopLevelDepth();
1078            }
1079            return topLevelDepth;
1080        }
1081
1082        @Override
1083        public int getBottomLevelDepth() {
1084            if (bottomLevelDepth == null) {
1085                bottomLevelDepth = hierarchyAccess.getBottomLevelDepth();
1086            }
1087            return bottomLevelDepth;
1088        }
1089
1090        @Override
1091        public RollupPolicy getRollupPolicy() {
1092            if (rollupPolicy == null) {
1093                rollupPolicy = hierarchyAccess.getRollupPolicy();
1094            }
1095            return rollupPolicy;
1096        }
1097
1098        @Override
1099        public boolean hasInaccessibleDescendants(Member member) {
1100            Boolean b = inaccessibleDescendantsMap.get(member);
1101            if (b == null) {
1102                b = hierarchyAccess.hasInaccessibleDescendants(member);
1103                inaccessibleDescendantsMap.put(member, b);
1104            }
1105            return b;
1106        }
1107    }
1108}
1109
1110// End RoleImpl.java