1. Project Clover database Tue Dec 20 2016 21:24:09 CET
  2. Package com.xpn.xwiki.internal.plugin.rightsmanager

File UserIterator.java

 

Coverage histogram

../../../../../../img/srcFileCovDistChart10.png
0% of files have more coverage

Code metrics

48
98
19
1
381
243
49
0.5
5.16
19
2.58

Classes

Class Line # Actions
UserIterator 56 98 0% 49 13
0.9212121492.1%
 

Contributing tests

This file is covered by 25 tests. .

Source view

1    /*
2    * See the NOTICE file distributed with this work for additional
3    * information regarding copyright ownership.
4    *
5    * This is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU Lesser General Public License as
7    * published by the Free Software Foundation; either version 2.1 of
8    * the License, or (at your option) any later version.
9    *
10    * This software is distributed in the hope that it will be useful,
11    * but WITHOUT ANY WARRANTY; without even the implied warranty of
12    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13    * Lesser General Public License for more details.
14    *
15    * You should have received a copy of the GNU Lesser General Public
16    * License along with this software; if not, write to the Free
17    * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18    * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
19    */
20    package com.xpn.xwiki.internal.plugin.rightsmanager;
21   
22    import java.util.ArrayDeque;
23    import java.util.ArrayList;
24    import java.util.Collection;
25    import java.util.Collections;
26    import java.util.Deque;
27    import java.util.Iterator;
28    import java.util.LinkedHashSet;
29    import java.util.List;
30    import java.util.NoSuchElementException;
31   
32    import org.apache.commons.lang3.StringUtils;
33    import org.xwiki.context.Execution;
34    import org.xwiki.context.ExecutionContext;
35    import org.xwiki.model.reference.DocumentReference;
36    import org.xwiki.model.reference.DocumentReferenceResolver;
37    import org.xwiki.model.reference.LocalDocumentReference;
38   
39    import com.xpn.xwiki.XWikiContext;
40    import com.xpn.xwiki.XWikiException;
41    import com.xpn.xwiki.doc.XWikiDocument;
42    import com.xpn.xwiki.objects.BaseObject;
43    import com.xpn.xwiki.plugin.rightsmanager.RightsManager;
44    import com.xpn.xwiki.user.api.XWikiRightService;
45   
46    /**
47    * Iterates over all the users found in the passed references (which can be references to groups or to users) and
48    * return extracted data from the User for each found user (the extracted data is controlled by the passed
49    * {@link UserDataExtractor}. Handles nested groups.
50    *
51    * @param <T> the type of data returned by the iterator when {@link #next()} is called
52    * @version $Id: 9fec00ad091e5dc7f05311b9a985385374a44f82 $
53    * @since 6.4.2
54    * @since 7.0M2
55    */
 
