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