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 -> 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 -> 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 -> 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 -> 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 -> 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}