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