001/*
002 *  Copyright 2017 Anyware Services
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package org.ametys.plugins.core.impl.right;
017
018import java.util.Collections;
019import java.util.HashMap;
020import java.util.List;
021import java.util.Map;
022import java.util.Set;
023
024import org.apache.avalon.framework.component.Component;
025import org.apache.avalon.framework.service.ServiceException;
026import org.apache.avalon.framework.service.ServiceManager;
027import org.apache.avalon.framework.service.Serviceable;
028import org.apache.commons.collections.CollectionUtils;
029
030import org.ametys.core.group.GroupIdentity;
031import org.ametys.core.right.AccessController;
032import org.ametys.core.right.ProfileAssignmentStorageExtensionPoint;
033import org.ametys.core.right.RightManager;
034import org.ametys.core.right.RightProfilesDAO;
035import org.ametys.core.user.UserIdentity;
036import org.ametys.runtime.plugin.component.AbstractLogEnabled;
037
038/**
039 * This class delegates all it can to the profile assignment storage extension point
040 */
041public abstract class AbstractProfileStorageBasedAccessController extends AbstractLogEnabled implements AccessController, Component, Serviceable
042{
043    /**
044     * The knd of cache to get/set
045     */
046    protected enum CacheKind
047    {
048        /** for anonymous user */
049        ANONYMOUS,
050        /** for any connected user */
051        ANY_CONNECTED_USER,
052        /** for users */
053        USERS,
054        /** for user */
055        USER,
056        /** for groups */
057        GROUPS
058    }
059    
060    /** The instance of ObjectUserIdentity for anonymous */
061    protected static final UserIdentity __ANONYMOUS_USER_IDENTITY = null; 
062    /** The instance of ObjectUserIdentity for any connected user */
063    protected static final UserIdentity __ANY_CONTECTED_USER_IDENTITY = new UserIdentity(null, null);   
064
065    /** The extension point for the profile assignment storages */
066    protected ProfileAssignmentStorageExtensionPoint _profileAssignmentStorageEP;
067    /** The right profile DAO */
068    protected RightProfilesDAO _rightProfileDAO;
069    /** The right manager */ 
070    protected RightManager _rightManager;
071    
072    /**
073     * This first cache is for right result on non-null contexts when calling hasXXX methods
074     * On the contratry of the other cache, this one is split between profiles, as we check for the first true result (no negativity)
075     * 
076     * { 
077     *      UserIdentity : 
078     *      {
079     *          Set<ProfileId> :
080     *          {
081     *              Context : boolean
082     *          }
083     *      }
084     * }
085     */
086    private final String _cache1 = this.getClass().getName() + "$Cache-1";
087    
088    /**
089     * This second cache is for right result on non-null contexts when calling getXXXByGroup methods
090     * 
091     * { 
092     *      Set<ProfileId> : 
093     *      {
094     *          Context : {
095     *              CacheKind.ANONYMOUS: AccessResult
096     *              CacheKind.ANY_CONNECTED_USER: AccessResult
097     *              CacheKind.USERS: Map<UserIdentity, AccessResult>,
098     *              CacheKind.USER: Map<UserIdentity, AccessResult>,
099     *              CacheKind.GROUPS: Map<GroupIdentity, AccessResult>
100     *          }
101     *      }
102     * }
103     */
104    private final String _cache2 = this.getClass().getName() + "$Cache-2";
105
106    
107    @Override
108    public void service(ServiceManager manager) throws ServiceException
109    {
110        _rightManager = (RightManager) manager.lookup(RightManager.ROLE);
111        _rightProfileDAO = (RightProfilesDAO) manager.lookup(RightProfilesDAO.ROLE);
112        _profileAssignmentStorageEP = (ProfileAssignmentStorageExtensionPoint) manager.lookup(ProfileAssignmentStorageExtensionPoint.ROLE);
113    }
114    
115    public Map<String, AccessResult> getPermissionByRight(UserIdentity user, Set<GroupIdentity> userGroups, Object object)
116    {
117        Map<String, AccessResult> rights = new HashMap<>();
118        
119        Map<String, AccessResult> permissionsByProfile = _profileAssignmentStorageEP.getPermissionsByProfile(user, userGroups, _convertContext(object));
120        
121        // Convert profile <-> accessresult to right <-> accessresult
122        for (String profileId : permissionsByProfile.keySet())
123        {
124            List<String> rights2 = _rightProfileDAO.getRights(profileId);
125            for (String rightId : rights2)
126            {
127                rights.put(rightId, AccessResult.merge(permissionsByProfile.get(profileId), rights.get(rightId)));
128            }
129        }
130
131        return rights;
132    }
133    
134    public AccessResult getPermission(UserIdentity user, Set<GroupIdentity> userGroups, String rightId, Object object)
135    {
136        Set<String> profilesIds = _rightProfileDAO.getProfilesWithRight(rightId);
137        if (profilesIds == null || profilesIds.isEmpty())
138        {
139            // No need for cache...
140            return AccessResult.UNKNOWN;
141        }
142        else
143        {
144            Object convertedObject = _convertContext(object);
145            return _getPermission(user, userGroups, profilesIds, object, convertedObject);
146        }
147    }
148    public AccessResult getReadAccessPermission(UserIdentity user, Set<GroupIdentity> userGroups, Object object)
149    {
150        Set<String> profilesIds = Collections.singleton(RightManager.READER_PROFILE_ID);
151        // Some converted context may change so cache must be done with the convertedObject
152        Object convertedObject = _convertContext(object);
153        return _getPermission(user, userGroups, profilesIds, object, convertedObject);
154    }
155    /**
156     * Works for getPermission or getReadAccessPermission
157     * @param user The use
158     * @param userGroups The groups
159     * @param profilesIds The profiles
160     * @param object The original context
161     * @param convertedObject The converted context
162     * @return the computed result
163     */
164    protected AccessResult _getPermission(UserIdentity user, Set<GroupIdentity> userGroups, Set<String> profilesIds, Object object, Object convertedObject)
165    {
166        @SuppressWarnings("unchecked")
167        Map<UserIdentity, AccessResult> cacheResult = (Map<UserIdentity, AccessResult>) _hasRightResultInSecondCache(convertedObject, profilesIds, CacheKind.USER);
168        if (cacheResult != null && cacheResult.containsKey(user))
169        {
170            return cacheResult.get(user);
171        }
172
173        Map<String, AccessResult> permissions = _profileAssignmentStorageEP.getPermissions(user, userGroups, profilesIds, convertedObject);
174        AccessResult result = AccessResult.merge(permissions.values());
175        cacheResult = cacheResult == null ? new HashMap<>() : cacheResult;
176        cacheResult.put(user, result);
177        _putInSecondCache(profilesIds, convertedObject, cacheResult, CacheKind.USER);
178        return result;
179    }
180
181    public AccessResult getPermissionForAnonymous(String rightId, Object object)
182    {
183        Set<String> profilesIds = _rightProfileDAO.getProfilesWithRight(rightId);
184        if (profilesIds == null || profilesIds.isEmpty())
185        {
186            // No need for cache...
187            return AccessResult.UNKNOWN;
188        }
189        else
190        {
191            // Some converted context may change so cache must be done with the convertedObject
192            Object convertedObject = _convertContext(object);
193            return _getPermissionForAnonymous(profilesIds, object, convertedObject);
194        }
195    }
196    public AccessResult getReadAccessPermissionForAnonymous(Object object)
197    {
198        Set<String> profilesIds = Collections.singleton(RightManager.READER_PROFILE_ID);
199        // Some converted context may change so cache must be done with the convertedObject
200        Object convertedObject = _convertContext(object);
201        return _getPermissionForAnonymous(profilesIds, object, convertedObject);
202    }
203    /**
204     * Works for getPermissionForAnonymous and getReadAccessPermissionForAnonymous
205     * @param profilesIds The profiles ids
206     * @param object The context
207     * @param convertedObject The converted context
208     * @return The access result
209     */
210    protected AccessResult _getPermissionForAnonymous(Set<String> profilesIds, Object object, Object convertedObject)
211    {
212        // Try to retrieve in second cache
213        AccessResult cacheResult = (AccessResult) _hasRightResultInSecondCache(convertedObject, profilesIds, CacheKind.ANONYMOUS);
214        if (cacheResult != null)
215        {
216            return cacheResult;
217        }
218        
219        AccessResult result = _profileAssignmentStorageEP.getPermissionForAnonymous(profilesIds, convertedObject);
220        _putInSecondCache(profilesIds, convertedObject, result, CacheKind.ANONYMOUS);
221        return result;
222    }
223    
224    public AccessResult getPermissionForAnyConnectedUser(String rightId, Object object)
225    {
226        Set<String> profilesIds = _rightProfileDAO.getProfilesWithRight(rightId);
227        if (profilesIds == null || profilesIds.isEmpty())
228        {
229            // No need for cache...
230            return AccessResult.UNKNOWN;
231        }
232        else
233        {
234            // Some converted context may change so cache must be done with the convertedObject
235            Object convertedObject = _convertContext(object);
236            return _getPermissionForAnyConnectedUser(profilesIds, object, convertedObject);
237        }
238    }
239    public AccessResult getReadAccessPermissionForAnyConnectedUser(Object object)
240    {
241        Set<String> profilesIds = Collections.singleton(RightManager.READER_PROFILE_ID);
242        // Some converted context may change so cache must be done with the convertedObject
243        Object convertedObject = _convertContext(object);
244        return _getPermissionForAnyConnectedUser(profilesIds, object, convertedObject);
245    }
246    /**
247     * Works for getPermissionForAnyConnectedUser and getReadAccessPermissionForAnyConnectedUser
248     * @param profilesIds The profiles ids
249     * @param object The context
250     * @param convertedObject The converted context
251     * @return the access result
252     */
253    protected AccessResult _getPermissionForAnyConnectedUser(Set<String> profilesIds, Object object, Object convertedObject)
254    {
255        // Try to retrieve in second cache
256        AccessResult cacheResult = (AccessResult) _hasRightResultInSecondCache(convertedObject, profilesIds, CacheKind.ANY_CONNECTED_USER);
257        if (cacheResult != null)
258        {
259            return cacheResult;
260        }
261        
262        AccessResult result = _profileAssignmentStorageEP.getPermissionForAnyConnectedUser(profilesIds, convertedObject);
263        _putInSecondCache(profilesIds, convertedObject, result, CacheKind.ANY_CONNECTED_USER);
264        return result;
265    }
266
267    public Map<UserIdentity, AccessResult> getPermissionByUser(String rightId, Object object)
268    {
269        Set<String> profilesIds = _rightProfileDAO.getProfilesWithRight(rightId);
270        if (profilesIds == null || profilesIds.isEmpty())
271        {
272            // No need for cache...
273            return Collections.EMPTY_MAP;
274        }
275        else
276        {
277            // Some converted context may change so cache must be done with the convertedObject
278            Object convertedObject = _convertContext(object);
279            return _getPermissionByUser(profilesIds, object, convertedObject);
280        }
281    }
282    public Map<UserIdentity, AccessResult> getReadAccessPermissionByUser(Object object)
283    {
284        Set<String> profilesIds = Collections.singleton(RightManager.READER_PROFILE_ID);
285        // Some converted context may change so cache must be done with the convertedObject
286        Object convertedObject = _convertContext(object);
287        return _getPermissionByUser(profilesIds, object, convertedObject);
288    }
289    /**
290     * Works for getPermissionByUser and getReadAccessPermissionByUser
291     * @param profilesIds The profiles ids
292     * @param object The context
293     * @param convertedObject The converted context
294     * @return The users and their access results
295     */
296    protected Map<UserIdentity, AccessResult> _getPermissionByUser(Set<String> profilesIds, Object object, Object convertedObject)
297    {
298        // Try to retrieve in second cache
299        @SuppressWarnings("unchecked")
300        Map<UserIdentity, AccessResult> cacheResult = (Map<UserIdentity, AccessResult>) _hasRightResultInSecondCache(convertedObject, profilesIds, CacheKind.USERS);
301        if (cacheResult != null)
302        {
303            return cacheResult;
304        }
305        
306        Map<UserIdentity, AccessResult> result = _profileAssignmentStorageEP.getPermissionsByUser(profilesIds, convertedObject);
307        _putInSecondCache(profilesIds, convertedObject, result, CacheKind.USERS);
308        return result;
309    }
310    
311    public Map<GroupIdentity, AccessResult> getPermissionByGroup(String rightId, Object object)
312    {
313        Set<String> profilesIds = _rightProfileDAO.getProfilesWithRight(rightId);
314        if (profilesIds == null || profilesIds.isEmpty())
315        {
316            // No need for cache...
317            return Collections.EMPTY_MAP;
318        }
319        else
320        {
321            // Some converted context may change so cache must be done with the convertedObject
322            Object convertedObject = _convertContext(object);
323            return _getPermissionByGroup(profilesIds, object, convertedObject);
324        }
325    }
326    public Map<GroupIdentity, AccessResult> getReadAccessPermissionByGroup(Object object)
327    {
328        Set<String> profilesIds = Collections.singleton(RightManager.READER_PROFILE_ID);
329        // Some converted context may change so cache must be done with the convertedObject
330        Object convertedObject = _convertContext(object);
331        return _getPermissionByGroup(profilesIds, object, convertedObject);
332    }
333    /**
334     * Works for getPermissionByGroup and getReadAccessPermissionByGroup
335     * @param profilesIds The profiles ids
336     * @param object The context
337     * @param convertedObject The converted context
338     * @return The users and their access results
339     */
340    protected Map<GroupIdentity, AccessResult> _getPermissionByGroup(Set<String> profilesIds, Object object, Object convertedObject)
341    {
342        // Try to retrieve in second cache
343        @SuppressWarnings("unchecked")
344        Map<GroupIdentity, AccessResult> cacheResult = (Map<GroupIdentity, AccessResult>) _hasRightResultInSecondCache(convertedObject, profilesIds, CacheKind.GROUPS);
345        if (cacheResult != null)
346        {
347            return cacheResult;
348        }
349        
350        Map<GroupIdentity, AccessResult> result = _profileAssignmentStorageEP.getPermissionsByGroup(profilesIds, convertedObject);
351        _putInSecondCache(profilesIds, convertedObject, result, CacheKind.GROUPS);
352        return result;
353    }
354
355    /**
356     * For methods getXXXXPermissionYYY allow to have a modification of the context before transfering it to the profile assignment storage extension point
357     * The default implemenation keep the context as it is
358     * @param initialContext The right context that is supported
359     * @return the context modified
360     */
361    protected Object _convertContext(Object initialContext)
362    {
363        return initialContext;
364    }
365    
366    public boolean hasAnonymousAnyReadAccessPermissionOnWorkspace(Set<Object> workspacesContexts)
367    {
368        Set<String> profilesIds = Collections.singleton(RightManager.READER_PROFILE_ID);
369        return _hasAnonymousAnyPermissionOnWorkspace(workspacesContexts, profilesIds);
370    }
371    public boolean hasAnonymousAnyPermissionOnWorkspace(Set<Object> workspacesContexts, String rightId)
372    {
373        Set<String> profilesIds = _rightProfileDAO.getProfilesWithRight(rightId);
374        return _hasAnonymousAnyPermissionOnWorkspace(workspacesContexts, profilesIds);
375    }
376    private boolean _hasAnonymousAnyPermissionOnWorkspace(Set<Object> workspacesContexts, Set<String> profilesIds)
377    {
378        // Try to retrieve in first cache (the one which manages non-null contexts)
379        Boolean cacheResult = _hasRightResultInFirstCache(__ANONYMOUS_USER_IDENTITY, profilesIds, workspacesContexts);
380        if (cacheResult != null)
381        {
382            return cacheResult;
383        }
384
385        // Otherwise continue
386        Set<? extends Object> rootContexts = _convertWorkspaceToRootRightContexts(workspacesContexts);
387        boolean rightResult = CollectionUtils.isNotEmpty(rootContexts) ? _profileAssignmentStorageEP.hasAnonymousAnyPermission(rootContexts, profilesIds) : false;
388        _putInFirstCache(__ANONYMOUS_USER_IDENTITY, profilesIds, workspacesContexts, rightResult);
389        return rightResult;
390    }
391
392    public boolean hasAnyConnectedUserAnyReadAccessPermissionOnWorkspace(Set<Object> workspacesContexts)
393    {
394        Set<String> profilesIds = Collections.singleton(RightManager.READER_PROFILE_ID);
395        return _hasAnyConnectedUserAnyPermissionOnWorkspace(workspacesContexts, profilesIds);
396    }
397    public boolean hasAnyConnectedUserAnyPermissionOnWorkspace(Set<Object> workspacesContexts, String rightId)
398    {
399        Set<String> profilesIds = _rightProfileDAO.getProfilesWithRight(rightId);
400        return _hasAnyConnectedUserAnyPermissionOnWorkspace(workspacesContexts, profilesIds);
401    }
402    private boolean _hasAnyConnectedUserAnyPermissionOnWorkspace(Set<Object> workspacesContexts, Set<String> profilesIds)
403    {
404        // Try to retrieve in first cache (the one which manages non-null contexts)
405        Boolean cacheResult = _hasRightResultInFirstCache(__ANY_CONTECTED_USER_IDENTITY, profilesIds, workspacesContexts);
406        if (cacheResult != null)
407        {
408            return cacheResult;
409        }
410
411        // Otherwise continue
412        Set<? extends Object> rootContexts = _convertWorkspaceToRootRightContexts(workspacesContexts);
413        boolean rightResult = CollectionUtils.isNotEmpty(rootContexts) ? _profileAssignmentStorageEP.hasAnyConnectedUserAnyPermission(rootContexts, profilesIds) : false;
414        _putInFirstCache(__ANY_CONTECTED_USER_IDENTITY, profilesIds, workspacesContexts, rightResult);
415        return rightResult;
416    }
417    
418    public boolean hasUserAnyReadAccessPermissionOnWorkspace(Set<Object> workspacesContexts, UserIdentity user, Set<GroupIdentity> userGroups)
419    {
420        Set<String> profilesIds = Collections.singleton(RightManager.READER_PROFILE_ID);
421        return _hasUserAnyPermissionOnWorkspace(workspacesContexts, user, userGroups, profilesIds);
422    }
423    public boolean hasUserAnyPermissionOnWorkspace(Set<Object> workspacesContexts, UserIdentity user, Set<GroupIdentity> userGroups, String rightId)
424    {
425        Set<String> profilesIds = _rightProfileDAO.getProfilesWithRight(rightId);
426        return _hasUserAnyPermissionOnWorkspace(workspacesContexts, user, userGroups, profilesIds);
427    }
428    private boolean _hasUserAnyPermissionOnWorkspace(Set<Object> workspacesContexts, UserIdentity user, Set<GroupIdentity> userGroups, Set<String> profilesIds)
429    {
430        // Try to retrieve in first cache (the one which manages non-null contexts)
431        Boolean cacheResult = _hasRightResultInFirstCache(user, profilesIds, workspacesContexts);
432        if (cacheResult != null)
433        {
434            return cacheResult;
435        }
436
437        // Otherwise continue
438        Set<? extends Object> rootContexts = _convertWorkspaceToRootRightContexts(workspacesContexts);
439        boolean rightResult = CollectionUtils.isNotEmpty(rootContexts) ? _profileAssignmentStorageEP.hasUserAnyPermission(rootContexts, user, userGroups, profilesIds) : false;
440        _putInFirstCache(user, profilesIds, workspacesContexts, rightResult);
441        return rightResult;
442    }
443    
444    /**
445     * Get the current workspaces contexts and turn it into root contexts in order to allow methods hasXXXAnyPermissionOnWorkspace to work
446     * @param workspacesContexts The workspace contexts. Such as '/${WorkspaceName}', '/admin'
447     * @return A null or empty set if the current AccessController does not apply to any workspace context, or the root object where ProfileAssignmentStorageExtension should start looking at to find any permission
448     */
449    protected abstract Set<? extends Object> _convertWorkspaceToRootRightContexts(Set<Object> workspacesContexts);
450    
451    
452    
453    
454
455    
456    /**
457     * Seek in cache
458     * @param userIdentity The user identity or AbstractProfileStorageBasedAccessController.__ANONYMOUS_USER_IDENTITY or AbstractProfileStorageBasedAccessController.__ANY_CONTECTED_USER_IDENTITY
459     * @param profilesIds The profiles identifiers
460     * @param object The context
461     * @return true or false if in cache. null otherwise
462     */
463    protected Boolean _hasRightResultInFirstCache(UserIdentity userIdentity, Set<String> profilesIds, Object object)
464    {
465        // On the contrary of the other cache, this one is split between profiles, as we check for the first true result (no negativity)
466        
467        if (profilesIds == null || profilesIds.isEmpty())
468        {
469            // No need for cache...
470            return false;
471        }
472        
473        Map<UserIdentity, Map<Set<String>, Map<Object, Boolean>>> mapCache = _rightManager.getCache(_cache1, false);
474        if (mapCache != null)
475        {
476            if (mapCache.containsKey(userIdentity))
477            {
478                int negativeProfiles = 0;
479                
480                Map<Set<String>, Map<Object, Boolean>> mapProfile = mapCache.get(userIdentity);
481                if (mapProfile.containsKey(profilesIds))
482                {
483                    Map<Object, Boolean> mapContext = mapProfile.get(profilesIds);
484                    if (mapContext.containsKey(object))
485                    {
486                        if (mapContext.get(object))
487                        {
488                            getLogger().debug("Find entry in cache for [{}, {}, {}] => true", userIdentity, profilesIds, object);
489                            return true;
490                        }
491                        else
492                        {
493                            negativeProfiles++;
494                        }
495                    }
496                }
497                
498                if (negativeProfiles == profilesIds.size())
499                {
500                    // All required profiles were negative in cache
501                    return false;
502                }
503            }
504        }
505        
506        getLogger().debug("Did not find entry in cache for [{}, {}, {}]", userIdentity, profilesIds, object);
507        return null;
508    }
509    
510    /**
511     * Add to cache
512     * @param userIdentity The user identity or AbstractProfileStorageBasedAccessController.__ANONYMOUS_USER_IDENTITY or AbstractProfileStorageBasedAccessController.__ANY_CONTECTED_USER_IDENTITY
513     * @param profilesIds The profiles identifiers
514     * @param object The context
515     * @param rightResult The cache value. true if hasXXX or false otherwise.
516     */
517    protected void _putInFirstCache(UserIdentity userIdentity, Set<String> profilesIds, Object object, boolean rightResult)
518    {
519        // On the contratry of the other cache, this one is split between profiles, as we check for the first true result (no negativity)
520
521        Map<UserIdentity, Map<Set<String>, Map<Object, Boolean>>> mapCache = _rightManager.getCache(_cache1, true);
522        if (mapCache != null)
523        {
524            if (!mapCache.containsKey(userIdentity))
525            {
526                mapCache.put(userIdentity, new HashMap<>());
527            }
528            
529            Map<Set<String>, Map<Object, Boolean>> mapProfile = mapCache.get(userIdentity);
530                
531            if (!mapProfile.containsKey(profilesIds))
532            {
533                mapProfile.put(profilesIds, new HashMap<>());
534            }
535            
536            Map<Object, Boolean> mapContext = mapProfile.get(profilesIds);
537            mapContext.put(object, rightResult);
538            
539        }
540    }
541
542    /**
543     * Seek in cache
544     * @param object The context
545     * @param profilesIds The set of profile ids to consider
546     * @return The cached result per group. null otherwise
547     * @param key The kind of cache to use
548     */
549    protected Object _hasRightResultInSecondCache(Object object, Set<String> profilesIds, CacheKind key)
550    {
551        Map<Set<String>, Map<Object, Map<CacheKind, Object>>> mapCache = _rightManager.getCache(_cache2, false);
552        if (mapCache != null)
553        {
554            if (mapCache.containsKey(profilesIds))
555            {
556                Map<Object, Map<CacheKind, Object>> mapAll = mapCache.get(profilesIds);
557                if (mapAll.containsKey(object))
558                {
559                    Map<CacheKind, Object> mapResult = mapAll.get(object);
560                    Object cacheResult = mapResult.get(key);
561                    if (cacheResult != null)
562                    {
563                        getLogger().debug("Find entry in cache for [{}, {}, {}] => {}", profilesIds, object, key, cacheResult);
564                        return cacheResult;
565                    }
566                }
567            }
568        }
569
570        getLogger().debug("Did not find entry in cache for [{}, {}, {}]", profilesIds, object, key);
571        return null;
572    }
573
574    /**
575     * Add to cache
576     * @param profilesIds The profiles ids to consider
577     * @param object The context
578     * @param result The result
579     * @param key The kind of cache to use
580     */
581    protected void _putInSecondCache(Set<String> profilesIds, Object object, Object result, CacheKind key)
582    {
583        Map<Set<String>, Map<Object, Map<CacheKind, Object>>> mapCache = _rightManager.getCache(_cache2, true);
584        if (mapCache != null)
585        {
586            if (!mapCache.containsKey(profilesIds))
587            {
588                mapCache.put(profilesIds, new HashMap<>());
589            }
590            Map<Object, Map<CacheKind, Object>> mapAll = mapCache.get(profilesIds);
591            
592            if (!mapAll.containsKey(object))
593            {
594                mapAll.put(object, new HashMap<>());
595            }
596            Map<CacheKind, Object> mapResult = mapAll.get(object);
597            
598            mapResult.put(key, result);
599        }
600    }
601}