001/* 002// This software is subject to the terms of the Eclipse Public License v1.0 003// Agreement, available at the following URL: 004// http://www.eclipse.org/legal/epl-v10.html. 005// You must accept the terms of that agreement to use this software. 006// 007// Copyright (C) 2003-2005 Julian Hyde 008// Copyright (C) 2005-2012 Pentaho 009// All Rights Reserved. 010*/ 011package mondrian.rolap; 012 013import mondrian.olap.*; 014import mondrian.resource.MondrianResource; 015import mondrian.rolap.sql.MemberChildrenConstraint; 016import mondrian.rolap.sql.TupleConstraint; 017 018import java.util.*; 019 020/** 021 * A <code>RestrictedMemberReader</code> reads only the members of a hierarchy 022 * allowed by a role's access profile. 023 * 024 * @author jhyde 025 * @since Feb 26, 2003 026 */ 027class RestrictedMemberReader extends DelegatingMemberReader { 028 029 private final Role.HierarchyAccess hierarchyAccess; 030 private final boolean ragged; 031 private final SqlConstraintFactory sqlConstraintFactory = 032 SqlConstraintFactory.instance(); 033 final Role role; 034 035 /** 036 * Creates a <code>RestrictedMemberReader</code>. 037 * 038 * <p>There's no filtering to be done unless 039 * either the role has restrictions on this hierarchy, 040 * or the hierarchy is ragged; there's a pre-condition to this effect.</p> 041 * 042 * @param memberReader Underlying (presumably unrestricted) member reader 043 * @param role Role whose access profile to obey. The role must have 044 * restrictions on this hierarchy 045 * @pre role.getAccessDetails(memberReader.getHierarchy()) != null || 046 * memberReader.getHierarchy().isRagged() 047 */ 048 RestrictedMemberReader(MemberReader memberReader, Role role) { 049 super(memberReader); 050 this.role = role; 051 RolapHierarchy hierarchy = memberReader.getHierarchy(); 052 ragged = hierarchy.isRagged(); 053 if (role.getAccessDetails(hierarchy) == null) { 054 assert ragged; 055 hierarchyAccess = RoleImpl.createAllAccess(hierarchy); 056 } else { 057 hierarchyAccess = role.getAccessDetails(hierarchy); 058 } 059 } 060 061 public boolean setCache(MemberCache cache) { 062 // Don't support cache-writeback. It would confuse the cache! 063 return false; 064 } 065 066 public RolapMember getLeadMember(RolapMember member, int n) { 067 int i = 0; 068 int increment = 1; 069 if (n < 0) { 070 increment = -1; 071 n = -n; 072 } 073 while (i < n) { 074 member = memberReader.getLeadMember(member, increment); 075 if (member.isNull()) { 076 return member; 077 } 078 if (canSee(member)) { 079 ++i; 080 } 081 } 082 return member; 083 } 084 085 public void getMemberChildren( 086 RolapMember parentMember, 087 List<RolapMember> children) 088 { 089 MemberChildrenConstraint constraint = 090 sqlConstraintFactory.getMemberChildrenConstraint(null); 091 getMemberChildren(parentMember, children, constraint); 092 } 093 094 public Map<? extends Member, Access> getMemberChildren( 095 RolapMember parentMember, 096 List<RolapMember> children, 097 MemberChildrenConstraint constraint) 098 { 099 List<RolapMember> fullChildren = new ArrayList<RolapMember>(); 100 memberReader.getMemberChildren(parentMember, fullChildren, constraint); 101 return processMemberChildren(fullChildren, children, constraint); 102 } 103 104 public void getMemberChildren( 105 List<RolapMember> parentMembers, 106 List<RolapMember> children) 107 { 108 MemberChildrenConstraint constraint = 109 sqlConstraintFactory.getMemberChildrenConstraint(null); 110 getMemberChildren(parentMembers, children, constraint); 111 } 112 113 public Map<? extends Member, Access> getMemberChildren( 114 List<RolapMember> parentMembers, 115 List<RolapMember> children, 116 MemberChildrenConstraint constraint) 117 { 118 List<RolapMember> fullChildren = new ArrayList<RolapMember>(); 119 memberReader.getMemberChildren(parentMembers, fullChildren, constraint); 120 return processMemberChildren(fullChildren, children, constraint); 121 } 122 123 private Map<RolapMember, Access> processMemberChildren( 124 List<RolapMember> fullChildren, 125 List<RolapMember> children, 126 MemberChildrenConstraint constraint) 127 { 128 // todo: optimize if parentMember is beyond last level 129 List<RolapMember> grandChildren = null; 130 Map<RolapMember, Access> memberToAccessMap = 131 new LinkedHashMap<RolapMember, Access>(); 132 for (int i = 0; i < fullChildren.size(); i++) { 133 RolapMember member = fullChildren.get(i); 134 // If a child is hidden (due to raggedness) include its children. 135 // This must be done before applying access-control. 136 if (ragged) { 137 if (member.isHidden()) { 138 // Replace this member with all of its children. 139 // They might be hidden too, but we'll get to them in due 140 // course. They also might be access-controlled; that's why 141 // we deal with raggedness before we apply access-control. 142 fullChildren.remove(i); 143 if (grandChildren == null) { 144 grandChildren = new ArrayList<RolapMember>(); 145 } else { 146 grandChildren.clear(); 147 } 148 memberReader.getMemberChildren( 149 member, grandChildren, constraint); 150 fullChildren.addAll(i, grandChildren); 151 // Step back to before the first child we just inserted, 152 // and go through the loop again. 153 --i; 154 continue; 155 } 156 } 157 // Filter out children which are invisible because of 158 // access-control. 159 final Access access; 160 if (hierarchyAccess != null) { 161 access = hierarchyAccess.getAccess(member); 162 } else { 163 access = Access.ALL; 164 } 165 switch (access) { 166 case NONE: 167 break; 168 default: 169 children.add(member); 170 memberToAccessMap.put(member, access); 171 break; 172 } 173 } 174 return memberToAccessMap; 175 } 176 177 /** 178 * Writes to members which we can see. 179 * @param members Input list 180 * @param filteredMembers Output list 181 */ 182 private void filterMembers( 183 List<RolapMember> members, 184 List<RolapMember> filteredMembers) 185 { 186 for (RolapMember member : members) { 187 if (canSee(member)) { 188 filteredMembers.add(member); 189 } 190 } 191 } 192 193 private boolean canSee(final RolapMember member) { 194 if (ragged && member.isHidden()) { 195 return false; 196 } 197 if (hierarchyAccess != null) { 198 final Access access = hierarchyAccess.getAccess(member); 199 return access != Access.NONE; 200 } 201 return true; 202 } 203 204 public List<RolapMember> getRootMembers() { 205 int topLevelDepth = hierarchyAccess.getTopLevelDepth(); 206 if (topLevelDepth > 0) { 207 RolapLevel topLevel = 208 (RolapLevel) getHierarchy().getLevels()[topLevelDepth]; 209 final List<RolapMember> memberList = 210 getMembersInLevel(topLevel); 211 if (memberList.isEmpty()) { 212 throw MondrianResource.instance() 213 .HierarchyHasNoAccessibleMembers.ex( 214 getHierarchy().getUniqueName()); 215 } 216 return memberList; 217 } 218 return super.getRootMembers(); 219 } 220 221 public List<RolapMember> getMembersInLevel( 222 RolapLevel level) 223 { 224 TupleConstraint constraint = 225 sqlConstraintFactory.getLevelMembersConstraint(null); 226 return getMembersInLevel(level, constraint); 227 } 228 229 public List<RolapMember> getMembersInLevel( 230 RolapLevel level, TupleConstraint constraint) 231 { 232 if (hierarchyAccess != null) { 233 final int depth = level.getDepth(); 234 if (depth < hierarchyAccess.getTopLevelDepth()) { 235 return Collections.emptyList(); 236 } 237 if (depth > hierarchyAccess.getBottomLevelDepth()) { 238 return Collections.emptyList(); 239 } 240 } 241 final List<RolapMember> membersInLevel = 242 memberReader.getMembersInLevel( 243 level, constraint); 244 List<RolapMember> filteredMembers = new ArrayList<RolapMember>(); 245 filterMembers(membersInLevel, filteredMembers); 246 return filteredMembers; 247 } 248 249 public RolapMember getDefaultMember() { 250 RolapMember defaultMember = 251 (RolapMember) getHierarchy().getDefaultMember(); 252 if (defaultMember != null) { 253 Access i = hierarchyAccess.getAccess(defaultMember); 254 if (i != Access.NONE) { 255 return defaultMember; 256 } 257 } 258 final List<RolapMember> rootMembers = getRootMembers(); 259 if (rootMembers.size() == 1) { 260 return rootMembers.get(0); 261 } else { 262 return new MultiCardinalityDefaultMember(rootMembers.get(0)); 263 } 264 } 265 266 /** 267 * This is a special subclass of {@link DelegatingRolapMember}. 268 * It is needed because {@link Evaluator} doesn't support multi cardinality 269 * default members. RolapHierarchy.LimitedRollupSubstitutingMemberReader 270 * .substitute() looks for this class and substitutes the 271 * <p>FIXME: If/when we refactor evaluator to support 272 * multi cardinality default members, we can remove this. 273 */ 274 static class MultiCardinalityDefaultMember extends DelegatingRolapMember { 275 protected MultiCardinalityDefaultMember(RolapMember member) { 276 super(member); 277 } 278 } 279 280 public RolapMember getMemberParent(RolapMember member) { 281 RolapMember parentMember = member.getParentMember(); 282 // Skip over hidden parents. 283 while (parentMember != null && parentMember.isHidden()) { 284 parentMember = parentMember.getParentMember(); 285 } 286 // Skip over non-accessible parents. 287 if (parentMember != null) { 288 if (hierarchyAccess.getAccess(parentMember) == Access.NONE) { 289 return null; 290 } 291 } 292 return parentMember; 293 } 294} 295 296// End RestrictedMemberReader.java