001/*
002 *  Copyright 2016 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.core.right;
017
018import java.util.ArrayList;
019import java.util.Collection;
020import java.util.Collections;
021import java.util.HashMap;
022import java.util.List;
023import java.util.Map;
024import java.util.Set;
025import java.util.stream.Collectors;
026
027import org.apache.avalon.framework.activity.Initializable;
028import org.apache.avalon.framework.service.ServiceException;
029import org.apache.avalon.framework.service.ServiceManager;
030import org.apache.commons.lang3.StringUtils;
031import org.apache.ibatis.session.SqlSession;
032
033import org.ametys.core.ObservationConstants;
034import org.ametys.core.cache.AbstractCacheManager;
035import org.ametys.core.cache.Cache;
036import org.ametys.core.datasource.AbstractMyBatisDAO;
037import org.ametys.core.observation.Event;
038import org.ametys.core.observation.ObservationManager;
039import org.ametys.core.user.CurrentUserProvider;
040import org.ametys.runtime.i18n.I18nizableText;
041
042/**
043 * Manages registration of profiles
044 */
045public class RightProfilesDAO extends AbstractMyBatisDAO implements Initializable
046{
047    /** The component role. */
048    public static final String ROLE = RightProfilesDAO.class.getName();
049
050    /** profile cache id */
051    private static final String PROFILES_CACHE = ROLE + "$profiles";
052
053    /** profile rights cache id */
054    private static final String PROFILE_RIGHTS_CACHE = ROLE + "$profileRigths";
055    
056    private ServiceManager _smanager;
057
058    private ObservationManager _observationManager;
059
060    private CurrentUserProvider _currentUserProvider;
061
062    private AbstractCacheManager _cacheManager;
063
064    
065    @Override
066    public void service(ServiceManager smanager) throws ServiceException
067    {
068        _smanager = smanager;
069        super.service(smanager);
070        _cacheManager = (AbstractCacheManager) smanager.lookup(AbstractCacheManager.ROLE);
071    }
072
073
074    public void initialize() throws Exception
075    {
076        _cacheManager.createMemoryCache(PROFILES_CACHE, 
077                new I18nizableText("plugin.core", "PLUGINS_CORE_RIGHT_PROFILES_CACHE_LABEL"),
078                new I18nizableText("plugin.core", "PLUGINS_CORE_RIGHT_PROFILES_CACHE_DESCRIPTION"),
079                true,
080                null);
081        _cacheManager.createMemoryCache(PROFILE_RIGHTS_CACHE, 
082                new I18nizableText("plugin.core", "PLUGINS_CORE_RIGHT_PROFILE_RIGHTS_CACHE_LABEL"),
083                new I18nizableText("plugin.core", "PLUGINS_CORE_RIGHT_PROFILE_RIGHTS_CACHE_DESCRIPTION"),
084                true,
085                null);
086    }
087    
088    /**
089     * Clear the profiles cache
090     */
091    public synchronized void clearCache()
092    {
093        _getProfilesCache().invalidateAll();
094        _getProfileRightsCache().invalidateAll();
095    }
096    
097    private synchronized Cache<String, Profile> _getProfilesCache()
098    {
099        Cache<String, Profile> cache = this._cacheManager.get(PROFILES_CACHE);
100
101        if (!cache.isInitialized())
102        {
103            try (SqlSession session = getSession())
104            {
105                Map<String, Profile> profilesMap = new HashMap<>();
106                List<Profile> profiles = session.selectList("Profiles.getProfiles");
107                profilesMap = profiles.stream().collect(Collectors.toMap(Profile::getId, p -> p));
108                cache.putAll(profilesMap);
109            }
110        }
111        
112        return cache;
113    }
114
115    private synchronized Cache<String, List<String>> _getProfileRightsCache()
116    {
117
118        Cache<String, List<String>> cache = this._cacheManager.get(PROFILE_RIGHTS_CACHE);
119
120        //As the cache can be cleared from admin tool, we add an entry to check is filled or not.
121        if (!cache.isInitialized())
122        {
123            try (SqlSession session = getSession())
124            {
125
126                Map<String, List<String>> profileRightsMap = new HashMap<>();
127                List<Map<String, String>> rightAssociations = session.selectList("Profiles.getProfileRights");
128                
129                profileRightsMap = rightAssociations.stream()
130                        .collect(Collectors.groupingBy(
131                            rightAssociation -> rightAssociation.get("profileId"),
132                            Collectors.mapping(
133                                rightAssociation -> rightAssociation.get("rightId"), 
134                                Collectors.toList())));
135                cache.putAll(profileRightsMap);
136            }
137        }
138        
139        return cache;
140    }
141    
142    /**
143     * Get all existing profiles
144     * @return The list for profiles
145     */
146    public List<Profile> getProfiles()
147    {
148        Collection<Profile> profiles = _getProfilesCache().asMap().values();
149        return new ArrayList<>(profiles);
150    }
151    
152    /**
153     * Get the profiles on a given context
154     * @param context The context. Can be null. If null, the profiles with no context are returned.
155     * @return The list for profiles for this context
156     */
157    public List<Profile> getProfiles(String context)
158    {
159        return _getProfilesCache().asMap().values().stream()
160                                      .filter(p -> StringUtils.equals(p.getContext(), context))
161                                      .collect(Collectors.toList());
162    }
163    
164    /**
165     * Get the profile with given identifier
166     * @param id The id of profile to retrieve
167     * @return The profile
168     */
169    public Profile getProfile(String id)
170    {
171        return _getProfilesCache().get(id);
172    }
173    
174    /**
175     * Get all profiles containing the right with given id
176     * @param rightId The id of right
177     * @return The id of profiles with this right
178     */
179    public Set<String> getProfilesWithRight (String rightId)
180    {
181        return _getProfileRightsCache().asMap().entrySet().stream()
182                .filter(e -> e.getValue().contains(rightId))
183                .map(e -> e.getKey())
184                .collect(Collectors.toSet());
185    }
186    
187    /**
188     * Creates a new profile with null context. The identifier of the profile will be automatically generated from label.
189     * @param label The label of profile
190     * @return The create profile
191     */
192    public Profile addProfile (String label)
193    {
194        return addProfile(label, null);
195    }
196    
197    /**
198     * Creates a new profile. The identifier of the profile will be automatically generated from label.
199     * @param label The label of profile
200     * @param context The context. Can be null
201     * @return The create profile
202     */
203    public Profile addProfile (String label, String context)
204    {
205        String id = _generateUniqueId(label);
206        Profile profile = new Profile(id, label, context);
207        addProfile(profile);
208        return profile;
209    }
210    
211    private String _generateUniqueId(String label)
212    {
213        // Id generated from name lowercased, trimmed, and spaces and underscores replaced by dashes
214        String value = label.toLowerCase().trim().replaceAll("[\\W_]", "-").replaceAll("-+", "-").replaceAll("^-", "");
215        int i = 2;
216        String suffixedValue = value;
217        while (getProfile(suffixedValue) != null)
218        {
219            suffixedValue = value + i;
220            i++;
221        }
222        
223        return suffixedValue;
224    }
225    
226    /**
227     * Creates a new profile
228     * @param id The unique identifier of profile
229     * @param label The label of profile
230     * @param context The context. Can be null
231     * @return The create profile
232     */
233    public Profile addProfile (String id, String label, String context)
234    {
235        Profile profile = new Profile(id, label, context);
236        addProfile(profile);
237        return profile;
238    }
239    
240    /**
241     * Add a new profile
242     * @param profile The profile to add
243     * @param silent Set to true to not notify observer of this update
244     */
245    public void addProfile (Profile profile, boolean silent)
246    {
247        try (SqlSession session = getSession(true))
248        {
249            session.insert("Profiles.addProfile", profile);
250            clearCache();
251            if (!silent)
252            {
253                _notifyEvent(profile, ObservationConstants.EVENT_PROFILE_ADDED);
254            }
255        }
256    }
257    
258    /**
259     * Add a new profile
260     * @param profile The profile to add
261     */
262    public void addProfile (Profile profile)
263    {
264        addProfile(profile, false);
265    }
266    
267    /**
268     * Rename a profile
269     * @param profile The profile to rename
270     * @param newLabel The updated label
271     */
272    public void renameProfile (Profile profile, String newLabel)
273    {
274        renameProfile(profile, newLabel, false);
275    }
276    
277    /**
278     * Rename a profile
279     * @param profile The profile to rename
280     * @param newLabel The updated label
281     * @param silent Set to true to not notify observer of this update
282     */
283    public void renameProfile (Profile profile, String newLabel, boolean silent)
284    {
285        try (SqlSession session = getSession(true))
286        {
287            Map<String, Object> params = new HashMap<>();
288            params.put("id", profile.getId());
289            params.put("label", newLabel);
290            session.update("Profiles.renameProfile", params);
291            
292            clearCache();
293            
294            if (!silent)
295            {
296                _notifyEvent(profile, ObservationConstants.EVENT_PROFILE_UPDATED);
297            }
298        }
299    }
300
301    
302    /**
303     * Get the rights of a profile
304     * @param profileId The profile id
305     * @return The rights
306     */
307    public List<String> getRights (String profileId)
308    {
309        if (!StringUtils.isEmpty(profileId))
310        {
311            List<String> list = _getProfileRightsCache().get(profileId);
312            if (list != null)
313            {
314                return list;
315            }
316        }
317        return Collections.EMPTY_LIST;
318    }
319
320    /**
321     * Get the rights of a profile
322     * @param profile The profile
323     * @return The rights
324     */
325    public List<String> getRights (Profile profile)
326    {
327        if (profile == null)
328        {
329            return Collections.EMPTY_LIST;
330        }
331        else
332        {
333            return getRights(profile.getId());
334        }
335    }
336    
337    /**
338     * Add a right to a profile
339     * @param profile The profile
340     * @param rightId The id of right to add
341     */
342    public void addRight (Profile profile, String rightId)
343    {
344        try (SqlSession session = getSession(true))
345        {
346            _addRight (session, profile, rightId);
347        }
348        
349        clearCache();
350    }
351    
352    /**
353     * Add a right to a profile
354     * @param profile The profile
355     * @param rightIds The id of rights to add
356     */
357    public void addRights (Profile profile, List<String> rightIds)
358    {
359        try (SqlSession session = getSession())
360        {
361            for (String rightId : rightIds)
362            {
363                _addRight (session, profile, rightId);
364            }
365            
366            session.commit();
367        }
368        
369        clearCache();
370    }
371    
372    /**
373     * Update the rights of a profile
374     * @param profile The profile
375     * @param rights The rights of the profile
376     */
377    public void updateRights (Profile profile, List<String> rights)
378    {
379        updateRights(profile, rights, false);
380    }
381    
382    /**
383     * Update the rights of a profile
384     * @param profile The profile
385     * @param rights The rights of the profile
386     * @param silent Set to true to not notify observer of this update
387     */
388    public void updateRights (Profile profile, List<String> rights, boolean silent)
389    {
390        try (SqlSession session = getSession())
391        {
392            session.delete("Profiles.deleteProfileRights", profile.getId());
393            
394            if (rights != null)
395            {
396                for (String rightId : rights)
397                {
398                    _addRight (session, profile, rightId);
399                }
400            }
401            
402            session.commit();
403            
404            clearCache();
405            
406            if (!silent)
407            {
408                _notifyEvent(profile, ObservationConstants.EVENT_PROFILE_UPDATED);
409            }
410        }
411    }
412    
413    private void _addRight (SqlSession session, Profile profile, String rightId)
414    {
415        Map<String, Object> params = new HashMap<>();
416        params.put("profileId", profile.getId());
417        params.put("rightId", rightId);
418        
419        session.insert("Profiles.addRight", params);
420    }
421    
422    /**
423     * Add a right to a profile
424     * @param profile The profile
425     */
426    public void removeRights (Profile profile)
427    {
428        removeRights(profile, false);
429    }
430    
431    /**
432     * Add a right to a profile
433     * @param profile The profile
434     * @param silent Set to true to not notify observer of this update
435     */
436    public void removeRights (Profile profile, boolean silent)
437    {
438        try (SqlSession session = getSession(true))
439        {
440            session.delete("Profiles.deleteProfileRights", profile.getId());
441            
442            clearCache();
443            
444            if (!silent)
445            {
446                _notifyEvent(profile, ObservationConstants.EVENT_PROFILE_UPDATED);
447            }
448        }
449    }
450    
451    /**
452     * Remove a right from a profile
453     * @param profile The profile
454     * @param rightId The id of right to add
455     */
456    public void removeRight (Profile profile, String rightId)
457    {
458        removeRight(profile, rightId, false);
459    }
460
461    /**
462     * Remove a right from a profile
463     * @param profile The profile
464     * @param rightId The id of right to add
465     * @param silent Set to true to not notify observer of this update
466     */
467    public void removeRight (Profile profile, String rightId, boolean silent)
468    {
469        try (SqlSession session = getSession(true))
470        {
471            Map<String, Object> params = new HashMap<>();
472            params.put("profileId", profile.getId());
473            params.put("rightId", rightId);
474            session.delete("Profiles.deleteProfileRight", params);
475            
476            clearCache();
477            
478            if (!silent)
479            {
480                _notifyEvent(profile, ObservationConstants.EVENT_PROFILE_UPDATED);
481            }
482        }
483        
484        clearCache();
485    }
486    
487    /**
488     * Delete a profile
489     * @param profile The profile to delete
490     */
491    public void deleteProfile (Profile profile)
492    {
493        deleteProfile(profile, false);
494    }
495    
496    /**
497     * Delete a profile
498     * @param profile The profile to delete
499     * @param silent Set to true to not notify observer of this update
500     */
501    public void deleteProfile (Profile profile, boolean silent)
502    {
503        try (SqlSession session = getSession())
504        {
505            session.delete("Profiles.deleteProfile", profile.getId());
506            session.delete("Profiles.deleteProfileRights", profile.getId());
507            
508            session.commit();
509            
510            clearCache();
511            
512            if (!silent)
513            {
514                _notifyEvent(profile, ObservationConstants.EVENT_PROFILE_DELETED);
515            }
516        }
517    }
518    
519    private void _notifyEvent (Profile profile, String eventId)
520    {
521        try
522        {
523            if (_observationManager == null)
524            {
525                _observationManager = (ObservationManager) _smanager.lookup(ObservationManager.ROLE);
526            }
527            if (_currentUserProvider == null)
528            {
529                _currentUserProvider = (CurrentUserProvider) _smanager.lookup(CurrentUserProvider.ROLE);
530            }
531            
532            Map<String, Object> eventParams = new HashMap<>();
533            eventParams.put(ObservationConstants.ARGS_PROFILE, profile);
534            _observationManager.notify(new Event(eventId, _currentUserProvider.getUser(), eventParams));
535        }
536        catch (ServiceException e)
537        {
538            getLogger().error("Fail to notify observers for event '" + eventId + "'", e);
539        }
540    }
541}