1. Project Clover database Tue Dec 20 2016 21:24:09 CET
  2. Package org.xwiki.security.authorization.cache.internal

File DefaultSecurityCache.java

 

Coverage histogram

../../../../../../img/srcFileCovDistChart9.png
38% of files have more coverage

Code metrics

130
257
51
2
967
602
129
0.5
5.04
25.5
2.53

Classes

Class Line # Actions
DefaultSecurityCache 63 148 0% 73 23
0.907630590.8%
DefaultSecurityCache.SecurityCacheEntry 127 109 0% 56 40
0.7883597678.8%
 

Contributing tests

This file is covered by 42 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 org.xwiki.security.authorization.cache.internal;
21   
22    import java.util.ArrayDeque;
23    import java.util.ArrayList;
24    import java.util.Arrays;
25    import java.util.Collection;
26    import java.util.Deque;
27    import java.util.HashSet;
28    import java.util.concurrent.locks.Lock;
29    import java.util.concurrent.locks.ReadWriteLock;
30    import java.util.concurrent.locks.ReentrantReadWriteLock;
31   
32    import javax.inject.Inject;
33    import javax.inject.Singleton;
34   
35    import org.slf4j.Logger;
36    import org.xwiki.cache.Cache;
37    import org.xwiki.cache.CacheManager;
38    import org.xwiki.cache.DisposableCacheValue;
39    import org.xwiki.cache.config.CacheConfiguration;
40    import org.xwiki.cache.eviction.LRUEvictionConfiguration;
41    import org.xwiki.component.annotation.Component;
42    import org.xwiki.component.phase.Initializable;
43    import org.xwiki.component.phase.InitializationException;
44    import org.xwiki.model.reference.EntityReferenceSerializer;
45    import org.xwiki.security.GroupSecurityReference;
46    import org.xwiki.security.SecurityReference;
47    import org.xwiki.security.UserSecurityReference;
48    import org.xwiki.security.authorization.SecurityAccessEntry;
49    import org.xwiki.security.authorization.SecurityEntry;
50    import org.xwiki.security.authorization.SecurityRuleEntry;
51    import org.xwiki.security.authorization.cache.ConflictingInsertionException;
52    import org.xwiki.security.authorization.cache.ParentEntryEvictedException;
53    import org.xwiki.security.authorization.cache.SecurityShadowEntry;
54   
55    /**
56    * Default implementation of the security cache.
57    *
58    * @version $Id: ff6c8323eba5128a38405fd4c8e638c6a2c2689d $
59    * @since 4.0M2
60    */
61    @Component
62    @Singleton
 
