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 synchronized in the messaging connector 360 * @param eventId the event id 361 * @return true if the event exist 362 */ 363 @Callable 364 public boolean isEventSynchronized(String eventId) 365 { 366 if (_messagingConnector == null) 367 { 368 return false; 369 } 370 JCRCalendarEvent event = _resolver.resolveById(eventId); 371 String synchronizedId = getSynchronizedId(event); 372 373 if (StringUtils.isBlank(synchronizedId)) 374 { 375 return false; 376 } 377 378 try 379 { 380 return _messagingConnector.isEventExist(synchronizedId, event.getOrganiser()); 381 } 382 catch (UnsupportedOperationException e) 383 { 384 getLogger().error("An error occurred while checking if event " + eventId + " exists", e); 385 return false; 386 } 387 388 389 } 390 391 /** 392 * Get the list of attendees email with their free/busy status 393 * @param startDate the start date 394 * @param endDate the end date 395 * @param isAllDay true if is all day 396 * @param organiserMap the organiser map 397 * @param attendeesList the attendee list 398 * @return the list of attendees email with their free/busy status 399 */ 400 @Callable 401 public Map<String, FreeBusyStatus> getFreeBusy(String startDate, String endDate, boolean isAllDay, Map<String, Object> organiserMap, Map<String, String> attendeesList) 402 { 403 Map<String, FreeBusyStatus> userIdFreeBusy = new HashMap<>(); 404 405 if (_messagingConnector != null) 406 { 407 Date start = DateUtils.parse(startDate); 408 Date end = DateUtils.parse(endDate); 409 410 UserIdentity organiser = _getUserFromParameter(organiserMap); 411 412 Map<String, FreeBusyStatus> freeBusy = _messagingConnector.getFreeBusy(start, end, isAllDay, attendeesList.keySet(), organiser); 413 for (String email : freeBusy.keySet()) 414 { 415 userIdFreeBusy.put(attendeesList.get(email), freeBusy.get(email)); 416 } 417 } 418 419 return userIdFreeBusy; 420 } 421 422 /** 423 * Set invitation to the event 424 * @param eventId the event id 425 * @param organiserMap the organiser map 426 * @param attendeesList the attendees list 427 */ 428 @Callable 429 public void setInvitations(String eventId, Map<String, Object> organiserMap, List<Map<String, Object>> attendeesList) 430 { 431 UserIdentity organiser = _getUserFromParameter(organiserMap); 432 setOrganiser(eventId, organiser); 433 434 List<CalendarEventAttendee> attendees = _getAttendeeListFromParameter(attendeesList); 435 setAttendees(eventId, attendees); 436 437 JCRCalendarEvent event = _resolver.resolveById(eventId); 438 event.saveChanges(); 439 440 if (_messagingConnector != null) 441 { 442 String synchronizedId = getSynchronizedId(event); 443 if (StringUtils.isNotBlank(synchronizedId) && _messagingConnector.isEventExist(synchronizedId, organiser)) 444 { 445 446 Map<String, Boolean> attendeesForMessagingConnector = new HashMap<>(); 447 for (CalendarEventAttendee attendee : attendees) 448 { 449 if (attendee.isExternal()) 450 { 451 attendeesForMessagingConnector.put(attendee.getEmail(), attendee.isMandatory()); 452 } 453 else 454 { 455 User user = _userManager.getUser(new UserIdentity(attendee.getLogin(), attendee.getPopulationId())); 456 attendeesForMessagingConnector.put(user.getEmail(), attendee.isMandatory()); 457 } 458 } 459 _messagingConnector.setAttendees(synchronizedId, attendeesForMessagingConnector, organiser); 460 } 461 else 462 { 463 _createEvent(event, organiser, attendees); 464 } 465 } 466 467 Map<String, Object> eventParams = new HashMap<>(); 468 eventParams.put(ObservationConstants.ARGS_CALENDAR, event.getParent()); 469 eventParams.put(ObservationConstants.ARGS_CALENDAR_EVENT, event); 470 eventParams.put(org.ametys.plugins.explorer.ObservationConstants.ARGS_ID, event.getId()); 471 eventParams.put(org.ametys.plugins.explorer.ObservationConstants.ARGS_PARENT_ID, event.getParent().getId()); 472 473 _observationManager.notify(new Event(ObservationConstants.EVENT_CALENDAR_EVENT_UPDATED, _currentUserProvider.getUser(), eventParams)); 474 } 475 476 /** 477 * Retrieves attendees to the event 478 * @param eventId the event id 479 * @return The map of attendees 480 */ 481 @Callable 482 public Map<String, Object> getAttendees(String eventId) 483 { 484 JCRCalendarEvent event = _resolver.resolveById(eventId); 485 Map<String, AttendeeInformation> attendees = new HashMap<>(); 486 487 String synchronizedId = getSynchronizedId(event); 488 if (_messagingConnector != null && StringUtils.isNotBlank(synchronizedId)) 489 { 490 attendees = _messagingConnector.getAttendees(synchronizedId, event.getOrganiser()); 491 } 492 493 Map<String, Object> result = new HashMap<>(); 494 List<Map<String, Object>> attendeesMap = new ArrayList<>(); 495 496 try 497 { 498 for (CalendarEventAttendee attendee : event.getAttendees()) 499 { 500 Map<String, Object> attendee2Json = new HashMap<>(); 501 if (!attendee.isExternal()) 502 { 503 UserIdentity userIdentity = new UserIdentity(attendee.getLogin(), attendee.getPopulationId()); 504 User user = _userManager.getUser(userIdentity); 505 506 attendee2Json.put("login", attendee.getLogin()); 507 attendee2Json.put("populationId", attendee.getPopulationId()); 508 509 attendee2Json.put("sortablename", user.getSortableName()); 510 attendee2Json.put("fullname", user.getFullName()); 511 512 String email = user.getEmail(); 513 attendee2Json.put("email", email); 514 AttendeeInformation attendeeInformation = attendees.get(email); 515 if (attendeeInformation != null) 516 { 517 attendee2Json.put("status", attendeeInformation.getResponseType()); 518 } 519 else 520 { 521 attendee2Json.put("status", ResponseType.Unknown); 522 } 523 } 524 else 525 { 526 String email = attendee.getEmail(); 527 attendee2Json.put("email", email); 528 attendee2Json.put("sortablename", email); 529 attendee2Json.put("fullname", 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 541 attendee2Json.put("external", attendee.isExternal()); 542 attendee2Json.put("mandatory", attendee.isMandatory()); 543 544 attendeesMap.add(attendee2Json); 545 } 546 } 547 catch (Exception e) 548 { 549 getLogger().error("An error occurred getting attendees for event " + eventId, e); 550 } 551 552 result.put("attendees", attendeesMap); 553 554 return result; 555 } 556 557 /** 558 * Retrieves organiser to the event 559 * @param eventId the event id 560 * @return The organiser 561 */ 562 @Callable 563 public Map<String, Object> getOrganiser(String eventId) 564 { 565 Map<String, Object> result = new HashMap<>(); 566 Map<String, Object> organiserMap = new HashMap<>(); 567 568 try 569 { 570 JCRCalendarEvent event = (JCRCalendarEvent) _resolver.resolveById(eventId); 571 UserIdentity organiserIdentity = event.getOrganiser(); 572 573 organiserMap.put("login", organiserIdentity.getLogin()); 574 organiserMap.put("populationId", organiserIdentity.getPopulationId()); 575 576 User organiser = _userManager.getUser(organiserIdentity); 577 organiserMap.put("sortablename", organiser.getSortableName()); 578 organiserMap.put("fullname", organiser.getFullName()); 579 } 580 catch (Exception e) 581 { 582 getLogger().error("An error occurred getting organiser for event " + eventId, e); 583 } 584 585 result.put("user", organiserMap); 586 587 return result; 588 } 589 590 /** 591 * Get user from the user parameter 592 * @param userMap the user map from parameters 593 * @return the user 594 */ 595 protected UserIdentity _getUserFromParameter(Map<String, Object> userMap) 596 { 597 if (userMap != null) 598 { 599 String login = (String) userMap.get("login"); 600 String populationId = (String) userMap.get("populationId"); 601 602 return new UserIdentity(login, populationId); 603 } 604 605 return null; 606 } 607 608 /** 609 * Get attendees list from the attendees parameter 610 * @param attendeesList the attendees list from parameters 611 * @return the attendees list 612 */ 613 protected List<CalendarEventAttendee> _getAttendeeListFromParameter(List<Map<String, Object>> attendeesList) 614 { 615 if (attendeesList != null) 616 { 617 List<CalendarEventAttendee> attendees = new ArrayList<>(); 618 for (Map<String, Object> attendee : attendeesList) 619 { 620 CalendarEventAttendee calendarEventAttendee = new CalendarEventAttendee(); 621 boolean isExternal = (boolean) attendee.get("external"); 622 if (isExternal) 623 { 624 calendarEventAttendee.setEmail((String) attendee.get("email")); 625 } 626 else 627 { 628 calendarEventAttendee.setPopulationId((String) attendee.get("populationId")); 629 calendarEventAttendee.setLogin((String) attendee.get("login")); 630 } 631 632 calendarEventAttendee.setIsExternal(isExternal); 633 calendarEventAttendee.setIsMandatory((boolean) attendee.get("mandatory")); 634 635 attendees.add(calendarEventAttendee); 636 } 637 return attendees; 638 } 639 640 return null; 641 } 642 643}