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