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