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}