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.messagingconnector;
017
018import java.util.ArrayList;
019import java.util.Date;
020import java.util.List;
021import java.util.Map;
022import java.util.Objects;
023import java.util.Set;
024import java.util.concurrent.TimeUnit;
025
026import org.ametys.core.user.UserIdentity;
027import org.ametys.core.user.population.UserPopulationDAO;
028import org.ametys.plugins.explorer.calendars.EventRecurrenceTypeEnum;
029import org.ametys.runtime.config.Config;
030import org.ametys.runtime.plugin.component.AbstractLogEnabled;
031import org.apache.avalon.framework.activity.Initializable;
032import org.apache.avalon.framework.service.ServiceException;
033import org.apache.avalon.framework.service.ServiceManager;
034import org.apache.avalon.framework.service.Serviceable;
035import org.apache.commons.lang3.StringUtils;
036
037import com.google.common.cache.Cache;
038import com.google.common.cache.CacheBuilder;
039
040/**
041 * Abstract implementation of {@link MessagingConnector} with cache.
042 *
043 */
044public abstract class AbstractMessagingConnector extends AbstractLogEnabled implements MessagingConnector, Initializable, Serviceable
045{
046    /** The user population DAO */
047    protected UserPopulationDAO _userPopulationDAO;
048
049    private Cache<EventCacheKey, List<CalendarEvent>> _eventsCache;
050    private Cache<EventCountCacheKey, Integer> _eventsCountCache;
051    private Cache<EmailCacheKey, List<EmailMessage>> _emailsCache;
052    private Cache<UserIdentity, Integer> _emailsCountCache;
053    private List<String> _populationIds;
054    
055    
056    public void service(ServiceManager manager) throws ServiceException
057    {
058        _userPopulationDAO = (UserPopulationDAO) manager.lookup(UserPopulationDAO.ROLE);
059    }
060    
061    @Override
062    public void initialize()
063    {
064        _populationIds = new ArrayList<>();
065        
066        String populationIdsAsString = Config.getInstance().getValueAsString("org.ametys.plugins.messagingconnector.population");
067        if (StringUtils.isNotBlank(populationIdsAsString))
068        {
069            List<String> userPopulationsIds = _userPopulationDAO.getUserPopulationsIds();
070            String[] populationIds = StringUtils.split(populationIdsAsString, ",");
071            
072            List<String> wrongPopulationIds = new ArrayList<>();
073            for (String populationId : populationIds)
074            {
075                String populationIdTrimed = StringUtils.trim(populationId);
076                if (!userPopulationsIds.contains(populationIdTrimed))
077                {
078                    wrongPopulationIds.add(populationIdTrimed);
079                }
080                else
081                {
082                    _populationIds.add(populationIdTrimed);
083                }
084            }
085            
086            if (!wrongPopulationIds.isEmpty())
087            {
088                throw new IllegalStateException("The following population ids defined in the configuration parameter 'population id' for the messaging connector do not exist : " + wrongPopulationIds);
089            }
090        }
091        
092        Long maxCacheSizeConf = Config.getInstance().getValueAsLong("org.ametys.plugins.messagingconnector.cache.maxsize");
093        Long maxCacheSize = (long) (maxCacheSizeConf != null ? maxCacheSizeConf.intValue() : 1000);
094        
095        Long cacheTtlConf = Config.getInstance().getValueAsLong("org.ametys.plugins.messagingconnector.cache.ttl");
096        Long cacheTtl = (long) (cacheTtlConf != null && cacheTtlConf.intValue() > 0 ? cacheTtlConf.intValue() : 60);
097        
098        CacheBuilder<Object, Object> cacheBuilder = CacheBuilder.newBuilder().expireAfterWrite(cacheTtl, TimeUnit.MINUTES);
099        
100        if (maxCacheSize > 0)
101        {
102            cacheBuilder.maximumSize(maxCacheSize);
103        }
104        
105        _eventsCache = cacheBuilder.<EventCacheKey, List<CalendarEvent>>build();
106        _eventsCountCache = cacheBuilder.<EventCountCacheKey, Integer>build(); 
107        _emailsCache = cacheBuilder.<EmailCacheKey, List<EmailMessage>>build();
108        _emailsCountCache = cacheBuilder.<UserIdentity, Integer>build();
109    }
110    
111    @Override
112    public List<String> getAllowedPopulationIds()
113    {
114        if (_populationIds.isEmpty())
115        {
116            List<String> userPopulationsIds = _userPopulationDAO.getUserPopulationsIds();
117            if (userPopulationsIds.size() == 1)
118            {
119                return userPopulationsIds;
120            }
121            
122            throw new IllegalStateException("There is more than one population defined. You must set the configuration parameter 'population id' for the messaging connector");
123        }
124        else
125        {
126            return _populationIds;
127        }
128    }
129    
130    /**
131     * True if the user is allowed
132     * @param userIdentity the user identity
133     * @return true if the user is allowed
134     */
135    protected boolean isAllowed(UserIdentity userIdentity)
136    {
137        if (userIdentity == null)
138        {
139            getLogger().warn("There is no connected user to get user's mails or events from messaging connector");
140            return false;
141        }
142        
143        List<String> allowedPopulations = getAllowedPopulationIds();
144        if (!allowedPopulations.contains(userIdentity.getPopulationId()))
145        {
146            getLogger().warn("The user " + userIdentity + " does not belong to any authorized user populations for messaging connector " + allowedPopulations);
147            return false;
148        }
149        
150        return true;
151    }
152    
153    @Override
154    public List<CalendarEvent> getEvents(UserIdentity userIdentity, Date fromDate, Date untilDate, int maxEvents) throws MessagingConnectorException
155    {
156        if (!isAllowed(userIdentity))
157        {
158            return new ArrayList<>();
159        }
160        
161        EventCacheKey eventCacheKey = new EventCacheKey(userIdentity, fromDate, untilDate, maxEvents);
162        List<CalendarEvent> events = _eventsCache.getIfPresent(eventCacheKey);
163        if (events == null)
164        {
165            events = internalGetEvents(userIdentity, fromDate, untilDate, maxEvents);
166            _eventsCache.put(eventCacheKey, events);
167        }
168        return events;
169    }
170    
171    @Override
172    public int getEventsCount(UserIdentity userIdentity, Date fromDate, Date untilDate) throws MessagingConnectorException
173    {
174        if (!isAllowed(userIdentity))
175        {
176            return 0;
177        }
178        
179        EventCountCacheKey eventCountCacheKey = new EventCountCacheKey(userIdentity, fromDate, untilDate);
180        Integer eventsCount = _eventsCountCache.getIfPresent(eventCountCacheKey);
181        if (eventsCount == null)
182        {
183            eventsCount = internalGetEventsCount(userIdentity, fromDate, untilDate);
184            _eventsCountCache.put(eventCountCacheKey, eventsCount);
185        }
186        return eventsCount;
187    }
188
189    @Override
190    public List<EmailMessage> getUnreadEmails(UserIdentity userIdentity, int maxEmails) throws MessagingConnectorException
191    {
192        if (!isAllowed(userIdentity))
193        {
194            return new ArrayList<>();
195        }
196        
197        EmailCacheKey emailCacheKey = new EmailCacheKey(userIdentity, maxEmails);
198        List<EmailMessage> emails = _emailsCache.getIfPresent(emailCacheKey);
199        if (emails == null)
200        {
201            emails = internalGetEmails(userIdentity, maxEmails);
202            _emailsCache.put(emailCacheKey, emails);
203        }
204        return emails;
205    }
206
207    @Override
208    public int getUnreadEmailCount(UserIdentity userIdentity) throws MessagingConnectorException
209    {
210        if (!isAllowed(userIdentity))
211        {
212            return 0;
213        }
214        
215        Integer emailsCount = _emailsCountCache.getIfPresent(userIdentity);
216        if (emailsCount == null)
217        {
218            emailsCount = internalGetEmailsCount(userIdentity);
219            _emailsCountCache.put(userIdentity, emailsCount);
220        }
221        return emailsCount;
222    }
223    
224    /**
225     * Get events between two date (no caching)
226     * @param userIdentity The user identity
227     * @param fromDate The date to start search
228     * @param untilDate The date to end search
229     * @param maxEvents The maximum number of events to retrieve
230     * @return The calendar events
231     * @throws MessagingConnectorException if failed to get events from server
232     */
233    protected abstract List<CalendarEvent> internalGetEvents(UserIdentity userIdentity, Date fromDate, Date untilDate, int maxEvents) throws MessagingConnectorException;
234    
235    /**
236     * Get events count between two date (no caching)
237     * @param userIdentity The user identity
238     * @param fromDate The date to start search
239     * @param untilDate The date to end search
240     * @return The number of calendar events
241     * @throws MessagingConnectorException if failed to get events from server
242     */
243    protected abstract int internalGetEventsCount(UserIdentity userIdentity, Date fromDate, Date untilDate) throws MessagingConnectorException;
244    
245    /**
246     * Get emails (no caching)
247     * @param userIdentity The user identity
248     * @param maxEmails The maximum number of emails to retrieve
249     * @return The emails
250     * @throws MessagingConnectorException if failed to get events from server
251     */
252    protected abstract List<EmailMessage> internalGetEmails(UserIdentity userIdentity, int maxEmails) throws MessagingConnectorException;
253    
254    /**
255     * Get emails count (no caching)
256     * @param userIdentity The user identity
257     * @return The emails count
258     * @throws MessagingConnectorException if failed to get events from server
259     */
260    protected abstract int internalGetEmailsCount(UserIdentity userIdentity) throws MessagingConnectorException;
261    
262    @Override
263    public boolean supportInvitation() throws MessagingConnectorException
264    {
265        return false;
266    }
267
268    @Override
269    public boolean isEventExist(String eventId, UserIdentity organiser) throws MessagingConnectorException
270    {
271        throw new UnsupportedOperationException("Invitation is not implemented for messaging connector");
272    }
273    
274    @Override
275    public String createEvent(String title, String description, String place, boolean isAllDay, Date startDate, Date endDate, EventRecurrenceTypeEnum recurrenceType, Date untilDate, Map<String, Boolean> attendees, UserIdentity organiser) throws MessagingConnectorException
276    {
277        throw new UnsupportedOperationException("Invitation is not implemented for messaging connector");
278    }
279
280    @Override
281    public void updateEvent(String eventId, String title, String description, String place, boolean isAllDay, Date startDate, Date endDate, EventRecurrenceTypeEnum recurrenceType, Date untilDate, Map<String, Boolean> attendees, UserIdentity organiser) throws MessagingConnectorException
282    {
283        throw new UnsupportedOperationException("Invitation is not implemented for messaging connector");
284    }
285
286    @Override
287    public void deleteEvent(String eventId, UserIdentity organiser) throws MessagingConnectorException
288    {
289        throw new UnsupportedOperationException("Invitation is not implemented for messaging connector");
290    }
291
292    @Override
293    public Map<String, AttendeeInformation> getAttendees(String eventId, UserIdentity organiser) throws MessagingConnectorException
294    {
295        throw new UnsupportedOperationException("Invitation is not implemented for messaging connector");
296    }
297
298    @Override
299    public void setAttendees(String eventId, Map<String, Boolean> attendees, UserIdentity organiser) throws MessagingConnectorException
300    {
301        throw new UnsupportedOperationException("Invitation is not implemented for messaging connector");
302    }
303
304    @Override
305    public Map<String, FreeBusyStatus> getFreeBusy(Date startDate, Date endDate, boolean isAllDay, Set<String> attendees, UserIdentity organiser) throws MessagingConnectorException
306    {
307        throw new UnsupportedOperationException("Invitation is not implemented for messaging connector");
308    }
309    
310    @Override
311    public boolean isUserExist(UserIdentity userIdentity) throws MessagingConnectorException
312    {
313        throw new UnsupportedOperationException("Invitation is not implemented for messaging connector");
314    }
315    
316    /**
317     * Internal class for key of events cache
318     *
319     */
320    class EventCacheKey
321    {
322        private UserIdentity _userIdentity;
323        private Date _fromDate;
324        private Date _untilDate;
325        private int _maxEvents;
326
327        public EventCacheKey (UserIdentity userIdentity, Date fromDate, Date untilDate, int maxEvents)
328        {
329            _userIdentity = userIdentity;
330            _fromDate = fromDate;
331            _untilDate = untilDate;
332            _maxEvents = maxEvents;
333        }
334        
335        UserIdentity getUserIdentity()
336        {
337            return _userIdentity;
338        }
339        
340        Date getFromDate()
341        {
342            return _fromDate;
343        }
344        
345        Date getUntilDate()
346        {
347            return _untilDate;
348        }
349        
350        int getMaxEvents()
351        {
352            return _maxEvents;
353        }
354        
355        @Override
356        public int hashCode()
357        {
358            return Objects.hash(_userIdentity, _fromDate, _untilDate, new Integer(_maxEvents));
359        }
360        
361        @Override
362        public boolean equals(Object obj)
363        {
364            if (obj == null)
365            {
366                return false;
367            }
368            
369            if (!(obj instanceof EventCacheKey))
370            {
371                return false;
372            }
373            
374            EventCacheKey toCompare = (EventCacheKey) obj;
375            
376            return _userIdentity.equals(toCompare.getUserIdentity()) && _fromDate.equals(toCompare.getFromDate()) && _untilDate.equals(toCompare.getUntilDate()) && _maxEvents == toCompare.getMaxEvents();
377        }
378    }
379    
380    /**
381     * Internal class for key of events count cache
382     *
383     */
384    class EventCountCacheKey
385    {
386        private UserIdentity _userIdentity;
387        private Date _fromDate;
388        private Date _untilDate;
389
390        public EventCountCacheKey (UserIdentity userIdentity, Date fromDate, Date untilDate)
391        {
392            _userIdentity = userIdentity;
393            _fromDate = fromDate;
394            _untilDate = untilDate;
395        }
396        
397        UserIdentity getUserIdentity()
398        {
399            return _userIdentity;
400        }
401        
402        Date getFromDate()
403        {
404            return _fromDate;
405        }
406        
407        Date getUntilDate()
408        {
409            return _untilDate;
410        }
411        
412        @Override
413        public int hashCode()
414        {
415            return Objects.hash(_userIdentity, _fromDate, _untilDate);
416        }
417        
418        @Override
419        public boolean equals(Object obj)
420        {
421            if (obj == null)
422            {
423                return false;
424            }
425            
426            if (!(obj instanceof EventCacheKey))
427            {
428                return false;
429            }
430            
431            EventCountCacheKey toCompare = (EventCountCacheKey) obj;
432            
433            return _userIdentity.equals(toCompare.getUserIdentity()) && _fromDate.equals(toCompare.getFromDate()) && _untilDate.equals(toCompare.getUntilDate());
434        }
435    }
436    
437    /**
438     * Internal class for key of events count cache
439     *
440     */
441    class EmailCacheKey
442    {
443        private UserIdentity _userIdentity;
444        private int _maxEmails;
445
446        public EmailCacheKey (UserIdentity userIdentity, int maxEmails)
447        {
448            _userIdentity = userIdentity;
449            _maxEmails = maxEmails;
450        }
451        
452        UserIdentity getUserIdentity()
453        {
454            return _userIdentity;
455        }
456
457        int getMaxEmails()
458        {
459            return _maxEmails;
460        }
461        
462        @Override
463        public int hashCode()
464        {
465            return Objects.hash(_userIdentity, _maxEmails);
466        }
467        
468        @Override
469        public boolean equals(Object obj)
470        {
471            if (obj == null)
472            {
473                return false;
474            }
475            
476            if (!(obj instanceof EventCacheKey))
477            {
478                return false;
479            }
480            
481            EmailCacheKey toCompare = (EmailCacheKey) obj;
482            
483            return _userIdentity.equals(toCompare.getUserIdentity()) && _maxEmails == toCompare.getMaxEmails();
484        }
485    }
486
487}