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}