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