001/* 002 * Copyright 2015 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.workspaces.calendars; 017 018import java.util.ArrayList; 019import java.util.Date; 020import java.util.HashMap; 021import java.util.List; 022import java.util.Map; 023 024import javax.jcr.Node; 025import javax.jcr.RepositoryException; 026 027import org.apache.avalon.framework.component.Component; 028import org.apache.avalon.framework.context.Context; 029import org.apache.avalon.framework.context.ContextException; 030import org.apache.avalon.framework.context.Contextualizable; 031import org.apache.avalon.framework.logger.AbstractLogEnabled; 032import org.apache.avalon.framework.service.ServiceException; 033import org.apache.avalon.framework.service.ServiceManager; 034import org.apache.avalon.framework.service.Serviceable; 035import org.apache.cocoon.components.ContextHelper; 036import org.apache.cocoon.environment.Request; 037import org.apache.commons.lang3.StringUtils; 038 039import org.ametys.core.observation.Event; 040import org.ametys.core.observation.ObservationManager; 041import org.ametys.core.ui.Callable; 042import org.ametys.core.user.CurrentUserProvider; 043import org.ametys.core.user.User; 044import org.ametys.core.user.UserIdentity; 045import org.ametys.core.user.UserManager; 046import org.ametys.core.util.DateUtils; 047import org.ametys.core.util.I18nUtils; 048import org.ametys.plugins.explorer.ObservationConstants; 049import org.ametys.plugins.explorer.calendars.Calendar; 050import org.ametys.plugins.explorer.calendars.CalendarEventAttendee; 051import org.ametys.plugins.explorer.calendars.jcr.JCRCalendarEvent; 052import org.ametys.plugins.messagingconnector.MessagingConnector; 053import org.ametys.plugins.messagingconnector.MessagingConnector.AttendeeInformation; 054import org.ametys.plugins.messagingconnector.MessagingConnector.FreeBusyStatus; 055import org.ametys.plugins.messagingconnector.MessagingConnector.ResponseType; 056import org.ametys.plugins.repository.AmetysObjectResolver; 057import org.ametys.plugins.repository.RepositoryConstants; 058import org.ametys.plugins.workspaces.project.ProjectManager; 059import org.ametys.plugins.workspaces.project.objects.Project; 060import org.ametys.runtime.i18n.I18nizableText; 061 062/** 063 * Manager for manipulating messaging connector linked to calendars event of a project 064 * 065 */ 066public class MessagingConnectorCalendarManager extends AbstractLogEnabled implements Serviceable, Component, Contextualizable 067{ 068 /** Avalon Role */ 069 public static final String ROLE = MessagingConnectorCalendarManager.class.getName(); 070 071 /** Property's name for synchronized id id */ 072 public static final String PROPERTY_SYNCHRONIZED_ID = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":synchronizedId"; 073 074 private MessagingConnector _messagingConnector; 075 private UserManager _userManager; 076 private AmetysObjectResolver _resolver; 077 private ObservationManager _observationManager; 078 private CurrentUserProvider _currentUserProvider; 079 private ProjectManager _projectManager; 080 private I18nUtils _i18nUtils; 081 private Context _context; 082 083 @Override 084 public void contextualize(Context context) throws ContextException 085 { 086 _context = context; 087 } 088 089 @Override 090 public void service(ServiceManager manager) throws ServiceException 091 { 092 boolean hasService = manager.hasService(MessagingConnector.ROLE); 093 _messagingConnector = hasService ? (MessagingConnector) manager.lookup(MessagingConnector.ROLE) : null; 094 095 _userManager = (UserManager) manager.lookup(UserManager.ROLE); 096 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 097 _observationManager = (ObservationManager) manager.lookup(ObservationManager.ROLE); 098 _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE); 099 _projectManager = (ProjectManager) manager.lookup(ProjectManager.ROLE); 100 _i18nUtils = (I18nUtils) manager.lookup(I18nUtils.ROLE); 101 } 102 103 /** 104 * Set the synchronized id (for messaging connector) to the event 105 * @param event the event 106 * @param synchronizedId the synchronized id 107 */ 108 public void setSynchronizedId(JCRCalendarEvent event, String synchronizedId) 109 { 110 try 111 { 112 event.getNode().setProperty(PROPERTY_SYNCHRONIZED_ID, synchronizedId); 113 event.saveChanges(); 114 } 115 catch (RepositoryException e) 116 { 117 getLogger().error("An error occurred setting synchronized id for event " + event.getId()); 118 } 119 } 120 121 /** 122 * Get the synchronized id (for messaging connector) of the event 123 * @param event the event 124 * @return the synchronized id 125 */ 126 public String getSynchronizedId(JCRCalendarEvent event) 127 { 128 try 129 { 130 Node eventNode = event.getNode(); 131 if (eventNode.hasProperty(PROPERTY_SYNCHRONIZED_ID)) 132 { 133 return eventNode.getProperty(PROPERTY_SYNCHRONIZED_ID).getString(); 134 } 135 } 136 catch (RepositoryException e) 137 { 138 getLogger().error("An error occurred getting synchronized id for event " + event.getId()); 139 } 140 141 return null; 142 } 143 144 /** 145 * Set attendees to the event 146 * @param eventId the event id 147 * @param attendees the list of attendees 148 */ 149 public void setAttendees(String eventId, List<CalendarEventAttendee> attendees) 150 { 151 if (attendees != null) 152 { 153 JCRCalendarEvent event = (JCRCalendarEvent) _resolver.resolveById(eventId); 154 try 155 { 156 event.setAttendees(attendees); 157 } 158 catch (RepositoryException e) 159 { 160 getLogger().error("An error occurred setting attendees for event " + eventId, e); 161 } 162 } 163 } 164 165 /** 166 * Set organiser to the event 167 * @param eventId the event id 168 * @param organiser the organiser 169 */ 170 public void setOrganiser(String eventId, UserIdentity organiser) 171 { 172 JCRCalendarEvent event = (JCRCalendarEvent) _resolver.resolveById(eventId); 173 if (organiser != null) 174 { 175 event.setOrganiser(organiser); 176 } 177 } 178 179 /** 180 * Add event invitation parameters (attendees and organiser) 181 * @param parameters the map of parameters 182 * @param eventId the event id 183 */ 184 public void addEventInvitation(Map<String, Object> parameters, String eventId) 185 { 186 @SuppressWarnings("unchecked") 187 List<Map<String, Object>> attendeesList = (List<Map<String, Object>>) parameters.get("attendees"); 188 List<CalendarEventAttendee> attendees = _getAttendeeListFromParameter(attendeesList); 189 setAttendees(eventId, attendees); 190 191 @SuppressWarnings("unchecked") 192 Map<String, Object> organiserMap = (Map<String, Object>) parameters.get("organiser"); 193 UserIdentity organiser = _getUserFromParameter(organiserMap); 194 setOrganiser(eventId, organiser); 195 196 if (organiser != null) 197 { 198 JCRCalendarEvent event = _resolver.resolveById(eventId); 199 _createEvent(event, organiser, attendees); 200 } 201 } 202 203 /** 204 * Edit event invitation parameters (attendees and organiser) 205 * @param parameters the map of parameters 206 * @param eventId the event id 207 */ 208 public void editEventInvitation(Map<String, Object> parameters, String eventId) 209 { 210 JCRCalendarEvent event = _resolver.resolveById(eventId); 211 212 @SuppressWarnings("unchecked") 213 List<Map<String, Object>> attendeesList = (List<Map<String, Object>>) parameters.get("attendees"); 214 List<CalendarEventAttendee> attendees = _getAttendeeListFromParameter(attendeesList); 215 try 216 { 217 if (attendees == null) 218 { 219 attendees = event.getAttendees(); 220 } 221 } 222 catch (RepositoryException e) 223 { 224 getLogger().error("An error occurred getting attendees from repository for event " + event.getId()); 225 } 226 setAttendees(eventId, attendees); 227 228 @SuppressWarnings("unchecked") 229 Map<String, Object> organiserMap = (Map<String, Object>) parameters.get("organiser"); 230 UserIdentity organiserFromMap = _getUserFromParameter(organiserMap); 231 UserIdentity organiser = organiserFromMap != null ? organiserFromMap : event.getOrganiser(); 232 setOrganiser(eventId, organiser); 233 234 if (_messagingConnector != null) 235 { 236 String synchronizedId = getSynchronizedId(event); 237 if (StringUtils.isNotBlank(synchronizedId) && _messagingConnector.isEventExist(synchronizedId, organiser)) 238 { 239 _editEvent(event, organiser, attendees); 240 } 241 else if (organiser != null) 242 { 243 _createEvent(event, organiser, attendees); 244 } 245 } 246 } 247 248 /** 249 * Create event in the messaging connector 250 * @param event the event 251 * @param organiser the organiser 252 * @param attendees the list of attendee 253 */ 254 protected void _createEvent(JCRCalendarEvent event, UserIdentity organiser, List<CalendarEventAttendee> attendees) 255 { 256 Map<String, Boolean> attendeesForMessagingConnector = new HashMap<>(); 257 for (CalendarEventAttendee attendee : attendees) 258 { 259 if (attendee.isExternal()) 260 { 261 attendeesForMessagingConnector.put(attendee.getEmail(), attendee.isMandatory()); 262 } 263 else 264 { 265 User user = _userManager.getUser(new UserIdentity(attendee.getLogin(), attendee.getPopulationId())); 266 attendeesForMessagingConnector.put(user.getEmail(), attendee.isMandatory()); 267 } 268 } 269 270 String synchronizedId = _messagingConnector.createEvent(_getEventTitle(event), event.getDescription(), event.getLocation(), event.getFullDay(), event.getStartDate(), event.getEndDate(), event.getRecurrenceType(), event.getRepeatUntil(), attendeesForMessagingConnector, event.getOrganiser()); 271 setSynchronizedId(event, synchronizedId); 272 } 273 274 /** 275 * Delete event in the messaging connector 276 * @param event the event 277 */ 278 public void deleteEvent(JCRCalendarEvent event) 279 { 280 if (isEventSynchronized(event.getId())) 281 { 282 String synchronizedId = getSynchronizedId(event); 283 _messagingConnector.deleteEvent(synchronizedId, event.getOrganiser()); 284 } 285 } 286 287 /** 288 * Edit event in the messaging connector 289 * @param event the event 290 * @param organiser the organiser 291 * @param attendees the list of attendee 292 */ 293 protected void _editEvent(JCRCalendarEvent event, UserIdentity organiser, List<CalendarEventAttendee> attendees) 294 { 295 String synchronizedId = getSynchronizedId(event); 296 Map<String, Boolean> attendeesForMessagingConnector = null; 297 if (attendees != null) 298 { 299 attendeesForMessagingConnector = new HashMap<>(); 300 for (CalendarEventAttendee attendee : attendees) 301 { 302 if (attendee.isExternal()) 303 { 304 attendeesForMessagingConnector.put(attendee.getEmail(), attendee.isMandatory()); 305 } 306 else 307 { 308 User user = _userManager.getUser(new UserIdentity(attendee.getLogin(), attendee.getPopulationId())); 309 attendeesForMessagingConnector.put(user.getEmail(), attendee.isMandatory()); 310 } 311 } 312 } 313 314 _messagingConnector.updateEvent(synchronizedId, _getEventTitle(event), event.getDescription(), event.getLocation(), event.getFullDay(), event.getStartDate(), event.getEndDate(), event.getRecurrenceType(), event.getRepeatUntil(), attendeesForMessagingConnector, event.getOrganiser()); 315 } 316 317 /** 318 * Get the computed title of the event 319 * @param event the event 320 * @return the computed title of the event 321 */ 322 protected String _getEventTitle(JCRCalendarEvent event) 323 { 324 Calendar calendar = event.getParent(); 325 Project project = _projectManager.getParentProject(calendar); 326 327 List<String> parameters = new ArrayList<>(); 328 parameters.add(project.getTitle()); 329 parameters.add(event.getTitle()); 330 I18nizableText titleI18n = new I18nizableText("plugin.workspaces", "PLUGINS_WORKSPACES_PROJECT_SERVICE_MODULE_CALENDAR_ADD_EVENT_MESSAGING_CONNECTOR_TITLE", parameters); 331 332 Request request = ContextHelper.getRequest(_context); 333 String lang = (String) request.getAttribute("sitemapLanguage"); 334 335 return _i18nUtils.translate(titleI18n, lang); 336 } 337 338 /** 339 * True if messaging connector support invitation 340 * @return true if messaging connector support invitation 341 */ 342 @Callable 343 public boolean supportInvitation() 344 { 345 return _messagingConnector != null && _messagingConnector.supportInvitation(); 346 } 347 348 /** 349 * True if user exists in the messaging connector 350 * @param userAsMap the user 351 * @return true if user exists in the messaging connector 352 */ 353 @Callable 354 public boolean isValidUser(Map<String, Object> userAsMap) 355 { 356 UserIdentity user = _getUserFromParameter(userAsMap); 357 return _messagingConnector != null && _messagingConnector.isUserExist(user); 358 } 359 360 /** 361 * True if the event is still synchronized in the messaging connector 362 * @param eventId the event id 363 * @return true if the event exist 364 */ 365 @Callable 366 public boolean isEventStillSynchronized(String eventId) 367 { 368 if (_messagingConnector == null) 369 { 370 return true; // No messaging connector (ignore it and return true) 371 } 372 373 JCRCalendarEvent event = _resolver.resolveById(eventId); 374 String synchronizedId = getSynchronizedId(event); 375 376 if (StringUtils.isBlank(synchronizedId)) 377 { 378 return true; // The event is not synchronized (ignore it and return true) 379 } 380 381 return _messagingConnector.isEventExist(synchronizedId, event.getOrganiser()); 382 } 383 384 /** 385 * True if the event is synchronized in the messaging connector 386 * @param eventId the event id 387 * @return true if the event exist 388 */ 389 @Callable 390 public boolean isEventSynchronized(String eventId) 391 { 392 if (_messagingConnector == null) 393 { 394 return false; 395 } 396 JCRCalendarEvent event = _resolver.resolveById(eventId); 397 String synchronizedId = getSynchronizedId(event); 398 399 if (StringUtils.isBlank(synchronizedId)) 400 { 401 return false; 402 } 403 404 return _messagingConnector.isEventExist(synchronizedId, event.getOrganiser()); 405 } 406 407 /** 408 * Get the list of attendees email with their free/busy status 409 * @param startDate the start date 410 * @param endDate the end date 411 * @param isAllDay true if is all day 412 * @param organiserMap the organiser map 413 * @param attendeesList the attendee list 414 * @return the list of attendees email with their free/busy status 415 */ 416 @Callable 417 public Map<String, FreeBusyStatus> getFreeBusy(String startDate, String endDate, boolean isAllDay, Map<String, Object> organiserMap, Map<String, String> attendeesList) 418 { 419 Map<String, FreeBusyStatus> userIdFreeBusy = new HashMap<>(); 420 421 if (_messagingConnector != null) 422 { 423 Date start = DateUtils.parse(startDate); 424 Date end = DateUtils.parse(endDate); 425 426 UserIdentity organiser = _getUserFromParameter(organiserMap); 427 428 Map<String, FreeBusyStatus> freeBusy = _messagingConnector.getFreeBusy(start, end, isAllDay, attendeesList.keySet(), organiser); 429 for (String email : freeBusy.keySet()) 430 { 431 userIdFreeBusy.put(attendeesList.get(email), freeBusy.get(email)); 432 } 433 } 434 435 return userIdFreeBusy; 436 } 437 438 /** 439 * Set invitation to the event 440 * @param eventId the event id 441 * @param organiserMap the organiser map 442 * @param attendeesList the attendees list 443 */ 444 @Callable 445 public void setInvitations(String eventId, Map<String, Object> organiserMap, List<Map<String, Object>> attendeesList) 446 { 447 UserIdentity organiser = _getUserFromParameter(organiserMap); 448 setOrganiser(eventId, organiser); 449 450 List<CalendarEventAttendee> attendees = _getAttendeeListFromParameter(attendeesList); 451 setAttendees(eventId, attendees); 452 453 JCRCalendarEvent event = _resolver.resolveById(eventId); 454 event.saveChanges(); 455 456 if (_messagingConnector != null) 457 { 458 String synchronizedId = getSynchronizedId(event); 459 if (StringUtils.isNotBlank(synchronizedId) && _messagingConnector.isEventExist(synchronizedId, organiser)) 460 { 461 462 Map<String, Boolean> attendeesForMessagingConnector = new HashMap<>(); 463 for (CalendarEventAttendee attendee : attendees) 464 { 465 if (attendee.isExternal()) 466 { 467 attendeesForMessagingConnector.put(attendee.getEmail(), attendee.isMandatory()); 468 } 469 else 470 { 471 User user = _userManager.getUser(new UserIdentity(attendee.getLogin(), attendee.getPopulationId())); 472 attendeesForMessagingConnector.put(user.getEmail(), attendee.isMandatory()); 473 } 474 } 475 _messagingConnector.setAttendees(synchronizedId, attendeesForMessagingConnector, organiser); 476 } 477 else 478 { 479 _createEvent(event, organiser, attendees); 480 } 481 } 482 483 Map<String, Object> eventParams = new HashMap<>(); 484 eventParams.put(ObservationConstants.ARGS_CALENDAR, event.getParent()); 485 eventParams.put(ObservationConstants.ARGS_CALENDAR_EVENT, event); 486 eventParams.put(ObservationConstants.ARGS_ID, event.getId()); 487 eventParams.put(ObservationConstants.ARGS_PARENT_ID, event.getParent().getId()); 488 489 _observationManager.notify(new Event(ObservationConstants.EVENT_CALENDAR_EVENT_UPDATED, _currentUserProvider.getUser(), eventParams)); 490 } 491 492 /** 493 * Retrieves attendees to the event 494 * @param eventId the event id 495 * @return The map of attendees 496 */ 497 @Callable 498 public Map<String, Object> getAttendees(String eventId) 499 { 500 JCRCalendarEvent event = _resolver.resolveById(eventId); 501 Map<String, AttendeeInformation> attendees = new HashMap<>(); 502 503 String synchronizedId = getSynchronizedId(event); 504 if (_messagingConnector != null && StringUtils.isNotBlank(synchronizedId)) 505 { 506 attendees = _messagingConnector.getAttendees(synchronizedId, event.getOrganiser()); 507 } 508 509 Map<String, Object> result = new HashMap<>(); 510 List<Map<String, Object>> attendeesMap = new ArrayList<>(); 511 512 try 513 { 514 for (CalendarEventAttendee attendee : event.getAttendees()) 515 { 516 Map<String, Object> attendee2Json = new HashMap<>(); 517 if (!attendee.isExternal()) 518 { 519 UserIdentity userIdentity = new UserIdentity(attendee.getLogin(), attendee.getPopulationId()); 520 User user = _userManager.getUser(userIdentity); 521 522 attendee2Json.put("login", attendee.getLogin()); 523 attendee2Json.put("populationId", attendee.getPopulationId()); 524 525 attendee2Json.put("sortablename", user.getSortableName()); 526 attendee2Json.put("fullname", user.getFullName()); 527 528 String email = user.getEmail(); 529 attendee2Json.put("email", email); 530 AttendeeInformation attendeeInformation = attendees.get(email); 531 if (attendeeInformation != null) 532 { 533 attendee2Json.put("status", attendeeInformation.getResponseType()); 534 } 535 else 536 { 537 attendee2Json.put("status", ResponseType.Unknown); 538 } 539 } 540 else 541 { 542 String email = attendee.getEmail(); 543 attendee2Json.put("email", email); 544 attendee2Json.put("sortablename", email); 545 attendee2Json.put("fullname", email); 546 AttendeeInformation attendeeInformation = attendees.get(email); 547 if (attendeeInformation != null) 548 { 549 attendee2Json.put("status", attendeeInformation.getResponseType()); 550 } 551 else 552 { 553 attendee2Json.put("status", ResponseType.Unknown); 554 } 555 } 556 557 attendee2Json.put("external", attendee.isExternal()); 558 attendee2Json.put("mandatory", attendee.isMandatory()); 559 560 attendeesMap.add(attendee2Json); 561 } 562 } 563 catch (Exception e) 564 { 565 getLogger().error("An error occurred getting attendees for event " + eventId, e); 566 } 567 568 result.put("attendees", attendeesMap); 569 570 return result; 571 } 572 573 /** 574 * Retrieves organiser to the event 575 * @param eventId the event id 576 * @return The organiser 577 */ 578 @Callable 579 public Map<String, Object> getOrganiser(String eventId) 580 { 581 Map<String, Object> result = new HashMap<>(); 582 Map<String, Object> organiserMap = new HashMap<>(); 583 584 try 585 { 586 JCRCalendarEvent event = (JCRCalendarEvent) _resolver.resolveById(eventId); 587 UserIdentity organiserIdentity = event.getOrganiser(); 588 589 organiserMap.put("login", organiserIdentity.getLogin()); 590 organiserMap.put("populationId", organiserIdentity.getPopulationId()); 591 592 User organiser = _userManager.getUser(organiserIdentity); 593 organiserMap.put("sortablename", organiser.getSortableName()); 594 organiserMap.put("fullname", organiser.getFullName()); 595 } 596 catch (Exception e) 597 { 598 getLogger().error("An error occurred getting organiser for event " + eventId, e); 599 } 600 601 result.put("user", organiserMap); 602 603 return result; 604 } 605 606 /** 607 * Get user from the user parameter 608 * @param userMap the user map from parameters 609 * @return the user 610 */ 611 protected UserIdentity _getUserFromParameter(Map<String, Object> userMap) 612 { 613 if (userMap != null) 614 { 615 String login = (String) userMap.get("login"); 616 String populationId = (String) userMap.get("populationId"); 617 618 return new UserIdentity(login, populationId); 619 } 620 621 return null; 622 } 623 624 /** 625 * Get attendees list from the attendees parameter 626 * @param attendeesList the attendees list from parameters 627 * @return the attendees list 628 */ 629 protected List<CalendarEventAttendee> _getAttendeeListFromParameter(List<Map<String, Object>> attendeesList) 630 { 631 if (attendeesList != null) 632 { 633 List<CalendarEventAttendee> attendees = new ArrayList<>(); 634 for (Map<String, Object> attendee : attendeesList) 635 { 636 CalendarEventAttendee calendarEventAttendee = new CalendarEventAttendee(); 637 boolean isExternal = (boolean) attendee.get("external"); 638 if (isExternal) 639 { 640 calendarEventAttendee.setEmail((String) attendee.get("email")); 641 } 642 else 643 { 644 calendarEventAttendee.setPopulationId((String) attendee.get("populationId")); 645 calendarEventAttendee.setLogin((String) attendee.get("login")); 646 } 647 648 calendarEventAttendee.setIsExternal(isExternal); 649 calendarEventAttendee.setIsMandatory((boolean) attendee.get("mandatory")); 650 651 attendees.add(calendarEventAttendee); 652 } 653 return attendees; 654 } 655 656 return null; 657 } 658 659}