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.time.Duration;
019import java.util.ArrayList;
020import java.util.Collections;
021import java.util.Date;
022import java.util.HashSet;
023import java.util.List;
024import java.util.Map;
025import java.util.Map.Entry;
026import java.util.Objects;
027import java.util.Set;
028
029import org.apache.avalon.framework.activity.Initializable;
030import org.apache.avalon.framework.service.ServiceException;
031import org.apache.avalon.framework.service.ServiceManager;
032import org.apache.avalon.framework.service.Serviceable;
033import org.apache.commons.lang3.StringUtils;
034
035import org.ametys.core.cache.AbstractCacheManager;
036import org.ametys.core.cache.Cache;
037import org.ametys.core.user.CurrentUserProvider;
038import org.ametys.core.user.UserIdentity;
039import org.ametys.core.user.population.UserPopulationDAO;
040import org.ametys.core.userpref.UserPreferencesException;
041import org.ametys.core.userpref.UserPreferencesManager;
042import org.ametys.plugins.explorer.calendars.EventRecurrenceTypeEnum;
043import org.ametys.plugins.messagingconnector.MessagingConnectorException.ExceptionType;
044import org.ametys.runtime.config.Config;
045import org.ametys.runtime.i18n.I18nizableText;
046import org.ametys.runtime.plugin.component.AbstractLogEnabled;
047
048
049/**
050 * Abstract implementation of {@link MessagingConnector} with cache.
051 *
052 */
053public abstract class AbstractMessagingConnector extends AbstractLogEnabled implements MessagingConnector, Initializable, Serviceable
054{
055    /** Duration of the cache for timeout errors, no calls will be done again for one user */
056    private static final int TIMEOUT_CACHE_DURATION_SECONDS = 30;
057
058    /** token cache id */
059    private static final String EVENTS_CACHE = AbstractMessagingConnector.class.getName() + "$eventsCache";
060    
061    /** token cache id */
062    private static final String EVENTS_COUNT_CACHE = AbstractMessagingConnector.class.getName() + "$eventsCountCache";
063    
064    /** token cache id */
065    private static final String EMAILS_CACHE = AbstractMessagingConnector.class.getName() + "$emailsCache";
066    
067    /** token cache id */
068    private static final String EMAILS_COUNT_CACHE = AbstractMessagingConnector.class.getName() + "$emailsCountCache";
069    
070    /** token cache id */
071    private static final String ERROR_CACHE = AbstractMessagingConnector.class.getName() + "$errorCache";
072    
073    /** token cache id */
074    private static final String TIMEOUT_ERROR_CACHE = AbstractMessagingConnector.class.getName() + "$timeoutErrorCache";
075    
076    /** The user population DAO */
077    protected UserPopulationDAO _userPopulationDAO;
078    /** The user preferences */
079    protected UserPreferencesManager _userPref;
080    /** The crypto helper */
081    protected CryptoHelper _cryptoHelper;
082    /** The current user provider */
083    protected CurrentUserProvider _currentUserProvider;
084    
085    /** CacheManager used to create and get cache */
086    protected AbstractCacheManager _cacheManager;
087    
088    private List<String> _populationIds;
089
090    public void service(ServiceManager manager) throws ServiceException
091    {
092        _userPopulationDAO = (UserPopulationDAO) manager.lookup(UserPopulationDAO.ROLE);
093        _userPref = (UserPreferencesManager) manager.lookup(UserPreferencesManager.ROLE);
094        _cryptoHelper = (CryptoHelper) manager.lookup(CryptoHelper.ROLE);
095        _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE);
096        _cacheManager = (AbstractCacheManager) manager.lookup(AbstractCacheManager.ROLE);
097    }
098    
099    @Override
100    public void initialize()
101    {
102        _populationIds = new ArrayList<>();
103        
104        String populationIdsAsString = Config.getInstance().getValue("org.ametys.plugins.messagingconnector.population");
105        if (StringUtils.isNotBlank(populationIdsAsString))
106        {
107            List<String> userPopulationsIds = _userPopulationDAO.getUserPopulationsIds();
108            String[] populationIds = StringUtils.split(populationIdsAsString, ",");
109            
110            List<String> wrongPopulationIds = new ArrayList<>();
111            for (String populationId : populationIds)
112            {
113                String populationIdTrimed = StringUtils.trim(populationId);
114                if (!userPopulationsIds.contains(populationIdTrimed))
115                {
116                    wrongPopulationIds.add(populationIdTrimed);
117                }
118                else
119                {
120                    _populationIds.add(populationIdTrimed);
121                }
122            }
123            
124            if (!wrongPopulationIds.isEmpty())
125            {
126                throw new IllegalStateException("The following population ids defined in the configuration parameter 'population id' for the messaging connector do not exist : " + wrongPopulationIds);
127            }
128        }
129        
130        Long cacheTtlConf = Config.getInstance().getValue("org.ametys.plugins.messagingconnector.cache.ttl");
131        Long cacheTtl = (long) (cacheTtlConf != null && cacheTtlConf.intValue() > 0 ? cacheTtlConf.intValue() : 60);
132        
133        _cacheManager.createMemoryCache(EVENTS_CACHE, 
134                new I18nizableText("plugin.messaging-connector", "PLUGINS_MESSAGINGCONNECTOR_EVENTS_CACHE_LABEL"),
135                new I18nizableText("plugin.messaging-connector", "PLUGINS_MESSAGINGCONNECTOR_EVENTS_CACHE_DESCRIPTION"),
136                true,
137                Duration.ofMinutes(cacheTtl));
138        _cacheManager.createMemoryCache(EVENTS_COUNT_CACHE, 
139                new I18nizableText("plugin.messaging-connector", "PLUGINS_MESSAGINGCONNECTOR_EVENTS_COUNT_CACHE_LABEL"),
140                new I18nizableText("plugin.messaging-connector", "PLUGINS_MESSAGINGCONNECTOR_EVENTS_COUNT_CACHE_DESCRIPTION"),
141                true,
142                Duration.ofMinutes(cacheTtl));
143        _cacheManager.createMemoryCache(EMAILS_CACHE, 
144                new I18nizableText("plugin.messaging-connector", "PLUGINS_MESSAGINGCONNECTOR_EMAILS_CACHE_LABEL"),
145                new I18nizableText("plugin.messaging-connector", "PLUGINS_MESSAGINGCONNECTOR_EMAILS_CACHE_DESCRIPTION"),
146                true,
147                Duration.ofMinutes(cacheTtl));
148        _cacheManager.createMemoryCache(EMAILS_COUNT_CACHE, 
149                new I18nizableText("plugin.messaging-connector", "PLUGINS_MESSAGINGCONNECTOR_EMAILS_COUNT_CACHE_LABEL"),
150                new I18nizableText("plugin.messaging-connector", "PLUGINS_MESSAGINGCONNECTOR_EMAILS_COUNT_CACHE_DESCRIPTION"),
151                true,
152                Duration.ofMinutes(cacheTtl));
153        _cacheManager.createMemoryCache(ERROR_CACHE, 
154                new I18nizableText("plugin.messaging-connector", "PLUGINS_MESSAGINGCONNECTOR_ERROR_CACHE_LABEL"),
155                new I18nizableText("plugin.messaging-connector", "PLUGINS_MESSAGINGCONNECTOR_ERROR_CACHE_DESCRIPTION"),
156                false,
157                Duration.ofSeconds(TIMEOUT_CACHE_DURATION_SECONDS));
158        _cacheManager.createMemoryCache(TIMEOUT_ERROR_CACHE, 
159                new I18nizableText("plugin.messaging-connector", "PLUGINS_MESSAGINGCONNECTOR_TIMEOUT_ERROR_CACHE_LABEL"),
160                new I18nizableText("plugin.messaging-connector", "PLUGINS_MESSAGINGCONNECTOR_TIMEOUT_ERROR_CACHE_DESCRIPTION"),
161                true,
162                Duration.ofSeconds(TIMEOUT_CACHE_DURATION_SECONDS));
163        
164    }
165    
166    @Override
167    public List<String> getAllowedPopulationIds()
168    {
169        if (_populationIds.isEmpty())
170        {
171            List<String> userPopulationsIds = _userPopulationDAO.getUserPopulationsIds();
172            if (userPopulationsIds.size() == 1)
173            {
174                return userPopulationsIds;
175            }
176            
177            throw new IllegalStateException("There is more than one population defined. You must set the configuration parameter 'population id' for the messaging connector");
178        }
179        else
180        {
181            return _populationIds;
182        }
183    }
184    
185    /**
186     * True if the user is allowed
187     * @param userIdentity the user identity
188     * @return true if the user is allowed
189     */
190    protected boolean isAllowed(UserIdentity userIdentity)
191    {
192        if (userIdentity == null)
193        {
194            getLogger().warn("There is no connected user to get user's mails or events from messaging connector");
195            return false;
196        }
197        
198        List<String> allowedPopulations = getAllowedPopulationIds();
199        if (!allowedPopulations.contains(userIdentity.getPopulationId()))
200        {
201            getLogger().warn("The user " + userIdentity + " does not belong to any authorized user populations for messaging connector " + allowedPopulations);
202            return false;
203        }
204        
205        return true;
206    }
207    
208    @Override
209    public List<CalendarEvent> getEvents(UserIdentity userIdentity, int maxDays, int maxEvents) throws MessagingConnectorException
210    {
211        if (!isAllowed(userIdentity))
212        {
213            return new ArrayList<>();
214        }
215
216        // Check if one of the last calls returned an exception and throw it directly if needed
217        _throwMessagingConnectorExceptionIfInCache(userIdentity);
218        
219        try
220        {
221            EventCacheKey eventCacheKey = new EventCacheKey(userIdentity, maxDays, maxEvents);
222            return _getEventsCache().get(eventCacheKey, key -> internalGetEvents(userIdentity, maxDays, maxEvents));
223        }
224        catch (MessagingConnectorException e)
225        {
226            // Save the exception in cache to avoid to call the server again
227            _putExceptionInCache(userIdentity, e.getType());
228            throw e;
229        }
230    }
231    
232    @Override
233    public int getEventsCount(UserIdentity userIdentity, int maxDays) throws MessagingConnectorException
234    {
235        if (!isAllowed(userIdentity))
236        {
237            return 0;
238        }
239        
240        // Check if one of the last calls returned an exception and throw it directly if needed
241        _throwMessagingConnectorExceptionIfInCache(userIdentity);
242        
243        try
244        {
245            EventCountCacheKey eventCountCacheKey = new EventCountCacheKey(userIdentity, maxDays);
246            return _getEventsCountCache().get(eventCountCacheKey, key -> internalGetEventsCount(userIdentity, maxDays));
247        }
248        catch (MessagingConnectorException e)
249        {
250            // Save the exception in cache to avoid to call the server again
251            _putExceptionInCache(userIdentity, e.getType());
252            throw e;
253        }
254    }
255
256    @Override
257    public List<EmailMessage> getUnreadEmails(UserIdentity userIdentity, int maxEmails) throws MessagingConnectorException
258    {
259        if (!isAllowed(userIdentity))
260        {
261            return new ArrayList<>();
262        }
263        
264        // Check if one of the last calls returned an exception and throw it directly if needed
265        _throwMessagingConnectorExceptionIfInCache(userIdentity);
266        
267        try
268        {
269            EmailCacheKey emailCacheKey = new EmailCacheKey(userIdentity, maxEmails);
270            return _getEmailsCache().get(emailCacheKey, key -> internalGetEmails(userIdentity, maxEmails));
271        }
272        catch (MessagingConnectorException e)
273        {
274            // Save the exception in cache to avoid to call the server again
275            _putExceptionInCache(userIdentity, e.getType());
276            throw e;
277        }
278    }
279
280    @Override
281    public int getUnreadEmailCount(UserIdentity userIdentity) throws MessagingConnectorException
282    {
283        if (!isAllowed(userIdentity))
284        {
285            return 0;
286        }
287        
288        // Check if one of the last calls returned an exception and throw it directly if needed
289        _throwMessagingConnectorExceptionIfInCache(userIdentity);
290        
291        try
292        {
293            return _getEmailsCountCache().get(userIdentity, key -> internalGetEmailsCount(userIdentity));
294        }
295        catch (MessagingConnectorException e)
296        {
297            // Save the exception in cache to avoid to call the server again
298            _putExceptionInCache(userIdentity, e.getType());
299            throw e;
300        }
301    }
302    
303    /**
304     * Get upcoming events (no caching)
305     * @param userIdentity The user identity
306     * @param maxDays The maximum number of days to search for
307     * @param maxEvents The maximum number of events to retrieve
308     * @return The calendar events
309     * @throws MessagingConnectorException if failed to get events from server
310     */
311    protected abstract List<CalendarEvent> internalGetEvents(UserIdentity userIdentity, int maxDays, int maxEvents) throws MessagingConnectorException;
312    
313    /**
314     * Get upcoming events count (no caching)
315     * @param userIdentity The user identity
316     * @param maxDays The maximum number of days to search for
317     * @return The number of calendar events
318     * @throws MessagingConnectorException if failed to get events from server
319     */
320    protected abstract int internalGetEventsCount(UserIdentity userIdentity, int maxDays) throws MessagingConnectorException;
321    
322    /**
323     * Get emails (no caching)
324     * @param userIdentity The user identity
325     * @param maxEmails The maximum number of emails to retrieve
326     * @return The emails
327     * @throws MessagingConnectorException if failed to get events from server
328     */
329    protected abstract List<EmailMessage> internalGetEmails(UserIdentity userIdentity, int maxEmails) throws MessagingConnectorException;
330    
331    /**
332     * Get the user password for the messaging connector
333     * @param userIdentity user to check
334     * @return the decrypted user password
335     * @throws UserPreferencesException error while reading user preferences
336     */
337    protected String getUserPassword(UserIdentity userIdentity) throws UserPreferencesException
338    {
339        if (supportUserCredential())
340        {
341            String encryptedValue = getUserCryptedPassword(userIdentity);
342            return _cryptoHelper.decrypt(encryptedValue);
343        }
344        else
345        {
346            throw new MessagingConnectorException("Cannot get password for user " + userIdentity + ": user credential are not supported by this messaging connector", MessagingConnectorException.ExceptionType.CONFIGURATION_EXCEPTION);
347        }
348    }
349    
350    /**
351     * Get the user password, still crypted
352     * @param userIdentity user to check
353     * @return the still crypted user password
354     * @throws UserPreferencesException error while reading user preferences
355     */
356    protected String getUserCryptedPassword(UserIdentity userIdentity) throws UserPreferencesException
357    {
358        return _userPref.getUserPreferenceAsString(userIdentity, "/messaging-connector", Collections.emptyMap(), "messaging-connector-password");
359    }
360    
361    @Override
362    public void setUserPassword(UserIdentity userIdentity, String password) throws UserPreferencesException, MessagingConnectorException
363    {
364        if (supportUserCredential())
365        {
366            String cryptedPassword = _cryptoHelper.encrypt(password);
367            _userPref.addUserPreference(userIdentity, "/messaging-connector", Collections.emptyMap(), "messaging-connector-password", cryptedPassword);
368            // Unauthorized cache is invalidated for this user
369            _invalidateExceptionForUserInCache(userIdentity, ExceptionType.UNAUTHORIZED);
370        }
371        else
372        {
373            throw new MessagingConnectorException("Cannot set password for user " + userIdentity + ": user credential are not supported by this messaging connector", MessagingConnectorException.ExceptionType.CONFIGURATION_EXCEPTION);
374        }
375    }
376
377    /**
378     * Get emails count (no caching)
379     * @param userIdentity The user identity
380     * @return The emails count
381     * @throws MessagingConnectorException if failed to get events from server
382     */
383    protected abstract int internalGetEmailsCount(UserIdentity userIdentity) throws MessagingConnectorException;
384    
385    @Override
386    public boolean supportInvitation() throws MessagingConnectorException
387    {
388        return false;
389    }
390
391    @Override
392    public boolean isEventExist(String eventId, UserIdentity organiser) throws MessagingConnectorException
393    {
394        // Check if one of the last calls returned an exception and throw it directly if needed
395        _throwMessagingConnectorExceptionIfInCache(organiser);
396        try
397        {
398            return internalIsEventExist(eventId, organiser);
399        }
400        catch (MessagingConnectorException e)
401        {
402            // Save the exception in cache to avoid to call the server again
403            _putExceptionInCache(organiser, e.getType());
404            throw e;
405        }
406    }
407
408    /**
409     * True if the event exist in the messaging connector
410     * @param eventId the event id
411     * @param organiser the organiser
412     * @return true if the event exist
413     * @throws MessagingConnectorException if an error occurred
414     */
415    protected boolean internalIsEventExist(String eventId, UserIdentity organiser) throws MessagingConnectorException
416    {
417        throw new UnsupportedOperationException("Invitation is not implemented for messaging connector");
418    }
419    
420    @Override
421    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
422    {
423        // Check if one of the last calls returned an exception and throw it directly if needed
424        _throwMessagingConnectorExceptionIfInCache(organiser);
425        try
426        {
427            return internalCreateEvent(title, description, place, isAllDay, startDate, endDate, recurrenceType, untilDate, attendees, organiser);
428        }
429        catch (MessagingConnectorException e)
430        {
431            // Save the exception in cache to avoid to call the server again
432            _putExceptionInCache(organiser, e.getType());
433            throw e;
434        }
435    }
436    /**
437     * Create an event
438     * @param title the event title
439     * @param description the event description
440     * @param place the event place
441     * @param isAllDay if the event is all day
442     * @param startDate the event start date
443     * @param endDate the event end date
444     * @param recurrenceType recurrence type
445     * @param untilDate until date of the recurring event
446     * @param attendees the map of attendees (email -&gt; optional or requested) to set
447     * @param organiser the event organiser
448     * @return the id of the event created
449     * @throws MessagingConnectorException if failed to get events from server
450     */
451    protected String internalCreateEvent(String title, String description, String place, boolean isAllDay, Date startDate, Date endDate, EventRecurrenceTypeEnum recurrenceType, Date untilDate, Map<String, Boolean> attendees, UserIdentity organiser) throws MessagingConnectorException
452    {
453        throw new UnsupportedOperationException("Invitation is not implemented for messaging connector");
454    }
455
456    @Override
457    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
458    {
459        // Check if one of the last calls returned an exception and throw it directly if needed
460        _throwMessagingConnectorExceptionIfInCache(organiser);
461        try
462        {
463            internalUpdateEvent(eventId, title, description, place, isAllDay, startDate, endDate, recurrenceType, untilDate, attendees, organiser);
464        }
465        catch (MessagingConnectorException e)
466        {
467            // Save the exception in cache to avoid to call the server again
468            _putExceptionInCache(organiser, e.getType());
469            throw e;
470        }
471    }
472
473    /**
474     * Update an event
475     * @param eventId the event id to delete
476     * @param title the event title
477     * @param description the event description
478     * @param place the event place
479     * @param isAllDay if the event is all day
480     * @param startDate the event start date
481     * @param endDate the event end date
482     * @param recurrenceType recurrence type
483     * @param untilDate until date of the recurring event
484     * @param attendees the map of attendees (email -&gt; optional or requested) to set
485     * @param organiser the event organiser
486     * @throws MessagingConnectorException if failed to get events from server
487     */
488    protected void internalUpdateEvent(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
489    {
490        throw new UnsupportedOperationException("Invitation is not implemented for messaging connector");
491    }
492
493    @Override
494    public void deleteEvent(String eventId, UserIdentity organiser) throws MessagingConnectorException
495    {
496        // Check if one of the last calls returned an exception and throw it directly if needed
497        _throwMessagingConnectorExceptionIfInCache(organiser);
498        try
499        {
500            internalDeleteEvent(eventId, organiser);
501        }
502        catch (MessagingConnectorException e)
503        {
504            // Save the exception in cache to avoid to call the server again
505            _putExceptionInCache(organiser, e.getType());
506            throw e;
507        }
508    }
509
510    /**
511     * Delete an event
512     * @param eventId the event id to delete
513     * @param organiser the event organiser
514     * @throws MessagingConnectorException if failed to get events from server
515     */
516    protected void internalDeleteEvent(String eventId, UserIdentity organiser) throws MessagingConnectorException
517    {
518        throw new UnsupportedOperationException("Invitation is not implemented for messaging connector");
519    }
520
521    @Override
522    public Map<String, AttendeeInformation> getAttendees(String eventId, UserIdentity organiser) throws MessagingConnectorException
523    {
524        // Check if one of the last calls returned an exception and throw it directly if needed
525        _throwMessagingConnectorExceptionIfInCache(organiser);
526        try
527        {
528            return internalGetAttendees(eventId, organiser);
529        }
530        catch (MessagingConnectorException e)
531        {
532            // Save the exception in cache to avoid to call the server again
533            _putExceptionInCache(organiser, e.getType());
534            throw e;
535        }
536    }
537
538    /**
539     * Get the map of attendees for an event
540     * @param eventId the event id
541     * @param organiser the event organiser
542     * @return the map of attendees (email -&gt; attendee information)
543     * @throws MessagingConnectorException if failed to get events from server
544     */
545    protected Map<String, AttendeeInformation> internalGetAttendees(String eventId, UserIdentity organiser) throws MessagingConnectorException
546    {
547        throw new UnsupportedOperationException("Invitation is not implemented for messaging connector");
548    }
549
550    @Override
551    public void setAttendees(String eventId, Map<String, Boolean> attendees, UserIdentity organiser) throws MessagingConnectorException
552    {
553        // Check if one of the last calls returned an exception and throw it directly if needed
554        _throwMessagingConnectorExceptionIfInCache(organiser);
555        try
556        {
557            internalSetAttendees(eventId, attendees, organiser);
558        }
559        catch (MessagingConnectorException e)
560        {
561            // Save the exception in cache to avoid to call the server again
562            _putExceptionInCache(organiser, e.getType());
563            throw e;
564        }
565    }
566
567    /**
568     * Set attendees for an event
569     * @param eventId the event id
570     * @param attendees the map of attendees (email -&gt; optional or requested) to set
571     * @param organiser the event organiser
572     * @throws MessagingConnectorException if failed to get events from server
573     */
574    protected void internalSetAttendees(String eventId, Map<String, Boolean> attendees, UserIdentity organiser) throws MessagingConnectorException
575    {
576        throw new UnsupportedOperationException("Invitation is not implemented for messaging connector");
577    }
578
579    @Override
580    public Map<String, FreeBusyStatus> getFreeBusy(Date startDate, Date endDate, boolean isAllDay, Set<String> attendees, UserIdentity organiser) throws MessagingConnectorException
581    {
582        // Check if one of the last calls returned an exception and throw it directly if needed
583        _throwMessagingConnectorExceptionIfInCache(organiser);
584        try
585        {
586            return internalGetFreeBusy(startDate, endDate, isAllDay, attendees, organiser);
587        }
588        catch (MessagingConnectorException e)
589        {
590            // Save the exception in cache to avoid to call the server again
591            _putExceptionInCache(organiser, e.getType());
592            throw e;
593        }
594    }
595
596    /**
597     * Get free/busy status for attendees for a time window
598     * @param startDate the start date
599     * @param endDate the end date
600     * @param isAllDay true if is an allday event
601     * @param attendees the list of attendees email
602     * @param organiser the event organiser
603     * @return the map of attendees (email -&gt; freeBusy status)
604     * @throws MessagingConnectorException if failed to get events from server
605     */
606    protected Map<String, FreeBusyStatus> internalGetFreeBusy(Date startDate, Date endDate, boolean isAllDay, Set<String> attendees, UserIdentity organiser) throws MessagingConnectorException
607    {
608        throw new UnsupportedOperationException("Invitation is not implemented for messaging connector");
609    }
610
611    @Override
612    public boolean userCredentialNeeded()
613    {
614        boolean credentialNeeded = false;
615        if (supportUserCredential())
616        {
617            UserIdentity user = _currentUserProvider.getUser();
618            if (user != null)
619            {
620                try
621                {
622                    String password = getUserPassword(user);
623                    if (StringUtils.isEmpty(password))
624                    {
625                        credentialNeeded = true;
626                    }
627                }
628                catch (UserPreferencesException e)
629                {
630                    credentialNeeded = true;
631                }
632            }
633        }
634        return credentialNeeded;
635    }
636
637    @Override
638    public boolean supportUserCredential()
639    {
640        return false;
641    }
642    
643    @Override
644    public boolean isUserExist(UserIdentity userIdentity) throws MessagingConnectorException
645    {
646        throw new UnsupportedOperationException("Invitation is not implemented for messaging connector");
647    }
648    
649    private void _invalidateExceptionForUserInCache(UserIdentity userIdentity, MessagingConnectorException.ExceptionType type)
650    {
651        Set<UserIdentity> usersInCache = _getErrorCache().get(type);
652        if (usersInCache != null)
653        {
654            usersInCache.remove(userIdentity);
655        }
656    }
657    
658    private void _putExceptionInCache(UserIdentity userIdentity, MessagingConnectorException.ExceptionType type)
659    {
660        Cache<ExceptionType, Set<UserIdentity>> cache = null;
661        switch (type)
662        {
663            case TIMEOUT:
664                cache = _getTimeoutErrorCache();
665                break;
666            case CONFIGURATION_EXCEPTION:
667            case UNAUTHORIZED:
668            case UNKNOWN:
669            default:
670                cache = _getErrorCache();
671                break;
672        }
673        
674        Set<UserIdentity> usersInCache = cache.get(type);
675        if (usersInCache == null)
676        {
677            usersInCache = new HashSet<>();
678            usersInCache.add(userIdentity);
679            cache.put(type, usersInCache);
680        }
681        else
682        {
683            usersInCache.add(userIdentity);
684        }
685    }
686    
687    private MessagingConnectorException.ExceptionType _getExceptionTypeFromCache(UserIdentity userIdentity)
688    {
689        for (Entry<ExceptionType, Set<UserIdentity>> entry : _getErrorCache().asMap().entrySet())
690        {
691            if (entry.getValue().contains(userIdentity))
692            {
693                // Get the first exception type found for this user (assume that no multiple exception can exist for a same user)
694                return entry.getKey();
695            }
696        }
697        
698        for (Entry<ExceptionType, Set<UserIdentity>> entry : _getTimeoutErrorCache().asMap().entrySet())
699        {
700            if (entry.getValue().contains(userIdentity))
701            {
702                return entry.getKey();
703            }
704        }
705        
706        return null;
707    }
708    
709    private void _throwMessagingConnectorExceptionIfInCache(UserIdentity userIdentity) throws MessagingConnectorException
710    {
711        MessagingConnectorException.ExceptionType type = _getExceptionTypeFromCache(userIdentity);
712        if (type != null)
713        {
714            throw new MessagingConnectorException(type.name() + " exception was found in cache for user " + userIdentity + ". See previous exception to get the real cause.", type);
715        }
716    }
717    
718    /**
719     * Internal class for key of events cache
720     *
721     */
722    static class EventCacheKey
723    {
724        private UserIdentity _userIdentity;
725        private int _maxDays;
726        private int _maxEvents;
727
728        public EventCacheKey (UserIdentity userIdentity, int maxDays, int maxEvents)
729        {
730            _userIdentity = userIdentity;
731            _maxDays = maxDays;
732            _maxEvents = maxEvents;
733        }
734        
735        UserIdentity getUserIdentity()
736        {
737            return _userIdentity;
738        }
739        
740        int getMaxDays()
741        {
742            return _maxDays;
743        }
744        
745        int getMaxEvents()
746        {
747            return _maxEvents;
748        }
749        
750        @Override
751        public int hashCode()
752        {
753            return Objects.hash(_userIdentity, _maxDays, _maxEvents);
754        }
755        
756        @Override
757        public boolean equals(Object obj)
758        {
759            if (obj == null)
760            {
761                return false;
762            }
763            
764            if (!(obj instanceof EventCacheKey))
765            {
766                return false;
767            }
768            
769            EventCacheKey toCompare = (EventCacheKey) obj;
770            
771            return _userIdentity.equals(toCompare.getUserIdentity()) && _maxDays == toCompare.getMaxDays() && _maxEvents == toCompare.getMaxEvents();
772        }
773    }
774    
775    /**
776     * Internal class for key of events count cache
777     *
778     */
779    static class EventCountCacheKey
780    {
781        private UserIdentity _userIdentity;
782        private int _maxDays;
783
784        public EventCountCacheKey (UserIdentity userIdentity, int maxDays)
785        {
786            _userIdentity = userIdentity;
787            _maxDays = maxDays;
788        }
789        
790        UserIdentity getUserIdentity()
791        {
792            return _userIdentity;
793        }
794        
795        int getMaxDays()
796        {
797            return _maxDays;
798        }
799        
800        
801        @Override
802        public int hashCode()
803        {
804            return Objects.hash(_userIdentity, _maxDays);
805        }
806        
807        @Override
808        public boolean equals(Object obj)
809        {
810            if (obj == null)
811            {
812                return false;
813            }
814            
815            if (!(obj instanceof EventCountCacheKey))
816            {
817                return false;
818            }
819            
820            EventCountCacheKey toCompare = (EventCountCacheKey) obj;
821            
822            return _userIdentity.equals(toCompare.getUserIdentity()) && _maxDays == toCompare.getMaxDays();
823        }
824    }
825    
826    /**
827     * Internal class for key of events count cache
828     *
829     */
830    static class EmailCacheKey
831    {
832        private UserIdentity _userIdentity;
833        private int _maxEmails;
834
835        public EmailCacheKey (UserIdentity userIdentity, int maxEmails)
836        {
837            _userIdentity = userIdentity;
838            _maxEmails = maxEmails;
839        }
840        
841        UserIdentity getUserIdentity()
842        {
843            return _userIdentity;
844        }
845
846        int getMaxEmails()
847        {
848            return _maxEmails;
849        }
850        
851        @Override
852        public int hashCode()
853        {
854            return Objects.hash(_userIdentity, _maxEmails);
855        }
856        
857        @Override
858        public boolean equals(Object obj)
859        {
860            if (obj == null)
861            {
862                return false;
863            }
864            
865            if (!(obj instanceof EmailCacheKey))
866            {
867                return false;
868            }
869            
870            EmailCacheKey toCompare = (EmailCacheKey) obj;
871            
872            return _userIdentity.equals(toCompare.getUserIdentity()) && _maxEmails == toCompare.getMaxEmails();
873        }
874    }
875
876    private Cache<EventCacheKey, List<CalendarEvent>> _getEventsCache() 
877    {
878        return this._cacheManager.get(EVENTS_CACHE);
879    }
880    
881    private Cache<EventCountCacheKey, Integer> _getEventsCountCache() 
882    {
883        return this._cacheManager.get(EVENTS_COUNT_CACHE);
884    }
885    
886    private Cache<EmailCacheKey, List<EmailMessage>> _getEmailsCache() 
887    {
888        return this._cacheManager.get(EMAILS_CACHE);
889    }
890    
891    private Cache<UserIdentity, Integer> _getEmailsCountCache() 
892    {
893        return this._cacheManager.get(EMAILS_COUNT_CACHE);
894    }
895    
896    private Cache<ExceptionType, Set<UserIdentity>> _getErrorCache() 
897    {
898        return this._cacheManager.get(ERROR_CACHE);
899    }
900    
901    private Cache<ExceptionType, Set<UserIdentity>> _getTimeoutErrorCache() 
902    {
903        return this._cacheManager.get(TIMEOUT_ERROR_CACHE);
904    }
905
906}