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 && 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