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