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