56    public class UserIterator<T> implements Iterator<T>
57    {
58    /**
59    * The local reference of the class containing group member.
60    */
61    static final LocalDocumentReference GROUP_CLASS_REF =
62    new LocalDocumentReference(RightsManager.DEFAULT_USERORGROUP_SPACE, "XWikiGroups");
63   
64    /**
65    * The local reference of the class containing user profile.
66    */
67    static final LocalDocumentReference USER_CLASS_REF =
68    new LocalDocumentReference(RightsManager.DEFAULT_USERORGROUP_SPACE, "XWikiUsers");
69   
70   
71    private DocumentReferenceResolver<String> explicitDocumentReferenceResolver;
72   
73    private final Execution execution;
74   
75    private final XWikiContext xwikiContext;
76   
77    private List<DocumentReference> userAndGroupReferences;
78   
79    private List<DocumentReference> excludedUserAndGroupReferences;
80   
81    private List<DocumentReference> processedGroups = new ArrayList<>();
82   
83    private Deque<Iterator<DocumentReference>> userAndGroupIteratorStack = new ArrayDeque<>();
84   
85    private T lookaheadValue;
86   
87    private UserDataExtractor<T> userDataExtractor;
88   
89    /**
90    * Recommended if this iterator is called from code using components.
91    *
92    * @param userAndGroupReferences the list of references (users or groups) to iterate over
93    * @param excludedUserAndGroupReferences the list of references (users or groups) to exclude. Can be null.
94    * @param userDataExtractor the extractor to use the return the value extracted from the user profile
95    * @param explicitDocumentReferenceResolver the resolver to use for transforming group member strings into
96    * {@link DocumentReference}
97    * @param execution the component used to access the {@link XWikiContext} we use to call oldcore APIs
98    */
 
99  36 toggle public UserIterator(List<DocumentReference> userAndGroupReferences,
100    List<DocumentReference> excludedUserAndGroupReferences, UserDataExtractor<T> userDataExtractor,
101    DocumentReferenceResolver<String> explicitDocumentReferenceResolver, Execution execution)
102    {
103  36 initialize(userAndGroupReferences, excludedUserAndGroupReferences, userDataExtractor,
104    explicitDocumentReferenceResolver);
105  36 this.execution = execution;
106  36 this.xwikiContext = null;
107    }
108   
109    /**
110    * Recommended if this iterator is called from code using components.
111    *
112    * @param userOrGroupReference the reference (user or group) to iterate over
113    * @param userDataExtractor the extractor to use the return the value extracted from the user profile
114    * @param explicitDocumentReferenceResolver the resolver to use for transforming group member strings into
115    * {@link DocumentReference}
116    * @param execution the component used to access the {@link XWikiContext} we use to call oldcore APIs
117    */
 
118  19 toggle public UserIterator(DocumentReference userOrGroupReference, UserDataExtractor<T> userDataExtractor,
119    DocumentReferenceResolver<String> explicitDocumentReferenceResolver, Execution execution)
120    {
121  19 initialize(userOrGroupReference, userDataExtractor, explicitDocumentReferenceResolver);
122  19 this.execution = execution;
123  19 this.xwikiContext = null;
124    }
125   
126    /**
127    * Recommended if this iterator is called from old code not using components.
128    *
129    * @param userAndGroupReferences the list of references (users or groups) to iterate over
130    * @param excludedUserAndGroupReferences the list of references (users or groups) to exclude. Can be null.
131    * @param userDataExtractor the extractor to use the return the value extracted from the user profile
132    * @param explicitDocumentReferenceResolver the resolver to use for transforming group member strings into
133    * {@link DocumentReference}
134    * @param xwikiContext the {@link XWikiContext} we use to call oldcore APIs
135    */
 
136  0 toggle public UserIterator(List<DocumentReference> userAndGroupReferences,
137    List<DocumentReference> excludedUserAndGroupReferences, UserDataExtractor<T> userDataExtractor,
138    DocumentReferenceResolver<String> explicitDocumentReferenceResolver, XWikiContext xwikiContext)
139    {
140  0 initialize(userAndGroupReferences, excludedUserAndGroupReferences, userDataExtractor,
141    explicitDocumentReferenceResolver);
142  0 this.execution = null;
143  0 this.xwikiContext = xwikiContext;
144    }
145   
146    /**
147    * Recommended if this iterator is called from old code not using components.
148    *
149    * @param userOrGroupReference the reference (user or group) to iterate over
150    * @param userDataExtractor the extractor to use the return the value extracted from the user profile
151    * @param explicitDocumentReferenceResolver the resolver to use for transforming group member strings into
152    * {@link DocumentReference}
153    * @param xwikiContext the {@link XWikiContext} we use to call oldcore APIs
154    */
 
155  9 toggle public UserIterator(DocumentReference userOrGroupReference, UserDataExtractor<T> userDataExtractor,
156    DocumentReferenceResolver<String> explicitDocumentReferenceResolver, XWikiContext xwikiContext)
157    {
158  9 initialize(userOrGroupReference, userDataExtractor, explicitDocumentReferenceResolver);
159  9 this.execution = null;
160  9 this.xwikiContext = xwikiContext;
161    }
162   
 
163  28 toggle private void initialize(DocumentReference userOrGroupReference, UserDataExtractor<T> userDataExtractor,
164    DocumentReferenceResolver<String> explicitDocumentReferenceResolver)
165    {
166  28 List<DocumentReference> references = userOrGroupReference == null
167    ? Collections.<DocumentReference>emptyList() : Collections.singletonList(userOrGroupReference);
168  28 initialize(references, null, userDataExtractor, explicitDocumentReferenceResolver);
169    }
170   
 
171  64 toggle private void initialize(List<DocumentReference> userAndGroupReferences,
172    List<DocumentReference> excludedUserAndGroupReferences, UserDataExtractor<T> userDataExtractor,
173    DocumentReferenceResolver<String> explicitDocumentReferenceResolver)
174    {
175  64 this.userAndGroupReferences = userAndGroupReferences;
176  64 this.excludedUserAndGroupReferences = excludedUserAndGroupReferences;
177  64 if (excludedUserAndGroupReferences == null) {
178  59 this.excludedUserAndGroupReferences = Collections.emptyList();
179    }
180  64 this.explicitDocumentReferenceResolver = explicitDocumentReferenceResolver;
181  64 this.userDataExtractor = userDataExtractor;
182  64 if (userAndGroupReferences != null && !userAndGroupReferences.isEmpty()) {
183  63 this.userAndGroupIteratorStack.push(userAndGroupReferences.iterator());
184    }
185    }
186   
 
187  92 toggle @Override
188    public boolean hasNext()
189    {
190  92 if (this.lookaheadValue == null) {
191  92 this.lookaheadValue = getNext();
192    }
193  92 return this.lookaheadValue != null;
194    }
195   
 
196  39 toggle @Override
197    public T next()
198    {
199  39 T currentValue = this.lookaheadValue;
200  39 if (currentValue != null) {
201  33 this.lookaheadValue = null;
202    } else {
203  6 currentValue = getNext();
204  3 if (currentValue == null) {
205  1 throw new NoSuchElementException(String.format(
206    "No more users to extract from the passed references [%s]", serializeUserAndGroupReferences()));
207    }
208    }
209  35 return currentValue;
210    }
211   
 
212  4 toggle private String serializeUserAndGroupReferences()
213    {
214  4 StringBuilder buffer = new StringBuilder();
215  4 Iterator<DocumentReference> iterator = this.userAndGroupReferences.iterator();
216  8 while (iterator.hasNext()) {
217  4 DocumentReference reference = iterator.next();
218  4 buffer.append('[').append(reference).append(']');
219  4 if (iterator.hasNext()) {
220  0 buffer.append(',').append(' ');
221    }
222    }
223  4 return buffer.toString();
224    }
225   
 
226  0 toggle @Override
227    public void remove()
228    {
229  0 throw new UnsupportedOperationException("remove");
230    }
231   
 
232  98 toggle private T getNext()
233    {
234  98 T currentValue = null;
235   
236    // Unless there are no more references in the stack we have not tried everything, try getting a user
237    // from the first element at the top of the stack.
238  179 while (currentValue == null && !this.userAndGroupIteratorStack.isEmpty()) {
239  84 currentValue = getNextUser(this.userAndGroupIteratorStack.peek());
240    }
241   
242  95 return currentValue;
243    }
244   
245   
 
246  84 toggle private T getNextUser(Iterator<DocumentReference> currentIterator)
247    {
248  84 T currentValue = null;
249   
250  84 DocumentReference currentReference = currentIterator.next();
251   
252    // If the reference is not in the excluded list (else skip it!)
253  84 if (!this.excludedUserAndGroupReferences.contains(currentReference)) {
254    // If it's not a virtual user (guest or superadmin user), then load the document
255  82 if (isSuperAdmin(currentReference)) {
256  2 currentValue = this.userDataExtractor.extractFromSuperadmin(currentReference);
257  80 } else if (isGuest(currentReference)) {
258  2 currentValue = this.userDataExtractor.extractFromGuest(currentReference);
259    } else {
260  78 XWikiDocument document = getFailsafeDocument(currentReference);
261    // If we found the document, try to get a user from the document, and stack group members if any
262  75 if (document != null && !document.isNew()) {
263  73 currentValue = handleUserOrGroupReference(currentReference, document);
264    }
265    }
266    }
267   
268  81 cleanStackIfNeeded(currentIterator);
269  81 return currentValue;
270    }
271   
 
272  82 toggle private boolean isSuperAdmin(DocumentReference reference)
273    {
274  82 return reference.getLastSpaceReference().getName().equals(RightsManager.DEFAULT_USERORGROUP_SPACE)
275    && reference.getName().equalsIgnoreCase(XWikiRightService.SUPERADMIN_USER);
276    }
277   
 
278  80 toggle private boolean isGuest(DocumentReference reference)
279    {
280  80 return reference.getLastSpaceReference().getName().equals(RightsManager.DEFAULT_USERORGROUP_SPACE)
281    && reference.getName().equals(XWikiRightService.GUEST_USER);
282    }
283   
 
284  73 toggle private T handleUserOrGroupReference(DocumentReference currentReference, XWikiDocument document)
285    {
286  73 T value = null;
287   
288    // Is the reference pointing to a user?
289  73 BaseObject userObject = document.getXObject(USER_CLASS_REF);
290  73 boolean isUserReference = userObject != null;
291   
292    // Is the reference pointing to a group?
293    // Note that a reference can point to a user reference and to a group reference at the same time!
294  73 List<BaseObject> members = document.getXObjects(GROUP_CLASS_REF);
295  73 boolean isGroupReference = members != null && !members.isEmpty();
296   
297    // If we have a group reference then stack the group members
298  73 if (isGroupReference) {
299    // Ensure groups are visited only once to prevent potential infinite loops
300  11 if (!processedGroups.contains(currentReference)) {
301  10 processedGroups.add(currentReference);
302   
303    // Extract the references and push them on the stack as an iterator
304  10 Collection<DocumentReference> groupMemberReferences =
305    convertToDocumentReferences(members, currentReference);
306  10 if (!groupMemberReferences.isEmpty()) {
307  10 this.userAndGroupIteratorStack.push(groupMemberReferences.iterator());
308    }
309    }
310    }
311    // If we have a user reference then we'll just return it
312  73 if (isUserReference) {
313  59 value = this.userDataExtractor.extract(currentReference, document, userObject);
314    }
315   
316  73 return value;
317    }
318   
 
319  81 toggle private void cleanStackIfNeeded(Iterator<DocumentReference> currentIterator)
320    {
321    // If there is no more reference to handle in the current iterator, pop the stack!
322  150 while (!this.userAndGroupIteratorStack.isEmpty() && !this.userAndGroupIteratorStack.peek().hasNext()) {
323  69 this.userAndGroupIteratorStack.pop();
324    }
325    }
326   
 
327  78 toggle private XWikiDocument getFailsafeDocument(DocumentReference reference)
328    {
329  78 try {
330  78 return getXwikiContext().getWiki().getDocument(reference, getXwikiContext());
331    } catch (XWikiException e) {
332  1 throw new RuntimeException(String.format("Failed to get document for User or Group [%s] when extracting "
333    + "all users for the references [%s]", reference, serializeUserAndGroupReferences()), e);
334    }
335    }
336   
 
337  10 toggle private Collection<DocumentReference> convertToDocumentReferences(List<BaseObject> memberObjects,
338    DocumentReference currentReference)
339    {
340    // Handle duplicates by using a Set (last one wins).
341  10 Collection<DocumentReference> members = new LinkedHashSet<>();
342  10 for (BaseObject memberObject : memberObjects) {
343    // If the member object is null, discard this entry!
344  20 if (memberObject == null) {
345  0 continue;
346    }
347   
348  20 String member = memberObject.getStringValue("member");
349   
350    // If the member reference is empty, discard this entry!
351  20 if (StringUtils.isBlank(member)) {
352  1 continue;
353    }
354   
355  19 DocumentReference resolvedReference =
356    this.explicitDocumentReferenceResolver.resolve(member, currentReference);
357  19 if (!resolvedReference.equals(currentReference)) {
358  19 members.add(resolvedReference);
359    }
360    }
361  10 return members;
362    }
363   
 
364  154 toggle protected XWikiContext getXwikiContext()
365    {
366  154 if (execution == null) {
367  16 return this.xwikiContext;
368    }
369   
370  138 XWikiContext context = null;
371  138 ExecutionContext executionContext = execution.getContext();
372  138 if (executionContext != null) {
373  137 context = (XWikiContext) executionContext.getProperty(XWikiContext.EXECUTIONCONTEXT_KEY);
374    }
375  138 if (executionContext == null || context == null) {
376  2 throw new RuntimeException(String.format("Aborting member extraction from passed references [%s] since "
377    + "no XWiki Context was found", serializeUserAndGroupReferences()));
378    }
379  136 return context;
380    }
381    }