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