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.ofMinutes(cacheTtl)); 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() throws MessagingConnectorException 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 MessagingConnectorException(userPopulationsIds.size() == 0 202 ? "There are no population defined." 203 : "There is more than one population defined. You must set the configuration parameter 'population id' for the messaging connector"); 204 205 } 206 else 207 { 208 return _populationIds; 209 } 210 } 211 212 /** 213 * True if the user is allowed 214 * @param userIdentity the user identity 215 * @return true if the user is allowed 216 */ 217 protected boolean isAllowed(UserIdentity userIdentity) 218 { 219 if (userIdentity == null) 220 { 221 getLogger().warn("There is no connected user to get user's mails or events from messaging connector"); 222 return false; 223 } 224 225 List<String> allowedPopulations = getAllowedPopulationIds(); 226 if (!allowedPopulations.contains(userIdentity.getPopulationId())) 227 { 228 getLogger().warn("The user " + userIdentity + " does not belong to any authorized user populations for messaging connector " + allowedPopulations); 229 return false; 230 } 231 232 return true; 233 } 234 235 @Override 236 public List<CalendarEvent> getEvents(UserIdentity userIdentity, int maxDays, int maxEvents) throws MessagingConnectorException 237 { 238 if (!isAllowed(userIdentity)) 239 { 240 return new ArrayList<>(); 241 } 242 243 // Check if one of the last calls returned an exception and throw it directly if needed 244 _throwMessagingConnectorExceptionIfInCache(userIdentity); 245 246 try 247 { 248 EventCacheKey eventCacheKey = new EventCacheKey(userIdentity, maxDays, maxEvents); 249 return _getEventsCache().get(eventCacheKey, key -> internalGetEvents(userIdentity, maxDays, maxEvents)); 250 } 251 catch (CacheException e) 252 { 253 if (e.getCause() instanceof MessagingConnectorException) 254 { 255 MessagingConnectorException mce = (MessagingConnectorException) e.getCause(); 256 // Save the exception in cache to avoid to call the server again 257 _putExceptionInCache(userIdentity, mce.getType()); 258 throw mce; 259 } 260 throw e; 261 } 262 } 263 264 @Override 265 public int getEventsCount(UserIdentity userIdentity, int maxDays) throws MessagingConnectorException 266 { 267 if (!isAllowed(userIdentity)) 268 { 269 return 0; 270 } 271 272 // Check if one of the last calls returned an exception and throw it directly if needed 273 _throwMessagingConnectorExceptionIfInCache(userIdentity); 274 275 try 276 { 277 EventCountCacheKey eventCountCacheKey = new EventCountCacheKey(userIdentity, maxDays); 278 return _getEventsCountCache().get(eventCountCacheKey, key -> internalGetEventsCount(userIdentity, maxDays)); 279 } 280 catch (CacheException e) 281 { 282 if (e.getCause() instanceof MessagingConnectorException) 283 { 284 MessagingConnectorException mce = (MessagingConnectorException) e.getCause(); 285 // Save the exception in cache to avoid to call the server again 286 _putExceptionInCache(userIdentity, mce.getType()); 287 throw mce; 288 } 289 throw e; 290 } 291 } 292 293 @Override 294 public List<EmailMessage> getUnreadEmails(UserIdentity userIdentity, int maxEmails) throws MessagingConnectorException 295 { 296 if (!isAllowed(userIdentity)) 297 { 298 return new ArrayList<>(); 299 } 300 301 // Check if one of the last calls returned an exception and throw it directly if needed 302 _throwMessagingConnectorExceptionIfInCache(userIdentity); 303 304 try 305 { 306 EmailCacheKey emailCacheKey = new EmailCacheKey(userIdentity, maxEmails); 307 return _getEmailsCache().get(emailCacheKey, key -> internalGetEmails(userIdentity, maxEmails)); 308 } 309 catch (CacheException e) 310 { 311 if (e.getCause() instanceof MessagingConnectorException) 312 { 313 MessagingConnectorException mce = (MessagingConnectorException) e.getCause(); 314 // Save the exception in cache to avoid to call the server again 315 _putExceptionInCache(userIdentity, mce.getType()); 316 throw mce; 317 } 318 throw e; 319 } 320 } 321 322 @Override 323 public int getUnreadEmailCount(UserIdentity userIdentity) throws MessagingConnectorException 324 { 325 if (!isAllowed(userIdentity)) 326 { 327 return 0; 328 } 329 330 // Check if one of the last calls returned an exception and throw it directly if needed 331 _throwMessagingConnectorExceptionIfInCache(userIdentity); 332 333 try 334 { 335 return _getEmailsCountCache().get(userIdentity, key -> internalGetEmailsCount(userIdentity)); 336 } 337 catch (CacheException e) 338 { 339 if (e.getCause() instanceof MessagingConnectorException) 340 { 341 MessagingConnectorException mce = (MessagingConnectorException) e.getCause(); 342 // Save the exception in cache to avoid to call the server again 343 _putExceptionInCache(userIdentity, mce.getType()); 344 throw mce; 345 } 346 throw e; 347 } 348 } 349 350 /** 351 * Get upcoming events (no caching) 352 * @param userIdentity The user identity 353 * @param maxDays The maximum number of days to search for 354 * @param maxEvents The maximum number of events to retrieve 355 * @return The calendar events 356 * @throws MessagingConnectorException if failed to get events from server 357 */ 358 protected abstract List<CalendarEvent> internalGetEvents(UserIdentity userIdentity, int maxDays, int maxEvents) throws MessagingConnectorException; 359 360 /** 361 * Get upcoming events count (no caching) 362 * @param userIdentity The user identity 363 * @param maxDays The maximum number of days to search for 364 * @return The number of calendar events 365 * @throws MessagingConnectorException if failed to get events from server 366 */ 367 protected abstract int internalGetEventsCount(UserIdentity userIdentity, int maxDays) throws MessagingConnectorException; 368 369 /** 370 * Get emails (no caching) 371 * @param userIdentity The user identity 372 * @param maxEmails The maximum number of emails to retrieve 373 * @return The emails 374 * @throws MessagingConnectorException if failed to get events from server 375 */ 376 protected abstract List<EmailMessage> internalGetEmails(UserIdentity userIdentity, int maxEmails) throws MessagingConnectorException; 377 378 /** 379 * Get the user password for the messaging connector 380 * @param userIdentity user to check 381 * @return the decrypted user password 382 * @throws UserPreferencesException error while reading user preferences 383 */ 384 protected String getUserPassword(UserIdentity userIdentity) throws UserPreferencesException 385 { 386 if (supportUserCredential(userIdentity)) 387 { 388 String encryptedValue = getUserCryptedPassword(userIdentity); 389 return _cryptoHelper.decrypt(encryptedValue); 390 } 391 else 392 { 393 throw new MessagingConnectorException("Cannot get password for user " + userIdentity + ": user credential are not supported by this messaging connector", MessagingConnectorException.ExceptionType.CONFIGURATION_EXCEPTION); 394 } 395 } 396 397 /** 398 * Get the user password, still crypted 399 * @param userIdentity user to check 400 * @return the still crypted user password 401 * @throws UserPreferencesException error while reading user preferences 402 */ 403 protected String getUserCryptedPassword(UserIdentity userIdentity) throws UserPreferencesException 404 { 405 return _userPref.getUserPreferenceAsString(userIdentity, "/messaging-connector", Collections.emptyMap(), "messaging-connector-password"); 406 } 407 408 @Override 409 public void setUserPassword(UserIdentity userIdentity, String password) throws UserPreferencesException, MessagingConnectorException 410 { 411 if (supportUserCredential(userIdentity)) 412 { 413 String cryptedPassword = _cryptoHelper.encrypt(password); 414 _userPref.addUserPreference(userIdentity, "/messaging-connector", Collections.emptyMap(), "messaging-connector-password", cryptedPassword); 415 // Unauthorized cache is invalidated for this user 416 _invalidateExceptionForUserInCache(userIdentity, ExceptionType.UNAUTHORIZED); 417 } 418 else 419 { 420 throw new MessagingConnectorException("Cannot set password for user " + userIdentity + ": user credential are not supported by this messaging connector", MessagingConnectorException.ExceptionType.CONFIGURATION_EXCEPTION); 421 } 422 } 423 424 /** 425 * Get emails count (no caching) 426 * @param userIdentity The user identity 427 * @return The emails count 428 * @throws MessagingConnectorException if failed to get events from server 429 */ 430 protected abstract int internalGetEmailsCount(UserIdentity userIdentity) throws MessagingConnectorException; 431 432 @Override 433 public boolean supportInvitation() throws MessagingConnectorException 434 { 435 return false; 436 } 437 438 @Override 439 public boolean isEventExist(String eventId, UserIdentity organiser) throws MessagingConnectorException 440 { 441 // Check if one of the last calls returned an exception and throw it directly if needed 442 _throwMessagingConnectorExceptionIfInCache(organiser); 443 try 444 { 445 return internalIsEventExist(eventId, organiser); 446 } 447 catch (MessagingConnectorException e) 448 { 449 // Save the exception in cache to avoid to call the server again 450 _putExceptionInCache(organiser, e.getType()); 451 throw e; 452 } 453 } 454 455 /** 456 * True if the event exist in the messaging connector 457 * @param eventId the event id 458 * @param organiser the organiser 459 * @return true if the event exist 460 * @throws MessagingConnectorException if an error occurred 461 */ 462 protected boolean internalIsEventExist(String eventId, UserIdentity organiser) throws MessagingConnectorException 463 { 464 throw new UnsupportedOperationException("Invitation is not implemented for messaging connector"); 465 } 466 467 @Override 468 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 469 { 470 // Check if one of the last calls returned an exception and throw it directly if needed 471 _throwMessagingConnectorExceptionIfInCache(organiser); 472 try 473 { 474 return internalCreateEvent(title, description, place, isAllDay, startDate, endDate, recurrenceType, untilDate, attendees, organiser); 475 } 476 catch (MessagingConnectorException e) 477 { 478 // Save the exception in cache to avoid to call the server again 479 _putExceptionInCache(organiser, e.getType()); 480 throw e; 481 } 482 } 483 /** 484 * Create an event 485 * @param title the event title 486 * @param description the event description 487 * @param place the event place 488 * @param isAllDay if the event is all day 489 * @param startDate the event start date 490 * @param endDate the event end date 491 * @param recurrenceType recurrence type 492 * @param untilDate until date of the recurring event 493 * @param attendees the map of attendees (email -> optional or requested) to set 494 * @param organiser the event organiser 495 * @return the id of the event created 496 * @throws MessagingConnectorException if failed to get events from server 497 */ 498 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 499 { 500 throw new UnsupportedOperationException("Invitation is not implemented for messaging connector"); 501 } 502 503 @Override 504 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 505 { 506 // Check if one of the last calls returned an exception and throw it directly if needed 507 _throwMessagingConnectorExceptionIfInCache(organiser); 508 try 509 { 510 internalUpdateEvent(eventId, title, description, place, isAllDay, startDate, endDate, recurrenceType, untilDate, attendees, organiser); 511 } 512 catch (MessagingConnectorException e) 513 { 514 // Save the exception in cache to avoid to call the server again 515 _putExceptionInCache(organiser, e.getType()); 516 throw e; 517 } 518 } 519 520 /** 521 * Update an event 522 * @param eventId the event id to delete 523 * @param title the event title 524 * @param description the event description 525 * @param place the event place 526 * @param isAllDay if the event is all day 527 * @param startDate the event start date 528 * @param endDate the event end date 529 * @param recurrenceType recurrence type 530 * @param untilDate until date of the recurring event 531 * @param attendees the map of attendees (email -> optional or requested) to set 532 * @param organiser the event organiser 533 * @throws MessagingConnectorException if failed to get events from server 534 */ 535 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 536 { 537 throw new UnsupportedOperationException("Invitation is not implemented for messaging connector"); 538 } 539 540 @Override 541 public void deleteEvent(String eventId, UserIdentity organiser) throws MessagingConnectorException 542 { 543 // Check if one of the last calls returned an exception and throw it directly if needed 544 _throwMessagingConnectorExceptionIfInCache(organiser); 545 try 546 { 547 internalDeleteEvent(eventId, organiser); 548 } 549 catch (MessagingConnectorException e) 550 { 551 // Save the exception in cache to avoid to call the server again 552 _putExceptionInCache(organiser, e.getType()); 553 throw e; 554 } 555 } 556 557 /** 558 * Delete an event 559 * @param eventId the event id to delete 560 * @param organiser the event organiser 561 * @throws MessagingConnectorException if failed to get events from server 562 */ 563 protected void internalDeleteEvent(String eventId, UserIdentity organiser) throws MessagingConnectorException 564 { 565 throw new UnsupportedOperationException("Invitation is not implemented for messaging connector"); 566 } 567 568 @Override 569 public Map<String, AttendeeInformation> getAttendees(String eventId, UserIdentity organiser) throws MessagingConnectorException 570 { 571 // Check if one of the last calls returned an exception and throw it directly if needed 572 _throwMessagingConnectorExceptionIfInCache(organiser); 573 try 574 { 575 return internalGetAttendees(eventId, organiser); 576 } 577 catch (MessagingConnectorException e) 578 { 579 // Save the exception in cache to avoid to call the server again 580 _putExceptionInCache(organiser, e.getType()); 581 throw e; 582 } 583 } 584 585 /** 586 * Get the map of attendees for an event 587 * @param eventId the event id 588 * @param organiser the event organiser 589 * @return the map of attendees (email -> attendee information) 590 * @throws MessagingConnectorException if failed to get events from server 591 */ 592 protected Map<String, AttendeeInformation> internalGetAttendees(String eventId, UserIdentity organiser) throws MessagingConnectorException 593 { 594 throw new UnsupportedOperationException("Invitation is not implemented for messaging connector"); 595 } 596 597 @Override 598 public void setAttendees(String eventId, Map<String, Boolean> attendees, UserIdentity organiser) throws MessagingConnectorException 599 { 600 // Check if one of the last calls returned an exception and throw it directly if needed 601 _throwMessagingConnectorExceptionIfInCache(organiser); 602 try 603 { 604 internalSetAttendees(eventId, attendees, organiser); 605 } 606 catch (MessagingConnectorException e) 607 { 608 // Save the exception in cache to avoid to call the server again 609 _putExceptionInCache(organiser, e.getType()); 610 throw e; 611 } 612 } 613 614 /** 615 * Set attendees for an event 616 * @param eventId the event id 617 * @param attendees the map of attendees (email -> optional or requested) to set 618 * @param organiser the event organiser 619 * @throws MessagingConnectorException if failed to get events from server 620 */ 621 protected void internalSetAttendees(String eventId, Map<String, Boolean> attendees, UserIdentity organiser) throws MessagingConnectorException 622 { 623 throw new UnsupportedOperationException("Invitation is not implemented for messaging connector"); 624 } 625 626 @Override 627 public Map<String, FreeBusyStatus> getFreeBusy(Date startDate, Date endDate, boolean isAllDay, Set<String> attendees, UserIdentity organiser) throws MessagingConnectorException 628 { 629 // Check if one of the last calls returned an exception and throw it directly if needed 630 _throwMessagingConnectorExceptionIfInCache(organiser); 631 try 632 { 633 return internalGetFreeBusy(startDate, endDate, isAllDay, attendees, organiser); 634 } 635 catch (MessagingConnectorException e) 636 { 637 // Save the exception in cache to avoid to call the server again 638 _putExceptionInCache(organiser, e.getType()); 639 throw e; 640 } 641 } 642 643 /** 644 * Get free/busy status for attendees for a time window 645 * @param startDate the start date 646 * @param endDate the end date 647 * @param isAllDay true if is an allday event 648 * @param attendees the list of attendees email 649 * @param organiser the event organiser 650 * @return the map of attendees (email -> freeBusy status) 651 * @throws MessagingConnectorException if failed to get events from server 652 */ 653 protected Map<String, FreeBusyStatus> internalGetFreeBusy(Date startDate, Date endDate, boolean isAllDay, Set<String> attendees, UserIdentity organiser) throws MessagingConnectorException 654 { 655 throw new UnsupportedOperationException("Invitation is not implemented for messaging connector"); 656 } 657 658 @Override 659 public boolean userCredentialNeeded(UserIdentity userIdentity) 660 { 661 boolean credentialNeeded = false; 662 if (supportUserCredential(userIdentity)) 663 { 664 UserIdentity user = _currentUserProvider.getUser(); 665 if (user != null) 666 { 667 try 668 { 669 String password = getUserPassword(user); 670 if (StringUtils.isEmpty(password)) 671 { 672 credentialNeeded = true; 673 } 674 } 675 catch (UserPreferencesException e) 676 { 677 credentialNeeded = true; 678 } 679 } 680 } 681 return credentialNeeded; 682 } 683 684 @Override 685 public boolean supportUserCredential(UserIdentity userIdentity) 686 { 687 return false; 688 } 689 690 @Override 691 public boolean isUserExist(UserIdentity userIdentity) throws MessagingConnectorException 692 { 693 throw new UnsupportedOperationException("Invitation is not implemented for messaging connector"); 694 } 695 696 private void _invalidateExceptionForUserInCache(UserIdentity userIdentity, MessagingConnectorException.ExceptionType type) 697 { 698 Set<UserIdentity> usersInCache = _getErrorCache().get(type); 699 if (usersInCache != null) 700 { 701 usersInCache.remove(userIdentity); 702 } 703 } 704 705 private void _putExceptionInCache(UserIdentity userIdentity, MessagingConnectorException.ExceptionType type) 706 { 707 Cache<ExceptionType, Set<UserIdentity>> cache = null; 708 switch (type) 709 { 710 case TIMEOUT: 711 cache = _getTimeoutErrorCache(); 712 break; 713 case CONFIGURATION_EXCEPTION: 714 case UNAUTHORIZED: 715 case UNKNOWN: 716 default: 717 cache = _getErrorCache(); 718 break; 719 } 720 721 Set<UserIdentity> usersInCache = cache.get(type); 722 if (usersInCache == null) 723 { 724 usersInCache = new HashSet<>(); 725 usersInCache.add(userIdentity); 726 cache.put(type, usersInCache); 727 } 728 else 729 { 730 usersInCache.add(userIdentity); 731 } 732 } 733 734 private MessagingConnectorException.ExceptionType _getExceptionTypeFromCache(UserIdentity userIdentity) 735 { 736 for (Entry<ExceptionType, Set<UserIdentity>> entry : _getErrorCache().asMap().entrySet()) 737 { 738 if (entry.getValue().contains(userIdentity)) 739 { 740 // Get the first exception type found for this user (assume that no multiple exception can exist for a same user) 741 return entry.getKey(); 742 } 743 } 744 745 for (Entry<ExceptionType, Set<UserIdentity>> entry : _getTimeoutErrorCache().asMap().entrySet()) 746 { 747 if (entry.getValue().contains(userIdentity)) 748 { 749 return entry.getKey(); 750 } 751 } 752 753 return null; 754 } 755 756 private void _throwMessagingConnectorExceptionIfInCache(UserIdentity userIdentity) throws MessagingConnectorException 757 { 758 MessagingConnectorException.ExceptionType type = _getExceptionTypeFromCache(userIdentity); 759 if (type != null) 760 { 761 throw new MessagingConnectorException(type.name() + " exception was found in cache for user " + userIdentity + ". See previous exception to get the real cause.", type); 762 } 763 } 764 765 /** 766 * Internal class for key of events cache 767 * 768 */ 769 static class EventCacheKey 770 { 771 private UserIdentity _userIdentity; 772 private int _maxDays; 773 private int _maxEvents; 774 775 public EventCacheKey (UserIdentity userIdentity, int maxDays, int maxEvents) 776 { 777 _userIdentity = userIdentity; 778 _maxDays = maxDays; 779 _maxEvents = maxEvents; 780 } 781 782 UserIdentity getUserIdentity() 783 { 784 return _userIdentity; 785 } 786 787 int getMaxDays() 788 { 789 return _maxDays; 790 } 791 792 int getMaxEvents() 793 { 794 return _maxEvents; 795 } 796 797 @Override 798 public int hashCode() 799 { 800 return Objects.hash(_userIdentity, _maxDays, _maxEvents); 801 } 802 803 @Override 804 public boolean equals(Object obj) 805 { 806 if (obj == null) 807 { 808 return false; 809 } 810 811 if (!(obj instanceof EventCacheKey)) 812 { 813 return false; 814 } 815 816 EventCacheKey toCompare = (EventCacheKey) obj; 817 818 return _userIdentity.equals(toCompare.getUserIdentity()) && _maxDays == toCompare.getMaxDays() && _maxEvents == toCompare.getMaxEvents(); 819 } 820 } 821 822 /** 823 * Internal class for key of events count cache 824 * 825 */ 826 static class EventCountCacheKey 827 { 828 private UserIdentity _userIdentity; 829 private int _maxDays; 830 831 public EventCountCacheKey (UserIdentity userIdentity, int maxDays) 832 { 833 _userIdentity = userIdentity; 834 _maxDays = maxDays; 835 } 836 837 UserIdentity getUserIdentity() 838 { 839 return _userIdentity; 840 } 841 842 int getMaxDays() 843 { 844 return _maxDays; 845 } 846 847 848 @Override 849 public int hashCode() 850 { 851 return Objects.hash(_userIdentity, _maxDays); 852 } 853 854 @Override 855 public boolean equals(Object obj) 856 { 857 if (obj == null) 858 { 859 return false; 860 } 861 862 if (!(obj instanceof EventCountCacheKey)) 863 { 864 return false; 865 } 866 867 EventCountCacheKey toCompare = (EventCountCacheKey) obj; 868 869 return _userIdentity.equals(toCompare.getUserIdentity()) && _maxDays == toCompare.getMaxDays(); 870 } 871 } 872 873 /** 874 * Internal class for key of events count cache 875 * 876 */ 877 static class EmailCacheKey 878 { 879 private UserIdentity _userIdentity; 880 private int _maxEmails; 881 882 public EmailCacheKey (UserIdentity userIdentity, int maxEmails) 883 { 884 _userIdentity = userIdentity; 885 _maxEmails = maxEmails; 886 } 887 888 UserIdentity getUserIdentity() 889 { 890 return _userIdentity; 891 } 892 893 int getMaxEmails() 894 { 895 return _maxEmails; 896 } 897 898 @Override 899 public int hashCode() 900 { 901 return Objects.hash(_userIdentity, _maxEmails); 902 } 903 904 @Override 905 public boolean equals(Object obj) 906 { 907 if (obj == null) 908 { 909 return false; 910 } 911 912 if (!(obj instanceof EmailCacheKey)) 913 { 914 return false; 915 } 916 917 EmailCacheKey toCompare = (EmailCacheKey) obj; 918 919 return _userIdentity.equals(toCompare.getUserIdentity()) && _maxEmails == toCompare.getMaxEmails(); 920 } 921 } 922 923 private Cache<EventCacheKey, List<CalendarEvent>> _getEventsCache() 924 { 925 return this._cacheManager.get(EVENTS_CACHE); 926 } 927 928 private Cache<EventCountCacheKey, Integer> _getEventsCountCache() 929 { 930 return this._cacheManager.get(EVENTS_COUNT_CACHE); 931 } 932 933 private Cache<EmailCacheKey, List<EmailMessage>> _getEmailsCache() 934 { 935 return this._cacheManager.get(EMAILS_CACHE); 936 } 937 938 private Cache<UserIdentity, Integer> _getEmailsCountCache() 939 { 940 return this._cacheManager.get(EMAILS_COUNT_CACHE); 941 } 942 943 private Cache<ExceptionType, Set<UserIdentity>> _getErrorCache() 944 { 945 return this._cacheManager.get(ERROR_CACHE); 946 } 947 948 private Cache<ExceptionType, Set<UserIdentity>> _getTimeoutErrorCache() 949 { 950 return this._cacheManager.get(TIMEOUT_ERROR_CACHE); 951 } 952 953}