/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2002-2005 Julian Hyde // Copyright (C) 2005-2017 Pentaho and others // All Rights Reserved. */ package mondrian.olap; import mondrian.rolap.RolapCube; import mondrian.rolap.RolapCubeDimension; import org.apache.log4j.Logger; import java.util.*; /** * Default implementation of the {@link Role} interface. * * @author jhyde, lucboudreau * @since Oct 5, 2002 */ public class RoleImpl implements Role { private boolean mutable = true; private final Map<Schema, Access> schemaGrants = new HashMap<Schema, Access>(); private final Map<Cube, Access> cubeGrants = new HashMap<Cube, Access>(); private final Map<Dimension, Access> dimensionGrants = new HashMap<Dimension, Access>(); private final Map<Hierarchy, HierarchyAccessImpl> hierarchyGrants = new HashMap<Hierarchy, HierarchyAccessImpl>(); private static final Logger LOGGER = Logger.getLogger(RoleImpl.class); private final List<Object[]> hashCache = new ArrayList<Object[]>(); private int hash = 0; /** * Creates a role with no permissions. */ public RoleImpl() { } public int hashCode() { // Although this code isn't entirely thread safe, it is good enough. // The implementations of Role are not expected to be thread safe, // but are only to be immutable once isMutable() returns true. // // Role objects are only often hashed for tuple list caches and only // once per mondrian schema per mondrian instance. If heavier usage // is added, this should probably be refactored into something more // thread safe with a ReentrantReadWriteLock. if (hash == 0) { int tmpHash = 7; for (Object obj : hashCache) { tmpHash = Util.hash(tmpHash, obj); } hash = tmpHash; } return hash; } public boolean equals(Object obj) { if (obj == null) { return false; } if (!(obj instanceof RoleImpl)) { return false; } final RoleImpl r = (RoleImpl) obj; return r.hashCache.equals(this.hashCache); } protected RoleImpl clone() { RoleImpl role = new RoleImpl(); role.mutable = mutable; role.schemaGrants.putAll(schemaGrants); role.cubeGrants.putAll(cubeGrants); role.dimensionGrants.putAll(dimensionGrants); role.hashCache.addAll(hashCache); for (Map.Entry<Hierarchy, HierarchyAccessImpl> entry : hierarchyGrants.entrySet()) { role.hierarchyGrants.put( entry.getKey(), (HierarchyAccessImpl) entry.getValue().clone()); } return role; } /** * Returns a copy of this <code>Role</code> which can be modified. */ public RoleImpl makeMutableClone() { RoleImpl role = clone(); role.mutable = true; return role; } /** * Prevents any further modifications. * @post !isMutable() */ public void makeImmutable() { mutable = false; } /** * Returns whether modifications are possible. */ public boolean isMutable() { return mutable; } /** * Defines access to all cubes and dimensions in a schema. * * @param schema Schema whose access to grant/deny. * @param access An {@link Access access code} * * @pre schema != null * @pre access == Access.ALL || access == Access.NONE * || access == Access.ALL_DIMENSIONS * @pre isMutable() */ public void grant(Schema schema, Access access) { assert schema != null; assert isMutable(); schemaGrants.put(schema, access); hashCache.add( new Object[] { schema.getId(), access.name()}); hash = 0; } public Access getAccess(Schema schema) { assert schema != null; final Access schemaAccess = schemaGrants.get(schema); if (schemaAccess == null) { // No specific rules means full access. return Access.CUSTOM; } else { return schemaAccess; } } /** * Converts a null Access object to {@link Access#NONE}. * * @param access Access object or null * @return Access object, never null */ private static Access toAccess(Access access) { return access == null ? Access.NONE : access; } /** * Defines access to a cube. * * @param cube Cube whose access to grant/deny. * @param access An {@link Access access code} * * @pre cube != null * @pre access == Access.ALL || access == Access.NONE * @pre isMutable() */ public void grant(Cube cube, Access access) { Util.assertPrecondition(cube != null, "cube != null"); assert access == Access.ALL || access == Access.NONE || access == Access.CUSTOM; Util.assertPrecondition(isMutable(), "isMutable()"); LOGGER.trace( "Grant " + access + " on cube " + cube.getName()); cubeGrants.put(cube, access); // Set the schema's access to 'custom' if no rules already exist. final Access schemaAccess = getAccess(cube.getSchema()); if (schemaAccess == Access.NONE) { LOGGER.trace( "Cascading grant " + Access.CUSTOM + " on schema " + cube.getSchema().getName()); grant(cube.getSchema(), Access.CUSTOM); } hashCache.add( new Object[] { cube.getClass().getName(), cube.getName(), access.name()}); hash = 0; } public Access getAccess(Cube cube) { assert cube != null; // Check for explicit rules. // Both 'custom' and 'all' are good enough Access access = cubeGrants.get(cube); if (access != null) { LOGGER.trace( "Access level " + access + " granted to cube " + cube.getName()); return access; } // Check for inheritance from the parent schema // 'All Dimensions' and 'custom' are not good enough access = schemaGrants.get(cube.getSchema()); if (access == Access.ALL) { LOGGER.trace( "Access level " + access + " granted to cube " + cube.getName() + " because of the grant to schema " + cube.getSchema().getName()); return Access.ALL; } // Deny access LOGGER.trace( "Access denided to cube" + cube.getName()); return Access.NONE; } /** * Defines access to a dimension. * * @param dimension Dimension whose access to grant/deny. * @param access An Access instance * * @pre dimension != null * @pre access == Access.ALL || access == Access.CUSTOM * || access == Access.NONE * @pre isMutable() */ public void grant(Dimension dimension, Access access) { assert dimension != null; assert access == Access.ALL || access == Access.NONE || access == Access.CUSTOM; Util.assertPrecondition(isMutable(), "isMutable()"); LOGGER.trace( "Grant " + access + " on dimension " + dimension.getUniqueName()); dimensionGrants.put(dimension, access); // Dimension grants do not cascade to the parent cube automatically. // We always figure out the inheritance at runtime since the place // where the dimension is used (either inside of a virtual cube, // a shared dimension or a cube) will influence on the decision. hashCache.add( new Object[] { dimension.getClass().getName(), dimension.getName(), access.name()}); hash = 0; } public Access getAccess(Dimension dimension) { assert dimension != null; // Check for explicit rules. Access access = getDimensionGrant(dimension); if (access == Access.CUSTOM) { // For legacy reasons, if there are no accessible hierarchies // and the dimension has an access level of custom, we deny. // TODO Remove for Mondrian 4.0 boolean canAccess = false; for (Hierarchy hierarchy : dimension.getHierarchies()) { final HierarchyAccessImpl hierarchyAccess = hierarchyGrants.get(hierarchy); if (hierarchyAccess != null && hierarchyAccess.access != Access.NONE) { canAccess = true; } } if (canAccess) { LOGGER.trace( "Access level " + access + " granted to dimension " + dimension.getUniqueName() + " because of the grant to one of its hierarchy."); return Access.CUSTOM; } else { LOGGER.trace( "Access denided to dimension " + dimension.getUniqueName() + " because there are no hierarchies accessible."); return Access.NONE; } } else if (access != null) { LOGGER.trace( "Access level " + access + " granted to dimension " + dimension.getUniqueName() + " because of explicit access rights."); return access; } // Check if this dimension inherits the cube's access rights. // 'Custom' level is not good enough for inherited access. access = checkDimensionAccessByCubeInheritance(dimension); if (access != Access.NONE) { LOGGER.trace( "Access level " + access + " granted to dimension " + dimension.getUniqueName() + " because of the grant to its parent cube."); return access; } // Check access at the schema level. // Levels of 'custom' and 'none' are not good enough. switch (getAccess(dimension.getSchema())) { case ALL: LOGGER.trace( "Access level ALL " + " granted to dimension " + dimension.getUniqueName() + " because of the grant to schema " + dimension.getSchema().getName()); return Access.ALL; case ALL_DIMENSIONS: // For all_dimensions to work, the cube access must be // at least 'custom' level if (access != Access.NONE) { return Access.ALL; } else { return Access.NONE; } default: LOGGER.trace( "Access denided to dimension " + dimension.getUniqueName() + " because of the access level of schema " + dimension.getSchema().getName()); return Access.NONE; } } private Access getDimensionGrant(final Dimension dimension) { if (dimension.isMeasures()) { for (Dimension key : dimensionGrants.keySet()) { if (key == dimension) { return dimensionGrants.get(key); } } return null; } else { return dimensionGrants.get(dimension); } } /** * This method is used to check if the access rights over a dimension * that might be inherited from the parent cube. * <p>It only checks for inherited access, and it presumes that there * are no dimension grants currently given to the dimension passed as an * argument. */ private Access checkDimensionAccessByCubeInheritance(Dimension dimension) { assert dimensionGrants.containsKey(dimension) == false || dimension.isMeasures(); for (Map.Entry<Cube, Access> cubeGrant : cubeGrants.entrySet()) { final Access access = toAccess(cubeGrant.getValue()); // The 'none' and 'custom' access level are not good enough if (access == Access.NONE || access == Access.CUSTOM) { continue; } final Dimension[] dimensions = cubeGrant.getKey().getDimensions(); for (Dimension dimension1 : dimensions) { // If the dimensions have the same identity, // we found an access rule. if (dimension == dimension1) { return cubeGrant.getValue(); } // If the passed dimension argument is of class // RolapCubeDimension, we must validate the cube // assignment and make sure the cubes are the same. // If not, skip to the next grant. if (dimension instanceof RolapCubeDimension && dimension.equals(dimension1) && !((RolapCubeDimension)dimension1) .getCube() .equals(cubeGrant.getKey())) { continue; } // Last thing is to allow for equality correspondences // to work with virtual cubes. if (cubeGrant.getKey() instanceof RolapCube && ((RolapCube)cubeGrant.getKey()).isVirtual() && dimension.equals(dimension1)) { return cubeGrant.getValue(); } } } return Access.NONE; } /** * Defines access to a hierarchy. * * @param hierarchy Hierarchy whose access to grant/deny. * @param access An {@link Access access code} * @param topLevel Top-most level which can be accessed, or null if the * highest level. May only be specified if <code>access</code> is * {@link mondrian.olap.Access#CUSTOM}. * @param bottomLevel Bottom-most level which can be accessed, or null if * the lowest level. May only be specified if <code>access</code> is * {@link mondrian.olap.Access#CUSTOM}. * * @param rollupPolicy Rollup policy * * @pre hierarchy != null * @pre Access.instance().isValid(access) * @pre (access == Access.CUSTOM) * || (topLevel == null && bottomLevel == null) * @pre topLevel == null || topLevel.getHierarchy() == hierarchy * @pre bottomLevel == null || bottomLevel.getHierarchy() == hierarchy * @pre isMutable() */ public void grant( Hierarchy hierarchy, Access access, Level topLevel, Level bottomLevel, RollupPolicy rollupPolicy) { assert hierarchy != null; assert access != null; assert (access == Access.CUSTOM) || (topLevel == null && bottomLevel == null); assert topLevel == null || topLevel.getHierarchy() == hierarchy; assert bottomLevel == null || bottomLevel.getHierarchy() == hierarchy; assert isMutable(); assert rollupPolicy != null; LOGGER.trace( "Granting access " + access + " on hierarchy " + hierarchy.getUniqueName()); hierarchyGrants.put( hierarchy, new HierarchyAccessImpl( this, hierarchy, access, topLevel, bottomLevel, rollupPolicy)); // Cascade the access right to 'custom' on the parent dim if necessary final Access dimAccess = toAccess(dimensionGrants.get(hierarchy.getDimension())); if (dimAccess == Access.NONE) { LOGGER.trace( "Cascading grant CUSTOM on dimension " + hierarchy.getDimension().getUniqueName() + " because of the grant to hierarchy" + hierarchy.getUniqueName()); grant(hierarchy.getDimension(), Access.CUSTOM); } hashCache.add( new Object[] { hierarchy.getClass().getName(), hierarchy.getName(), access.name()}); hash = 0; } public Access getAccess(Hierarchy hierarchy) { assert hierarchy != null; HierarchyAccessImpl hierarchyAccess = hierarchyGrants.get(hierarchy); if (hierarchyAccess != null) { LOGGER.trace( "Access level " + hierarchyAccess.access + " granted to dimension " + hierarchy.getUniqueName()); return hierarchyAccess.access; } // There was no explicit rule for this particular hierarchy. // Let's check the parent dimension. Access access = getAccess(hierarchy.getDimension()); if (access == Access.ALL) { // Access levels of 'none' and 'custom' are not enough. LOGGER.trace( "Access level ALL " + " granted to hierarchy " + hierarchy.getUniqueName() + " because of the grant to dimension " + hierarchy.getDimension().getUniqueName()); return Access.ALL; } // Access denied, since we know that the dimension check has // checked for its parents as well. LOGGER.trace( "Access denided to hierarchy " + hierarchy.getUniqueName()); return Access.NONE; } public HierarchyAccess getAccessDetails(Hierarchy hierarchy) { Util.assertPrecondition(hierarchy != null, "hierarchy != null"); if (hierarchyGrants.containsKey(hierarchy)) { return hierarchyGrants.get(hierarchy); } final Access hierarchyAccess; final Access schemaGrant = schemaGrants.get(hierarchy.getDimension().getSchema()); if (schemaGrant != null) { if (schemaGrant == Access.ALL) { hierarchyAccess = Access.ALL; } else { hierarchyAccess = Access.NONE; } } else { hierarchyAccess = Access.ALL; } return new HierarchyAccessImpl( this, hierarchy, hierarchyAccess, null, null, RollupPolicy.HIDDEN); } public Access getAccess(Level level) { assert level != null; HierarchyAccessImpl hierarchyAccess = hierarchyGrants.get(level.getHierarchy()); if (hierarchyAccess != null && hierarchyAccess.access != Access.NONE) { if (checkLevelIsOkWithRestrictions( hierarchyAccess, level)) { // We're good. Let it through. LOGGER.trace( "Access level " + hierarchyAccess.access + " granted to level " + level.getUniqueName() + " because of the grant to hierarchy " + level.getHierarchy().getUniqueName()); return hierarchyAccess.access; } } // No information could be deducted from the parent hierarchy. // Let's use the parent dimension. Access access = getAccess(level.getDimension()); LOGGER.trace( "Access level " + access + " granted to level " + level.getUniqueName() + " because of the grant to dimension " + level.getDimension().getUniqueName()); return access; } private static boolean checkLevelIsOkWithRestrictions( HierarchyAccessImpl hierarchyAccess, Level level) { // Check if this level is explicitly excluded by top/bototm // level restrictions. if (level.getDepth() < hierarchyAccess.topLevel.getDepth()) { return false; } if (level.getDepth() > hierarchyAccess.bottomLevel.getDepth()) { return false; } return true; } /** * Defines access to a member in a hierarchy. * * <p>Notes:<ol> * <li>The order of grants matters. If you grant/deny access to a * member, previous grants/denials to its descendants are ignored.</li> * <li>Member grants do not supersde top/bottom levels set using * {@link #grant(Hierarchy, Access, Level, Level, mondrian.olap.Role.RollupPolicy)}. * <li>If you have access to a member, then you can see its ancestors * <em>even those explicitly denied</em>, up to the top level. * </ol> * * @pre member != null * @pre isMutable() * @pre getAccess(member.getHierarchy()) == Access.CUSTOM */ public void grant(Member member, Access access) { Util.assertPrecondition(member != null, "member != null"); assert isMutable(); assert getAccess(member.getHierarchy()) == Access.CUSTOM; HierarchyAccessImpl hierarchyAccess = hierarchyGrants.get(member.getHierarchy()); assert hierarchyAccess != null; assert hierarchyAccess.access == Access.CUSTOM; hierarchyAccess.grant(this, member, access); hashCache.add( new Object[] { member.getClass().getName(), member.getName(), access.name()}); hash = 0; } public Access getAccess(Member member) { assert member != null; // Always allow access to calculated members. if (member.isCalculatedInQuery()) { return Access.ALL; } // Check if the parent hierarchy has any access // rules for this. final HierarchyAccessImpl hierarchyAccess = hierarchyGrants.get(member.getHierarchy()); if (hierarchyAccess != null) { return hierarchyAccess.getAccess(member); } // Then let's check ask the parent level. Access access = getAccess(member.getLevel()); LOGGER.trace( "Access level " + access + " granted to level " + member.getUniqueName() + " because of the grant to level " + member.getLevel().getUniqueName()); return access; } public Access getAccess(NamedSet set) { Util.assertPrecondition(set != null, "set != null"); // TODO Named sets cannot be secured at the moment. LOGGER.trace( "Access level ALL " + " granted to NamedSet " + set.getUniqueName() + " because I said so."); return Access.ALL; } public boolean canAccess(OlapElement olapElement) { Util.assertPrecondition(olapElement != null, "olapElement != null"); if (olapElement instanceof Member) { return getAccess((Member) olapElement) != Access.NONE; } else if (olapElement instanceof Level) { return getAccess((Level) olapElement) != Access.NONE; } else if (olapElement instanceof NamedSet) { return getAccess((NamedSet) olapElement) != Access.NONE; } else if (olapElement instanceof Hierarchy) { return getAccess((Hierarchy) olapElement) != Access.NONE; } else if (olapElement instanceof Cube) { return getAccess((Cube) olapElement) != Access.NONE; } else if (olapElement instanceof Dimension) { return getAccess((Dimension) olapElement) != Access.NONE; } else { return false; } } /** * Creates an element which represents all access to a hierarchy. * * @param hierarchy Hierarchy * @return element representing all access to a given hierarchy */ public static HierarchyAccess createAllAccess(Hierarchy hierarchy) { return new HierarchyAccessImpl( Util.createRootRole(hierarchy.getDimension().getSchema()), hierarchy, Access.ALL, null, null, Role.RollupPolicy.FULL); } /** * Returns a role that is the union of the given roles. * * @param roleList List of roles * @return Union role */ public static Role union(final List<Role> roleList) { assert roleList.size() > 0; return new UnionRoleImpl(roleList); } // ~ Inner classes -------------------------------------------------------- /** * Represents the access that a role has to a particular hierarchy. */ private static class HierarchyAccessImpl implements Role.AllHierarchyAccess { private final Hierarchy hierarchy; private final Level topLevel; private final Access access; private final Level bottomLevel; private final Map<String, MemberAccess> memberGrants = new HashMap<String, MemberAccess>(); private final RollupPolicy rollupPolicy; private final Role role; /** * Creates a <code>HierarchyAccessImpl</code>. * @param role A role this access belongs to. * @param hierarchy A hierarchy this object describes. * @param access The access granted to this role for this hierarchy. * @param topLevel The top level to restrict the role to, or null to * grant access up top the top level of the hierarchy parameter. * @param bottomLevel The bottom level to restrict the role to, or null * to grant access down to the bottom level of the hierarchy parameter. * @param rollupPolicy The rollup policy to apply. */ HierarchyAccessImpl( Role role, Hierarchy hierarchy, Access access, Level topLevel, Level bottomLevel, RollupPolicy rollupPolicy) { assert role != null; assert hierarchy != null; assert access != null; assert rollupPolicy != null; this.role = role; this.hierarchy = hierarchy; this.access = access; this.rollupPolicy = rollupPolicy; this.topLevel = topLevel == null ? hierarchy.getLevels()[0] : topLevel; this.bottomLevel = bottomLevel == null ? hierarchy.getLevels()[hierarchy.getLevels().length - 1] : bottomLevel; } public HierarchyAccess clone() { HierarchyAccessImpl hierarchyAccess = new HierarchyAccessImpl( role, hierarchy, access, topLevel, bottomLevel, rollupPolicy); hierarchyAccess.memberGrants.putAll(memberGrants); return hierarchyAccess; } /** * Grants access to a member. * * @param member Member * @param access Access */ void grant(RoleImpl role, Member member, Access access) { Util.assertTrue(member.getHierarchy() == hierarchy); // Remove any existing grants to descendants of "member" for (Iterator<MemberAccess> memberIter = memberGrants.values().iterator(); memberIter.hasNext();) { MemberAccess mAccess = memberIter.next(); if (mAccess.member.isChildOrEqualTo(member)) { LOGGER.trace( "Member grant " + mAccess + " removed because a grant on " + member.getUniqueName() + " overrides it."); memberIter.remove(); } } LOGGER.trace( "Granting access " + access + " on member " + member.getUniqueName()); memberGrants.put( member.getUniqueName(), new MemberAccess(member, access)); if (access == Access.NONE) { // Since we're denying access, the ancestor's // access level goes from NONE to CUSTOM // and from ALL to RESTRICTED. for (Member m = member.getParentMember(); m != null; m = m.getParentMember()) { MemberAccess mAccess = memberGrants.get(m.getUniqueName()); final Access parentAccess = mAccess == null ? null : mAccess.access; // If no current access is allowed, upgrade to "custom" // which means nothing unless explicitly allowed. if (parentAccess == Access.NONE && checkLevelIsOkWithRestrictions( this, m.getLevel())) { LOGGER.trace( "Cascading grant CUSTOM on member " + m.getUniqueName() + " because of the grant to member " + member.getUniqueName()); memberGrants.put( m.getUniqueName(), new MemberAccess(m, Access.CUSTOM)); } // If the current parent's access is not defined or // 'all', we switch it to RESTRICTED, meaning // that the user has access to everything unless // explicitly denied. if ((parentAccess == null || parentAccess == Access.ALL) && checkLevelIsOkWithRestrictions( this, m.getLevel())) { LOGGER.trace( "Cascading grant RESTRICTED on member " + m.getUniqueName() + " because of the grant to member " + member.getUniqueName()); memberGrants.put( m.getUniqueName(), new MemberAccess(m, Access.RESTRICTED)); } } } else { // Create 'custom' access for any ancestors of 'member' which // do not have explicit access but which have at least one // child visible. for (Member m = member.getParentMember(); m != null; m = m.getParentMember()) { if (checkLevelIsOkWithRestrictions( this, m.getLevel())) { MemberAccess mAccess = memberGrants.get(m.getUniqueName()); final Access parentAccess = toAccess(mAccess == null ? null : mAccess.access); if (parentAccess == Access.NONE) { LOGGER.trace( "Cascading grant CUSTOM on member " + m.getUniqueName() + " because of the grant to member " + member.getUniqueName()); memberGrants.put( m.getUniqueName(), new MemberAccess(m, Access.CUSTOM)); } } } // Also set custom access for the parent hierarchy. final Access hierarchyAccess = role.getAccess(member.getLevel().getHierarchy()); if (hierarchyAccess == Access.NONE) { LOGGER.trace( "Cascading grant CUSTOM on hierarchy " + hierarchy.getUniqueName() + " because of the grant to member " + member.getUniqueName()); // Upgrade to CUSTOM level. role.grant( hierarchy, Access.CUSTOM, topLevel, bottomLevel, rollupPolicy); } } } public Access getAccess() { return access; } public Access getAccess(Member member) { if (this.access != Access.CUSTOM) { return this.access; } MemberAccess mAccess = memberGrants.get(member.getUniqueName()); Access access = mAccess == null ? null : mAccess.access; // Check for an explicit deny. if (access == Access.NONE) { LOGGER.trace( "Access level " + Access.NONE + " granted to member " + member.getUniqueName() + " because it is explicitly denided."); return Access.NONE; } // Check for explicit grant if (access == Access.ALL || access == Access.CUSTOM) { LOGGER.trace( "Access level " + access + " granted to member " + member.getUniqueName()); return access; } // Restricted is ok. This means an explicit grant // followed by a deny of one of the children: so custom. if (access == Access.RESTRICTED) { LOGGER.trace( "Access level " + Access.CUSTOM + " granted to member " + member.getUniqueName() + " because it was RESTRICTED. "); return Access.CUSTOM; } // Check if the member is out of the bounds // defined by topLevel and bottomLevel if (!checkLevelIsOkWithRestrictions(this, member.getLevel())) { LOGGER.trace( "Access denided to member " + member.getUniqueName() + " because its level " + member.getLevel().getUniqueName() + " is out of the permitted bounds of between " + this.topLevel.getUniqueName() + " and " + this.bottomLevel.getUniqueName()); return Access.NONE; } // Nothing was explicitly defined for this member. // Check for grants on its parents for (Member m = member.getParentMember(); m != null; m = m.getParentMember()) { MemberAccess pAccess = memberGrants.get(m.getUniqueName()); final Access parentAccess = pAccess == null ? null : pAccess.access; if (parentAccess == null) { // No explicit rules for this parent continue; } // Check for parent deny if (parentAccess == Access.NONE || parentAccess == Access.CUSTOM) { LOGGER.trace( "Access denided to member " + member.getUniqueName() + " because its parent " + m.getUniqueName() + " is of access level " + parentAccess); return Access.NONE; } // Both RESTRICTED and ALL are OK for parents. LOGGER.trace( "Access level ALL granted to member " + member.getUniqueName() + " because its parent " + m.getUniqueName() + " is of access level " + parentAccess); return Access.ALL; } // Check for inherited access from ancestors. // "Custom" is not good enough. We are looking for "all" access. access = role.getAccess(member.getLevel()); if (access == Access.ALL) { LOGGER.trace( "Access ALL granted to member " + member.getUniqueName() + " because its level " + member.getLevel().getUniqueName() + " is of access level ALL"); return Access.ALL; } // This member might be at a level allowed by the // topLevel/bottomLevel attributes. If there are no explicit // member grants defined at this level but the member fits // those bounds, we give access. if (memberGrants.size() == 0) { LOGGER.trace( "Access level ALL granted to member " + member.getUniqueName() + " because it lies between the permitted level bounds and there are no explicit member grants defined in hierarchy " + member.getHierarchy().getUniqueName()); return Access.ALL; } // No access LOGGER.trace( "Access denided to member " + member.getUniqueName() + " because none of its parents allow access to it."); return Access.NONE; } public final int getTopLevelDepth() { return topLevel.getDepth(); } public final int getBottomLevelDepth() { return bottomLevel.getDepth(); } public RollupPolicy getRollupPolicy() { return rollupPolicy; } /** * Tells whether a given member has some of its children being * restricted by the access controls of this role instance. */ public boolean hasInaccessibleDescendants(Member member) { for (MemberAccess access : memberGrants.values()) { switch (access.access) { case NONE: case CUSTOM: if (access.isSubGrant(member)) { // At least one of the limited member is // part of the descendants of this member. return true; } } } // All descendants are accessible. return false; } } /** * A MemberAccess contains information about a grant applied * to a member for a given role. It is only an internal data * structure and should not be exposed via the API. */ private static class MemberAccess { private final Member member; private final Access access; // We use a weak hash map so that it naturally clears // when more memory is required by other parts. // This cache is useful for optimization, but cannot be // let to grow indefinitely. This would cause problems // on high cardinality dimensions. private final Map<String, Boolean> parentsCache = new WeakHashMap<String, Boolean>(); public MemberAccess( Member member, Access access) { this.member = member; this.access = access; } /** * Tells whether the member concerned by this grant object * is a children of a given member. The result of the computation * is cached for faster results, since this might get called * very often. */ private boolean isSubGrant(Member parentMember) { if (parentsCache.containsKey(parentMember.getUniqueName())) { return parentsCache.get(parentMember.getUniqueName()); } for (Member m = member; m != null; m = m.getParentMember()) { if (m.equals(parentMember)) { // We have proved that this granted member is a // descendant of 'member'. Cache it and return. parentsCache.put( parentMember.getUniqueName(), Boolean.TRUE); return true; } } // Not a parent. Cache it and return. if (MondrianProperties.instance() .EnableRolapCubeMemberCache.get()) { parentsCache.put( parentMember.getUniqueName(), Boolean.FALSE); } return false; } public String toString() { return "MemberAccess{" + member.getUniqueName() + " : " + access.toString() + "}"; } } /** * Implementation of {@link mondrian.olap.Role.HierarchyAccess} that * delegates all methods to an underlying hierarchy access. */ public static abstract class DelegatingHierarchyAccess implements AllHierarchyAccess { protected final HierarchyAccess hierarchyAccess; /** * Creates a DelegatingHierarchyAccess. * * @param hierarchyAccess Underlying hierarchy access */ public DelegatingHierarchyAccess(HierarchyAccess hierarchyAccess) { assert hierarchyAccess != null; this.hierarchyAccess = hierarchyAccess; } public Access getAccess(Member member) { return hierarchyAccess.getAccess(member); } public int getTopLevelDepth() { return hierarchyAccess.getTopLevelDepth(); } public int getBottomLevelDepth() { return hierarchyAccess.getBottomLevelDepth(); } public RollupPolicy getRollupPolicy() { return hierarchyAccess.getRollupPolicy(); } public boolean hasInaccessibleDescendants(Member member) { return hierarchyAccess.hasInaccessibleDescendants(member); } public Access getAccess() { if (hierarchyAccess instanceof AllHierarchyAccess) { return ((AllHierarchyAccess) hierarchyAccess).getAccess(); } throw Util.newInternal( "Unsupported operation. Should implement AllHierarchyAccess."); } } /** * Implementation of {@link mondrian.olap.Role.HierarchyAccess} that caches * the access of each member and level. * * <p>This reduces the number of calls to the underlying HierarchyAccess, * which is particularly useful for a union role which is based on many * roles. * * <p>Caching uses two {@link java.util.WeakHashMap} objects, so should * release resources if memory is scarce. However, it may use up memory and * cause segments etc. to be removed from the cache when GC is triggered. * For this reason, you should only use this wrapper for a HierarchyAccess * which would otherwise have poor performance; currently used for union * roles with 5 or more member roles. */ static class CachingHierarchyAccess extends DelegatingHierarchyAccess { private final Map<Member, Access> memberAccessMap = new WeakHashMap<Member, Access>(); private RollupPolicy rollupPolicy; private Map<Member, Boolean> inaccessibleDescendantsMap = new WeakHashMap<Member, Boolean>(); private Integer topLevelDepth; private Integer bottomLevelDepth; /** * Creates a CachingHierarchyAccess. * * @param hierarchyAccess Underlying hierarchy access */ public CachingHierarchyAccess(HierarchyAccess hierarchyAccess) { super(hierarchyAccess); } @Override public Access getAccess(Member member) { Access access = memberAccessMap.get(member); if (access != null) { return access; } access = hierarchyAccess.getAccess(member); memberAccessMap.put(member, access); return access; } @Override public int getTopLevelDepth() { if (topLevelDepth == null) { topLevelDepth = hierarchyAccess.getTopLevelDepth(); } return topLevelDepth; } @Override public int getBottomLevelDepth() { if (bottomLevelDepth == null) { bottomLevelDepth = hierarchyAccess.getBottomLevelDepth(); } return bottomLevelDepth; } @Override public RollupPolicy getRollupPolicy() { if (rollupPolicy == null) { rollupPolicy = hierarchyAccess.getRollupPolicy(); } return rollupPolicy; } @Override public boolean hasInaccessibleDescendants(Member member) { Boolean b = inaccessibleDescendantsMap.get(member); if (b == null) { b = hierarchyAccess.hasInaccessibleDescendants(member); inaccessibleDescendantsMap.put(member, b); } return b; } } } // End RoleImpl.java