63    public class DefaultSecurityCache implements SecurityCache, Initializable
64    {
65    /** Default capacity for security cache. */
66    private static final int DEFAULT_CAPACITY = 10000;
67   
68    /** Separator used for composing key for the cache. */
69    private static final String KEY_CACHE_SEPARATOR = "@@";
70   
71    /** Logger. **/
72    @Inject
73    private Logger logger;
74   
75    /** Fair read-write lock used for fair scheduling of cache access. */
76    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(true);
77   
78    /** Fair read lock. */
79    private final Lock readLock = readWriteLock.readLock();
80   
81    /** Fair write lock. */
82    private final Lock writeLock = readWriteLock.writeLock();
83   
84    /** The keys in the cache are generated from instances of {@link org.xwiki.model.reference.EntityReference}. */
85    @Inject
86    private EntityReferenceSerializer<String> keySerializer;
87   
88    /** Cache manager to create the cache. */
89    @Inject
90    private CacheManager cacheManager;
91   
92    /** The cache instance. */
93    private Cache<SecurityCacheEntry> cache;
94   
95    /** The new entry being added */
96    private SecurityCacheEntry newEntry;
97   
98    /**
99    * @return a new configured security cache
100    * @throws InitializationException if a CacheException arise during creation
101    */
 
102  202 toggle private Cache<SecurityCacheEntry> newCache() throws InitializationException
103    {
104  202 CacheConfiguration cacheConfig = new CacheConfiguration();
105  202 cacheConfig.setConfigurationId("platform.security.authorization.cache");
106  202 LRUEvictionConfiguration lru = new LRUEvictionConfiguration();
107  202 lru.setMaxEntries(DEFAULT_CAPACITY);
108  202 cacheConfig.put(LRUEvictionConfiguration.CONFIGURATIONID, lru);
109  202 try {
110  202 return cacheManager.createNewCache(cacheConfig);
111    } catch (Exception e) {
112  0 throw new InitializationException(
113    String.format("Unable to create the security cache with a capacity of [%d] entries",
114    lru.getMaxEntries()), e);
115    }
116    }
117   
 
118  202 toggle @Override
119    public void initialize() throws InitializationException
120    {
121  202 cache = newCache();
122    }
123   
124    /**
125    * Cache entry.
126    */
 
127    private class SecurityCacheEntry implements DisposableCacheValue
128    {
129    /**
130    * The cached security entry.
131    */
132    private SecurityEntry entry;
133   
134    /**
135    * Parents of this cached entry.
136    */
137    private Collection<SecurityCacheEntry> parents;
138   
139    /**
140    * Children of this cached entry.
141    */
142    private Collection<SecurityCacheEntry> children;
143   
144    /**
145    * True if this entry has been removed.
146    */
147    private boolean disposed;
148   
149    /**
150    * Create a new cache entry for a security rule, linking it to its parent.
151    * @param entry the security rule entry to cache.
152    * @throws ParentEntryEvictedException if the parent required is no more available in the cache.
153    */
 
154  2268 toggle SecurityCacheEntry(SecurityRuleEntry entry) throws ParentEntryEvictedException
155    {
156  2268 this.entry = entry;
157  2268 SecurityReference parentReference = entry.getReference().getParentSecurityReference();
158  2268 if (parentReference != null) {
159  2108 SecurityCacheEntry parent = DefaultSecurityCache.this.getEntry(parentReference);
160  2108 if (parent == null) {
161  1 throw new ParentEntryEvictedException();
162    }
163  2107 this.parents = Arrays.asList(parent);
164  2107 parent.addChild(this);
165  2107 logNewEntry();
166    } else {
167  160 this.parents = null;
168  160 logNewEntry();
169    }
170   
171    }
172   
173    /**
174    * Create a new cache entry for a security shadow, linking it to its parent.
175    * @param entry the security rule entry to cache.
176    * @throws ParentEntryEvictedException if the parent required is no more available in the cache.
177    */
 
178  44 toggle SecurityCacheEntry(SecurityShadowEntry entry) throws ParentEntryEvictedException
179    {
180  44 this.entry = entry;
181  44 SecurityCacheEntry parent1 = DefaultSecurityCache.this.getEntry(entry.getReference());
182  44 SecurityCacheEntry parent2 = DefaultSecurityCache.this.getEntry(entry.getWikiReference());
183  44 if (parent1 == null || parent2 == null) {
184  2 throw new ParentEntryEvictedException();
185    }
186  42 this.parents = Arrays.asList(parent1, parent2);
187  42 parent1.addChild(this);
188  42 parent2.addChild(this);
189  42 logNewEntry();
190    }
191   
192    /**
193    * Create a new cache entry for a user access, linking it to the related entity and user.
194    * @param entry the security access entry to cache.
195    * @throws ParentEntryEvictedException if the parents required are no more available in the cache.
196    */
 
197  0 toggle SecurityCacheEntry(SecurityAccessEntry entry) throws ParentEntryEvictedException
198    {
199  0 this(entry, null);
200    }
201   
202    /**
203    * Create a new cache entry for a user access, linking it to the related entity and user, or shadow user.
204    * @param entry the security access entry to cache.
205    * @param wiki if not null, the wiki context of the shadow user.
206    * @throws ParentEntryEvictedException if the parents required are no more available in the cache.
207    */
 
208  2642 toggle SecurityCacheEntry(SecurityAccessEntry entry, SecurityReference wiki) throws ParentEntryEvictedException
209    {
210  2642 this.entry = entry;
211  2642 boolean isSelf = entry.getReference().equals(entry.getUserReference());
212  2642 SecurityCacheEntry parent1 = DefaultSecurityCache.this.getEntry(entry.getReference());
213  2642 SecurityCacheEntry parent2 = (isSelf) ? parent1
214  2495 : (wiki != null) ? DefaultSecurityCache.this.getShadowEntry(entry.getUserReference(), wiki)
215    : DefaultSecurityCache.this.getEntry(entry.getUserReference());
216  2642 if (parent1 == null || parent2 == null) {
217  2 throw new ParentEntryEvictedException();
218    }
219  2640 this.parents = (isSelf) ? Arrays.asList(parent1) : Arrays.asList(parent1, parent2);
220  2640 parent1.addChild(this);
221  2640 if (!isSelf) {
222  2493 parent2.addChild(this);
223    }
224  2640 logNewEntry();
225    }
226   
227    /**
228    * Create a new cache entry for a user rule entry, linking it to its parent and to all provided groups.
229    * @param entry the security rule entry to cache.
230    * @param groups the list of groups to link this entry to.
231    * @throws ParentEntryEvictedException if the parents required are no more available in the cache.
232    */
 
233  195 toggle SecurityCacheEntry(SecurityRuleEntry entry, Collection<GroupSecurityReference> groups)
234    throws ParentEntryEvictedException
235    {
236  195 this(entry, groups, entry.getReference().getParentSecurityReference());
237    }
238   
239    /**
240    * Create a new cache entry for a user rule entry, linking it to its parent and to all provided groups.
241    * @param entry the security rule entry to cache.
242    * @param groups the list of groups to link this entry to.
243    * @throws ParentEntryEvictedException if the parents required are no more available in the cache.
244    */
 
245  58 toggle SecurityCacheEntry(SecurityShadowEntry entry, Collection<GroupSecurityReference> groups)
246    throws ParentEntryEvictedException
247    {
248  58 this(entry, groups, entry.getReference());
249    }
250   
251    /**
252    * Create a new cache entry for a user rule entry, linking it to its parent and to all provided groups.
253    * @param entry the security rule entry to cache.
254    * @param groups the list of groups to link this entry to.
255    * @param parentReference the reference to the parent to link to.
256    * @throws ParentEntryEvictedException if the parents required are no more available in the cache.
257    */
 
258  254 toggle private SecurityCacheEntry(SecurityEntry entry, Collection<GroupSecurityReference> groups,
259    SecurityReference parentReference)
260    throws ParentEntryEvictedException
261    {
262  254 this.entry = entry;
263  254 int parentSize = groups.size() + ((parentReference == null) ? 0 : 1);
264  254 if (parentSize > 0) {
265  254 this.parents = new ArrayList<SecurityCacheEntry>(parentSize);
266  254 if (parentReference != null) {
267  254 SecurityCacheEntry parent = DefaultSecurityCache.this.getEntry(parentReference);
268  254 if (parent == null) {
269  0 throw new ParentEntryEvictedException();
270    }
271  254 this.parents.add(parent);
272  254 parent.addChild(this);
273    }
274  254 addParentGroups(groups, parentReference);
275  253 logNewEntry();
276    } else {
277  0 this.parents = null;
278  0 logNewEntry();
279    }
280    }
281   
282    /**
283    * Add provided groups as parent of this entry, excluding the main parent reference.
284    *
285    * @param groups the list of groups to add.
286    * @param parentReference the main parent reference to exclude.
287    * @throws ParentEntryEvictedException if the parents required are no more available in the cache.
288    */
 
289  276 toggle private void addParentGroups(Collection<GroupSecurityReference> groups,
290    SecurityReference parentReference) throws ParentEntryEvictedException
291    {
292  276 for (GroupSecurityReference group : groups) {
293  171 if (group.equals(parentReference)) {
294  0 continue;
295    }
296  171 SecurityCacheEntry parent = (entry instanceof SecurityShadowEntry && group.isGlobal())
297    ? DefaultSecurityCache.this.getShadowEntry(group,
298    ((SecurityShadowEntry) entry).getWikiReference())
299    : DefaultSecurityCache.this.getEntry(group);
300  171 if (parent == null) {
301  1 throw new ParentEntryEvictedException();
302    }
303  170 this.parents.add(parent);
304  170 parent.addChild(this);
305    }
306    }
307   
308    /**
309    * Update an existing cached security rule entry with parents groups if it does not have any already.
310    *
311    * @param groups the groups to be added to this entry, if null or empty nothing will be done.
312    * @throws ParentEntryEvictedException if one of the groups has been evicted from the cache.
313    */
 
314  271 toggle boolean updateParentGroups(Collection<GroupSecurityReference> groups)
315    throws ParentEntryEvictedException
316    {
317  271 if (isUser() || !(entry instanceof SecurityRuleEntry)) {
318  228 return false;
319    }
320   
321  43 if (groups != null && !groups.isEmpty()) {
322  22 if (this.parents == null) {
323  0 this.parents = new ArrayList<SecurityCacheEntry>(groups.size());
324  0 addParentGroups(groups, null);
325    } else {
326  22 SecurityCacheEntry parent = this.parents.iterator().next();
327  22 this.parents = new ArrayList<SecurityCacheEntry>(groups.size() + 1);
328  22 this.parents.add(parent);
329  22 addParentGroups(groups, parent.entry.getReference());
330    }
331    }
332   
333  43 return true;
334    }
335   
336    /**
337    * Log the new entry creation.
338    */
 
339  5202 toggle private void logNewEntry()
340    {
341  5202 if (logger.isDebugEnabled()) {
342  0 if (parents == null || parents.size() == 0) {
343  0 logger.debug("New orphan entry [{}].", getKey());
344  0 return;
345    }
346  0 StringBuilder sb = new StringBuilder("New entry [");
347  0 sb.append(getKey()).append("] as child of ");
348  0 boolean first = true;
349  0 for (SecurityCacheEntry parent : parents) {
350  0 if (!first) {
351  0 sb.append(", ");
352    } else {
353  0 first = false;
354    }
355  0 sb.append('[').append(parent.getKey()).append(']');
356    }
357  0 sb.append(".");
358  0 logger.debug(sb.toString());
359    }
360    }
361   
362    /**
363    * @return the original security entry cached in this cache entry.
364    */
 
365  143487 toggle SecurityEntry getEntry()
366    {
367  143487 return this.entry;
368    }
369   
370    /**
371    * @return the serialized key of this entry.
372    */
 
373  2956 toggle String getKey()
374    {
375  2956 return DefaultSecurityCache.this.getEntryKey(entry);
376    }
377   
378    /**
379    * Dispose this entry from the cache, removing all children relation in its parents, and removing
380    * all its children recursively. This method is not thread safe in regards to the cache, proper
381    * locking should be done externally.
382    */
 
383  5589 toggle @Override
384    public void dispose()
385    {
386  5589 if (!disposed) {
387  2963 disposed = true;
388  2963 disconnectFromParents();
389  2963 disposeChildren();
390    }
391    }
392   
 
393  2963 toggle protected void disconnectFromParents()
394    {
395  2963 if (parents != null) {
396  2868 for (SecurityCacheEntry parent : parents) {
397  4610 if (!parent.disposed) {
398  1384 parent.removeChild(this);
399    }
400    }
401    }
402    }
403   
 
404  2963 toggle private void disposeChildren()
405    {
406  2963 if (children != null) {
407  931 for (SecurityCacheEntry child : children) {
408  3226 if (!child.disposed) {
409  2682 if (logger.isDebugEnabled()) {
410  0 logger.debug("Cascaded removal of entry [{}] from cache.", child.getKey());
411    }
412    // XWIKI-13746: Prevent an addition in progress to bite his own entry in a bad way.
413  2682 if (child == newEntry) {
414  0 child.dispose();
415    } else {
416  2682 try {
417  2682 DefaultSecurityCache.this.cache.remove(child.getKey());
418    } catch (Throwable e) {
419  0 logger.error("Security cache failure during eviction of entry [{}]", child.getKey(), e);
420    }
421    }
422    }
423    }
424    }
425    }
426   
427    /**
428    * Add a children to this cache entry.
429    * @param entry the children entry to add.
430    */
 
431  7748 toggle private void addChild(SecurityCacheEntry entry)
432    {
433  7748 if (this.children == null) {
434  1979 this.children = new ArrayList<SecurityCacheEntry>();
435    }
436  7748 this.children.add(entry);
437    }
438   
439    /**
440    * Remove a children from this cache entry.
441    * @param entry the children entry to remove.
442    */
 
443  1384 toggle private void removeChild(SecurityCacheEntry entry)
444    {
445  1384 if (this.children != null) {
446  1384 this.children.remove(entry);
447  1384 if (logger.isDebugEnabled()) {
448  0 logger.debug("Remove child [{}] from [{}].", entry.getKey(), getKey());
449    }
450    }
451    }
452   
453    /**
454    * @return true if the entity is a user.
455    */
 
456  2599 toggle public boolean isUser()
457    {
458  2599 return entry.getReference() instanceof UserSecurityReference && !(entry instanceof SecurityAccessEntry);
459    }
460    }
461   
462    /**
463    * @param reference the reference to build the key.
464    * @return a unique key for this reference.
465    */
 
466  100454 toggle private String getEntryKey(SecurityReference reference)
467    {
468  100454 return keySerializer.serialize(reference);
469    }
470   
471    /**
472    * @param userReference the user reference to build the key.
473    * @param reference the entity reference to build the key.
474    * @return a unique key for the combination of this user and entity.
475    */
 
476  65005 toggle private String getEntryKey(UserSecurityReference userReference, SecurityReference reference)
477    {
478  65005 return keySerializer.serialize(userReference)
479    + KEY_CACHE_SEPARATOR + keySerializer.serialize(reference);
480    }
481   
482    /**
483    * @param userReference the user reference to build the key.
484    * @param root the entity reference of the sub-wiki.
485    * @return a unique key for the combination of this user and entity.
486    */
 
487  442 toggle private String getShadowEntryKey(SecurityReference userReference, SecurityReference root)
488    {
489  442 return keySerializer.serialize(root) + KEY_CACHE_SEPARATOR + keySerializer.serialize(userReference);
490    }
491   
492    /**
493    * @param entry the security entry for which a key is requested. It could be either a {@link SecurityRuleEntry}
494    * or a {@link SecurityAccessEntry}.
495    * @return a unique key for this security entry.
496    */
 
497  5589 toggle private String getEntryKey(SecurityEntry entry)
498    {
499  5589 if (entry instanceof SecurityAccessEntry) {
500  1741 return getEntryKey((SecurityAccessEntry) entry);
501  3848 } else if (entry instanceof SecurityRuleEntry) {
502  3714 return getEntryKey((SecurityRuleEntry) entry);
503    } else {
504  133 return getEntryKey((SecurityShadowEntry) entry);
505    }
506    }
507   
508    /**
509    * @param entry the security entry for which a key is requested. It could be either a {@link SecurityRuleEntry}
510    * or a {@link SecurityAccessEntry}.
511    * @return a unique key for this security entry.
512    */
 
513  133 toggle private String getEntryKey(SecurityShadowEntry entry)
514    {
515  133 return getShadowEntryKey(entry.getReference(), entry.getWikiReference());
516    }
517   
518    /**
519    * @param entry the security rule entry for which the key is requested.
520    * @return a unique key for this security rule entry.
521    */
 
522  3713 toggle private String getEntryKey(SecurityRuleEntry entry)
523    {
524  3714 return getEntryKey(entry.getReference());
525    }
526   
527    /**
528    * @param entry the security access entry for which the key is requested.
529    * @return a unique key for this security access entry.
530    */
 
531  4589 toggle private String getEntryKey(SecurityAccessEntry entry)
532    {
533  4589 return getEntryKey(entry.getUserReference(), entry.getReference());
534    }
535   
536    /**
537    * @param reference the reference requested.
538    * @return a security cache entry corresponding to given reference, null if none is available in the cache.
539    */
 
540  96740 toggle private SecurityCacheEntry getEntry(SecurityReference reference)
541    {
542  96739 readLock.lock();
543  96740 try {
544  96740 return cache.get(getEntryKey(reference));
545    } finally {
546  96740 readLock.unlock();
547    }
548    }
549   
550    /**
551    * @param userReference the user reference requested.
552    * @param reference the reference requested.
553    * @return a security cache entry corresponding to the given user and reference, null if none is available
554    * in the cache.
555    */
 
556  60416 toggle private SecurityCacheEntry getEntry(UserSecurityReference userReference, SecurityReference reference)
557    {
558  60416 readLock.lock();
559  60416 try {
560  60416 return cache.get(getEntryKey(userReference, reference));
561    } finally {
562  60416 readLock.unlock();
563    }
564    }
565   
566    /**
567    * @param userReference the user reference requested.
568    * @param wiki the wiki context of the shadow reference to retrieve.
569    * @return a security cache entry corresponding to the given user and reference, null if none is available
570    * in the cache.
571    */
 
572  309 toggle private SecurityCacheEntry getShadowEntry(SecurityReference userReference, SecurityReference wiki)
573    {
574  309 readLock.lock();
575  309 try {
576  309 return cache.get(getShadowEntryKey(userReference, wiki));
577    } finally {
578  309 readLock.unlock();
579    }
580    }
581   
582    /**
583    * @param key the key of the cache slot to check.
584    * @param entry the entry to compare to.
585    * @return true, if the given entry has been inserted by another thread, false if the slot is available.
586    * @throws ConflictingInsertionException if another thread use this slot with a different entry.
587    */
 
588  2848 toggle private boolean isAlreadyInserted(String key, SecurityEntry entry) throws ConflictingInsertionException
589    {
590  2848 try {
591  2848 return isAlreadyInserted(key, entry, null);
592    } catch (ParentEntryEvictedException e) {
593    // Impossible to reach
594  0 return true;
595    }
596    }
597   
 
598  5481 toggle private boolean isAlreadyInserted(String key, SecurityEntry entry, Collection<GroupSecurityReference> groups)
599    throws ConflictingInsertionException, ParentEntryEvictedException
600    {
601  5481 SecurityCacheEntry oldEntry = cache.get(key);
602  5481 if (oldEntry != null) {
603  273 if (!oldEntry.getEntry().equals(entry)) {
604    // Another thread have inserted an entry which is different from this entry!
605  2 throw new ConflictingInsertionException();
606    }
607    // If the user/group has been completed
608  271 if (oldEntry.updateParentGroups(groups)) {
609    // Upgrade it to a user/group entry
610  43 oldEntry.entry = entry;
611    }
612   
613  271 return true;
614    }
615    // The slot is available
616  5208 return false;
617    }
618   
619    /**
620    * Add a new entry in the cache and prevent cache container deadlock (in cooperation with the entry
621    * dispose method) in case adding the entry cause this same entry to be evicted.
622    * @param key the key of the entry to be added.
623    * @param entry the entry to add.
624    * @throws ConflictingInsertionException when the entry have been disposed while being added, the full load should
625    * be retried.
626    */
 
627  5202 toggle private void addEntry(String key, SecurityCacheEntry entry) throws ConflictingInsertionException
628    {
629  5202 try {
630  5202 newEntry = entry;
631  5202 cache.set(key, newEntry);
632  5202 if (entry.disposed) {
633    // XWIKI-13746: The added entry have been disposed while being added, meaning that the eviction
634    // triggered by adding the entry has hit the entry itself, so remove it and fail.
635  0 cache.remove(key);
636  0 throw new ConflictingInsertionException();
637    }
638    } finally {
639  5202 newEntry = null;
640    }
641    }
642   
 
643  2258 toggle @Override
644    public void add(SecurityRuleEntry entry)
645    throws ParentEntryEvictedException, ConflictingInsertionException
646    {
647  2258 add(entry, null);
648    }
649   
 
650  2527 toggle @Override
651    public void add(SecurityRuleEntry entry, Collection<GroupSecurityReference> groups)
652    throws ConflictingInsertionException, ParentEntryEvictedException
653    {
654  2530 add((SecurityEntry) entry, groups);
655    }
656   
 
657  103 toggle @Override
658    public void add(SecurityShadowEntry entry, Collection<GroupSecurityReference> groups)
659    throws ConflictingInsertionException, ParentEntryEvictedException
660    {
661  103 add((SecurityEntry) entry, groups);
662    }
663   
664    /**
665    * Add either a rule or shadow user/group entry into the cache.
666    * @param entry the rule or shadow entry
667    * @param groups Local groups references that this user/group is a member.
668    * @exception ParentEntryEvictedException when the parent entry of
669    * this entry was evicted before this insertion. Since all
670    * entries, except wiki-entries, must have a parent cached, the
671    * {@link org.xwiki.security.authorization.cache.SecurityCacheLoader} must restart its load attempt.
672    * @throws ConflictingInsertionException when another thread have
673    * inserted this entry, but with a different content.
674    */
 
675  2630 toggle private void add(SecurityEntry entry, Collection<GroupSecurityReference> groups)
676    throws ConflictingInsertionException, ParentEntryEvictedException
677    {
678  2630 String key = getEntryKey(entry);
679   
680  2633 writeLock.lock();
681  2633 try {
682  2632 if (isAlreadyInserted(key, entry, groups)) {
683  66 return;
684    }
685  2566 addEntry(key, newSecurityCacheEntry(entry, groups));
686   
687  2562 logger.debug("Added rule/shadow entry [{}] into the cache.", key);
688    } finally {
689  2633 writeLock.unlock();
690    }
691    }
692   
693    /**
694    * Construct a security cache entry for the given arguments.
695    * @param entry the rule or shadow entry
696    * @param groups Local groups references that this user/group is a member.
697    * @return the created security cache entry
698    * @exception ParentEntryEvictedException when the parent entry of
699    * this entry was evicted before this insertion. Since all
700    * entries, except wiki-entries, must have a parent cached, the
701    * {@link org.xwiki.security.authorization.cache.SecurityCacheLoader} must restart its load attempt.
702    * @throws ConflictingInsertionException when another thread have
703    * inserted this entry, but with a different content.
704    */
 
705  2566 toggle private SecurityCacheEntry newSecurityCacheEntry(SecurityEntry entry, Collection<GroupSecurityReference> groups)
706    throws ConflictingInsertionException, ParentEntryEvictedException
707    {
708  2566 if (entry instanceof SecurityRuleEntry) {
709  2464 return (groups == null)
710    ? new SecurityCacheEntry((SecurityRuleEntry) entry)
711    : new SecurityCacheEntry((SecurityRuleEntry) entry, groups);
712    } else {
713  102 return (groups == null)
714    ? new SecurityCacheEntry((SecurityShadowEntry) entry)
715    : new SecurityCacheEntry((SecurityShadowEntry) entry, groups);
716    }
717    }
718   
 
719  269 toggle @Override
720    public void add(SecurityAccessEntry entry)
721    throws ParentEntryEvictedException, ConflictingInsertionException
722    {
723  269 internalAdd(entry, null);
724    }
725   
 
726  2579 toggle @Override
727    public void add(SecurityAccessEntry entry, SecurityReference wiki)
728    throws ParentEntryEvictedException, ConflictingInsertionException
729    {
730  2579 internalAdd(entry, wiki);
731    }
732   
733    /**
734    * Add an entry to this cache.
735    * @param entry The access entry to add.
736    * @param wiki The sub-wiki context of this entry. Null for a global entry.
737    * @throws ParentEntryEvictedException when the parent entry of
738    * this entry was evicted before this insertion. Since all
739    * entries, except wiki-entries, must have a parent cached, the
740    * {@link org.xwiki.security.authorization.cache.SecurityCacheLoader} must restart its load attempt.
741    * @throws ConflictingInsertionException when another thread have
742    * inserted this entry, but with a different content.
743    */
 
744  2848 toggle private void internalAdd(SecurityAccessEntry entry, SecurityReference wiki)
745    throws ParentEntryEvictedException, ConflictingInsertionException
746    {
747  2848 String key = getEntryKey(entry);
748   
749  2848 writeLock.lock();
750  2848 try {
751  2847 if (isAlreadyInserted(key, entry)) {
752  205 return;
753    }
754  2642 addEntry(key, new SecurityCacheEntry(entry, wiki));
755   
756  2640 logger.debug("Added access entry [{}] into the cache.", key);
757    } finally {
758  2848 newEntry = null;
759  2848 writeLock.unlock();
760    }
761    }
762   
763    /**
764    * Retrieve an entry from the cache directly the internal cache. Used during unit test only.
765    * @param entryKey the key to be retrieved.
766    * @return the entry stored in the internal cache or Null if no entry was found.
767    */
 
768  1826 toggle SecurityEntry get(String entryKey) {
769  1826 SecurityCacheEntry entry = cache.get(entryKey);
770  1826 return (entry != null) ? entry.getEntry() : null;
771    }
772   
 
773  60415 toggle @Override
774    public SecurityAccessEntry get(UserSecurityReference user, SecurityReference entity)
775    {
776  60415 SecurityCacheEntry entry = getEntry(user, entity);
777  60415 if (entry == null) {
778  1145 if (logger.isDebugEnabled()) {
779  0 logger.debug("Miss read access entry for [{}].", getEntryKey(user, entity));
780    }
781  1145 return null;
782    }
783  59270 if (logger.isDebugEnabled()) {
784  0 logger.debug("Success read access entry for [{}].", getEntryKey(user, entity));
785    }
786  59270 return (SecurityAccessEntry) entry.getEntry();
787    }
788   
 
789  82499 toggle @Override
790    public SecurityRuleEntry get(SecurityReference entity)
791    {
792  82498 SecurityCacheEntry entry = getEntry(entity);
793  82499 if (entry == null) {
794  3610 if (logger.isDebugEnabled()) {
795  0 logger.debug("Miss read rule entry for [{}].", getEntryKey(entity));
796    }
797  3610 return null;
798    }
799  78889 if (logger.isDebugEnabled()) {
800  0 logger.debug("Success read rule entry for [{}].", getEntryKey(entity));
801    }
802  78889 return (SecurityRuleEntry) entry.getEntry();
803    }
804   
 
805  1 toggle @Override
806    public void remove(UserSecurityReference user, SecurityReference entity)
807    {
808  1 writeLock.lock();
809  1 try {
810  1 SecurityCacheEntry entry = getEntry(user, entity);
811  1 if (entry != null) {
812  1 if (logger.isDebugEnabled()) {
813  0 logger.debug("Remove outdated access entry for [{}].", getEntryKey(user, entity));
814    }
815  1 this.cache.remove(entry.getKey());
816    }
817    } finally {
818  1 writeLock.unlock();
819    }
820    }
821   
 
822  4214 toggle @Override
823    public void remove(SecurityReference entity)
824    {
825  4214 writeLock.lock();
826  4214 try {
827  4214 SecurityCacheEntry entry = getEntry(entity);
828  4214 if (entry != null) {
829  273 if (logger.isDebugEnabled()) {
830  0 logger.debug("Remove outdated rule entry for [{}].", getEntryKey(entity));
831    }
832  273 this.cache.remove(entry.getKey());
833    }
834    } finally {
835  4214 writeLock.unlock();
836    }
837    }
838   
 
839  15 toggle @Override
840    public Collection<GroupSecurityReference> getImmediateGroupsFor(UserSecurityReference user)
841    {
842  15 Collection<GroupSecurityReference> groups = new HashSet<>();
843   
844  15 SecurityCacheEntry userEntry = getEntry(user);
845    // If the user is not in the cache, or if it is, but not as a user, but as a regular document
846  15 if (userEntry == null || !userEntry.isUser()) {
847    // In that case, the ancestors are not fully loaded
848  0 return null;
849    }
850   
851  15 for (SecurityCacheEntry parent : userEntry.parents) {
852    // Add the parent group (if we have not already seen it)
853  16 SecurityReference parentRef = parent.getEntry().getReference();
854  16 if (parentRef instanceof GroupSecurityReference) {
855  1 groups.add((GroupSecurityReference) parentRef);
856    }
857    }
858  15 return groups;
859    }
860   
 
861  2524 toggle @Override
862    public Collection<GroupSecurityReference> getGroupsFor(UserSecurityReference user, SecurityReference entityWiki)
863    {
864  2524 Collection<GroupSecurityReference> groups = new HashSet<>();
865   
866  2524 SecurityCacheEntry userEntry = (entityWiki != null) ? getShadowEntry(user, entityWiki) : getEntry(user);
867   
868    // If the user is not in the cache, or if it is, but not as a user, but as a regular document
869  2524 if (userEntry == null || !userEntry.isUser()) {
870    // In that case, the ancestors are not fully loaded
871  1297 return null;
872    }
873   
874    // We are going to get the parents of the security cache entry recursively, that is why we use a stack
875    // (instead of using the execution stack which would be more costly).
876  1227 Deque<SecurityCacheEntry> entriesToExplore = new ArrayDeque<>();
877   
878    // Special case if the user is a shadow.
879  1227 if (entityWiki != null) {
880    // We start with the parents of the original entry, and the parent of this shadow (excluding the original)
881  31 addParentsWhenEntryIsShadow(userEntry, user, groups, entriesToExplore);
882    } else {
883    // We start with the current user
884  1196 entriesToExplore.add(userEntry);
885    }
886   
887    // Let's go
888  3562 while (!entriesToExplore.isEmpty()) {
889  2335 SecurityCacheEntry entry = entriesToExplore.pop();
890   
891    // We add the parents of the current entry
892  2335 addParentsToTheListOfEntriesToExplore(entry.parents, groups, entriesToExplore);
893   
894    // If the entry has a shadow (in the concerned subwiki), we also add the parents of the shadow
895  2335 if (entityWiki != null) {
896  21 GroupSecurityReference entryRef = (GroupSecurityReference) entry.getEntry().getReference();
897  21 if (entryRef.isGlobal()) {
898  8 SecurityCacheEntry shadow = getShadowEntry(entryRef, entityWiki);
899  8 if (shadow != null) {
900  8 addParentsToTheListOfEntriesToExplore(shadow.parents, groups, entriesToExplore, entry);
901    }
902    }
903    }
904    }
905   
906  1227 return groups;
907    }
908   
 
909  31 toggle private void addParentsWhenEntryIsShadow(SecurityCacheEntry shadow, UserSecurityReference user,
910    Collection<GroupSecurityReference> groups,
911    Deque<SecurityCacheEntry> entriesToExplore)
912    {
913  31 SecurityCacheEntry originalEntry = getEntry(user);
914   
915    // We add the parents of the original (but not the original, otherwise we could have the same group twice)
916  31 addParentsToTheListOfEntriesToExplore(originalEntry.parents, groups, entriesToExplore);
917    // And we add the parent groups of the shadow
918  31 addParentsToTheListOfEntriesToExplore(shadow.parents, groups, entriesToExplore, originalEntry);
919    }
920   
921    /**
922    * Add the parents of an entry to the list of entries to explore.
923    *
924    * @param parents the parents of the entry
925    * @param groups the collection where we store the found groups
926    * @param entriesToExplore the collection holding the entries we still have to explore
927    */
 
928  2366 toggle private void addParentsToTheListOfEntriesToExplore(Collection<SecurityCacheEntry> parents,
929    Collection<GroupSecurityReference> groups,
930    Deque<SecurityCacheEntry> entriesToExplore)
931    {
932  2366 addParentsToTheListOfEntriesToExplore(parents, groups, entriesToExplore, null);
933    }
934   
935    /**
936    * Add the parents of an entry to the list of entries to explore.
937    *
938    * @param parents the parents of the entry
939    * @param groups the collection where we store the found groups
940    * @param entriesToExplore the collection holding the entries we still have to explore
941    * @param originalEntry the original entry of the current entry (if the current entry is a shadow), null otherwise
942    */
 
943  2405 toggle private void addParentsToTheListOfEntriesToExplore(Collection<SecurityCacheEntry> parents,
944    Collection<GroupSecurityReference> groups,
945    Deque<SecurityCacheEntry> entriesToExplore, SecurityCacheEntry originalEntry)
946    {
947  2405 if (parents == null) {
948  0 return;
949    }
950   
951  2405 for (SecurityCacheEntry parent : parents) {
952    // skip this parent if the entry is a shadow and the parent is the original entry
953    // (ie: don't explore the original entry)
954  3552 if (originalEntry != null && parent == originalEntry) {
955  39 continue;
956    }
957   
958    // Add the parent group (if we have not already seen it)
959  3513 SecurityReference parentRef = parent.getEntry().getReference();
960  3513 if (parentRef instanceof GroupSecurityReference && groups.add((GroupSecurityReference) parentRef)) {
961  1139 entriesToExplore.add(parent);
962    }
963    }
964    }
965   
966   
967    }