001/*
002 *  Copyright 2017 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.exchange;
017
018import java.net.URI;
019import java.net.URISyntaxException;
020import java.util.ArrayList;
021import java.util.Calendar;
022import java.util.Collection;
023import java.util.Collections;
024import java.util.Date;
025import java.util.GregorianCalendar;
026import java.util.HashMap;
027import java.util.List;
028import java.util.Map;
029import java.util.Set;
030import java.util.TimeZone;
031
032import org.apache.avalon.framework.service.ServiceException;
033import org.apache.avalon.framework.service.ServiceManager;
034import org.apache.commons.lang3.EnumUtils;
035import org.apache.commons.lang3.StringUtils;
036import org.joda.time.DateTime;
037import org.jsoup.Jsoup;
038
039import org.ametys.core.user.CurrentUserProvider;
040import org.ametys.core.user.User;
041import org.ametys.core.user.UserIdentity;
042import org.ametys.core.user.UserManager;
043import org.ametys.core.util.DateUtils;
044import org.ametys.plugins.explorer.calendars.EventRecurrenceTypeEnum;
045import org.ametys.plugins.messagingconnector.AbstractMessagingConnector;
046import org.ametys.plugins.messagingconnector.CalendarEvent;
047import org.ametys.plugins.messagingconnector.EmailMessage;
048import org.ametys.plugins.messagingconnector.MessagingConnectorException;
049import org.ametys.runtime.config.Config;
050
051import microsoft.exchange.webservices.data.core.ExchangeService;
052import microsoft.exchange.webservices.data.core.enumeration.availability.AvailabilityData;
053import microsoft.exchange.webservices.data.core.enumeration.misc.ConnectingIdType;
054import microsoft.exchange.webservices.data.core.enumeration.misc.ExchangeVersion;
055import microsoft.exchange.webservices.data.core.enumeration.property.LegacyFreeBusyStatus;
056import microsoft.exchange.webservices.data.core.enumeration.property.MeetingResponseType;
057import microsoft.exchange.webservices.data.core.enumeration.property.WellKnownFolderName;
058import microsoft.exchange.webservices.data.core.enumeration.property.time.DayOfTheWeek;
059import microsoft.exchange.webservices.data.core.enumeration.search.LogicalOperator;
060import microsoft.exchange.webservices.data.core.enumeration.service.ConflictResolutionMode;
061import microsoft.exchange.webservices.data.core.enumeration.service.DeleteMode;
062import microsoft.exchange.webservices.data.core.enumeration.service.SendInvitationsMode;
063import microsoft.exchange.webservices.data.core.enumeration.service.SendInvitationsOrCancellationsMode;
064import microsoft.exchange.webservices.data.core.enumeration.service.ServiceResult;
065import microsoft.exchange.webservices.data.core.exception.service.remote.ServiceRequestException;
066import microsoft.exchange.webservices.data.core.exception.service.remote.ServiceResponseException;
067import microsoft.exchange.webservices.data.core.response.AttendeeAvailability;
068import microsoft.exchange.webservices.data.core.service.folder.CalendarFolder;
069import microsoft.exchange.webservices.data.core.service.folder.Folder;
070import microsoft.exchange.webservices.data.core.service.item.Appointment;
071import microsoft.exchange.webservices.data.core.service.item.Item;
072import microsoft.exchange.webservices.data.core.service.schema.EmailMessageSchema;
073import microsoft.exchange.webservices.data.credential.ExchangeCredentials;
074import microsoft.exchange.webservices.data.credential.WebCredentials;
075import microsoft.exchange.webservices.data.misc.ImpersonatedUserId;
076import microsoft.exchange.webservices.data.misc.availability.AttendeeInfo;
077import microsoft.exchange.webservices.data.misc.availability.GetUserAvailabilityResults;
078import microsoft.exchange.webservices.data.misc.availability.TimeWindow;
079import microsoft.exchange.webservices.data.property.complex.Attendee;
080import microsoft.exchange.webservices.data.property.complex.AttendeeCollection;
081import microsoft.exchange.webservices.data.property.complex.FolderId;
082import microsoft.exchange.webservices.data.property.complex.ItemId;
083import microsoft.exchange.webservices.data.property.complex.Mailbox;
084import microsoft.exchange.webservices.data.property.complex.MessageBody;
085import microsoft.exchange.webservices.data.property.complex.recurrence.pattern.Recurrence;
086import microsoft.exchange.webservices.data.property.complex.time.TimeZoneDefinition;
087import microsoft.exchange.webservices.data.search.CalendarView;
088import microsoft.exchange.webservices.data.search.FindItemsResults;
089import microsoft.exchange.webservices.data.search.ItemView;
090import microsoft.exchange.webservices.data.search.filter.SearchFilter;
091import microsoft.exchange.webservices.data.util.TimeZoneUtils;
092
093/**
094 * 
095 * The connector used by the messaging connector plugin when the exchange mail
096 * server is used. Implements the methods of the MessagingConnector interface in
097 * order to get the informations from the mail server
098 *
099 */
100public class ExchangeConnector extends AbstractMessagingConnector
101{
102    /** The current user provider */
103    protected CurrentUserProvider _currentUserProvider;
104
105    /** The user manager */
106    private UserManager _userManager;
107
108    @Override
109    public void service(ServiceManager manager) throws ServiceException
110    {
111        super.service(manager);
112        _userManager = (UserManager) manager.lookup(UserManager.ROLE);
113        _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE);
114    }
115
116    /**
117     * Get the service of connexion to the server exchange
118     * @param userIdentity The user identity
119     * @return the service
120     * @throws URISyntaxException if an error occurred
121     */
122    protected ExchangeService getService(UserIdentity userIdentity) throws URISyntaxException
123    {
124        if (userIdentity == null)
125        {
126            return null;
127        }
128        
129        String userName = Config.getInstance().getValueAsString("org.ametys.plugins.exchange.username");
130        String password = Config.getInstance().getValueAsString("org.ametys.plugins.exchange.password");
131        String url = Config.getInstance().getValueAsString("org.ametys.plugins.exchange.url");
132
133        ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2010_SP2);
134        ExchangeCredentials credentials = new WebCredentials(userName, password);
135        service.setCredentials(credentials);
136
137        String authMethod = Config.getInstance().getValueAsString("org.ametys.plugins.exchange.authmethod");
138
139        if ("email".equals(authMethod))
140        {
141            User user = _userManager.getUser(userIdentity);
142            String email = user.getEmail();
143            if (StringUtils.isBlank(email))
144            {
145                if (getLogger().isWarnEnabled())
146                {
147                    getLogger().warn("The user '" + userIdentity.getLogin() + "' has no email address set, thus exchange cannot be contacted using 'email' authentication method");
148                }
149                return null;
150            }
151            service.setImpersonatedUserId(new ImpersonatedUserId(ConnectingIdType.SmtpAddress, email));
152        }
153        else
154        {
155            service.setImpersonatedUserId(new ImpersonatedUserId(ConnectingIdType.PrincipalName, userIdentity.getLogin()));
156        }
157
158        service.setUrl(new URI(url));
159        return service;
160    }
161
162    @Override
163    protected List<CalendarEvent> internalGetEvents(UserIdentity userIdentity, Date fromDate, Date untilDate, int maxEvents) throws MessagingConnectorException
164    {
165        try
166        {        
167            List<CalendarEvent> calendar = new ArrayList<>();
168            ExchangeService service = getService(userIdentity);
169
170            if (service != null)
171            {
172             // The search filter to get futur or not terminated events
173                CalendarFolder cf = CalendarFolder.bind(service, WellKnownFolderName.Calendar);
174    
175                int maxDays = (int) DateUtils.asLocalDate(untilDate).toEpochDay() - (int) DateUtils.asLocalDate(fromDate).toEpochDay();
176                GregorianCalendar gc = new GregorianCalendar();
177                gc.add(Calendar.DATE, maxDays != 0 ? maxDays : 1000);
178                CalendarView calendarView = new CalendarView(new Date(), gc.getTime());
179                FindItemsResults<Appointment> findResultsEvent = cf.findAppointments(calendarView);
180    
181                calendarView.setMaxItemsReturned(maxEvents > 0 ? maxEvents : null);
182                findResultsEvent = cf.findAppointments(calendarView);
183    
184                for (Appointment event : findResultsEvent.getItems())
185                {
186                    CalendarEvent newEvent = new CalendarEvent();
187                    newEvent.setStartDate(event.getStart());
188                    newEvent.setEndDate(event.getEnd());
189                    newEvent.setSubject(event.getSubject());
190                    newEvent.setLocation(event.getLocation());
191                    calendar.add(newEvent);
192                }
193                
194            }
195            return calendar;
196        }
197        catch (Exception e)
198        {
199            throw new MessagingConnectorException("Failed to get the events for user " + userIdentity.toString(), e);
200        }
201    }
202    
203    @Override
204    protected int internalGetEventsCount(UserIdentity userIdentity, Date fromDate, Date untilDate) throws MessagingConnectorException
205    {
206        try
207        {
208            int nextEventsCount = 0;
209            ExchangeService service = getService(userIdentity);
210            if (service != null)
211            {
212             // The search filter to get futur or not terminated events
213                CalendarFolder cf = CalendarFolder.bind(service, WellKnownFolderName.Calendar);
214    
215                int maxDays = (int) DateUtils.asLocalDate(untilDate).toEpochDay() - (int) DateUtils.asLocalDate(fromDate).toEpochDay();
216                GregorianCalendar gc = new GregorianCalendar();
217                gc.add(Calendar.DATE, maxDays != 0 ? maxDays : 1000);
218                CalendarView calendarView = new CalendarView(new Date(), gc.getTime());
219                FindItemsResults<Appointment> findResultsEvent = cf.findAppointments(calendarView);
220                nextEventsCount = findResultsEvent.getTotalCount();
221            }
222            return nextEventsCount;
223        }
224        catch (Exception e)
225        {
226            throw new MessagingConnectorException("Failed to get the events count for user " + userIdentity.toString(), e);
227        }
228    }
229    
230    @Override
231    protected List<EmailMessage> internalGetEmails(UserIdentity userIdentity, int maxEmails) throws MessagingConnectorException
232    {
233        try
234        {
235            List<EmailMessage> mailMessage = new ArrayList<>();
236            
237            ExchangeService service = getService(userIdentity);
238
239            if (service != null)
240            {
241                // The search filter to get unread email
242                SearchFilter sf = new SearchFilter.SearchFilterCollection(LogicalOperator.And, new SearchFilter.IsEqualTo(EmailMessageSchema.IsRead, false));
243                ItemView view = new ItemView(maxEmails);
244                FindItemsResults<Item> findResultsMail = service.findItems(WellKnownFolderName.Inbox, sf, view);
245    
246                List<Item> messagesReceived = findResultsMail.getItems();
247                for (Item message : messagesReceived)
248                {
249                    message.load();
250    
251                    EmailMessage newMessage = new EmailMessage();
252                    newMessage.setSender(((microsoft.exchange.webservices.data.core.service.item.EmailMessage) message).getSender().getAddress());
253                    if (message.getSubject() != null)
254                    {
255                        newMessage.setSubject(message.getSubject());
256                    }
257                    if (message.getBody() != null)
258                    {
259                        newMessage.setSummary(html2text(message.getBody().toString()));
260                    }
261                    mailMessage.add(newMessage);
262                }
263            }
264            return mailMessage;
265        }
266        catch (Exception e)
267        {
268            throw new MessagingConnectorException("Failed to get the emails for user " + userIdentity.toString(), e);
269        }
270        
271    }
272    
273    @Override
274    protected int internalGetEmailsCount(UserIdentity userIdentity) throws MessagingConnectorException
275    {
276        try
277        {
278            int emailsCount = 0;
279            
280            ExchangeService service = getService(userIdentity);
281
282            if (service != null)
283            {
284                // The search filter to get unread email
285                SearchFilter sf = new SearchFilter.SearchFilterCollection(LogicalOperator.And, new SearchFilter.IsEqualTo(EmailMessageSchema.IsRead, false));
286                ItemView view = new ItemView(20);
287                FindItemsResults<Item> findResultsMail = service.findItems(WellKnownFolderName.Inbox, sf, view);
288                
289                emailsCount = findResultsMail.getTotalCount();
290            }
291            return emailsCount;
292        }
293        catch (Exception e)
294        {
295            throw new MessagingConnectorException("Failed to get the emails for user " + userIdentity.toString(), e);
296        }
297    }
298
299    @Override
300    public boolean supportInvitation() throws MessagingConnectorException
301    {
302        return true;
303    }
304
305    @Override
306    public boolean isEventExist(String eventId, UserIdentity organiser) throws MessagingConnectorException
307    {
308        try
309        {
310            ExchangeService service = getService(organiser);
311            if (service != null)
312            {
313                ItemId itemId = new ItemId(eventId);
314                Appointment appointment = Appointment.bind(service, itemId);
315                
316                return appointment != null;
317            }
318        }
319        catch (ServiceResponseException e)
320        {
321            return false; //Exchange doesn't find the event with id 'event Id'
322        }
323        catch (Exception e) 
324        {
325            throw new MessagingConnectorException("Failed to get event " + eventId + " from organiser " + organiser.toString(), e);
326        }
327        
328        return false;
329    }
330    
331    @Override
332    public String createEvent(String title, String description, String place, boolean isAllDay, Date startDate, Date endDate, EventRecurrenceTypeEnum recurrenceType, Date untilDate, Map<String, Boolean> attendees, UserIdentity organiser) throws MessagingConnectorException
333    {
334        try
335        {
336            ExchangeService service = getService(organiser);
337            if (service != null)
338            {
339                Appointment appointment = new Appointment(service);
340                
341                _setDataEvent(service, appointment, title, description, place, isAllDay, startDate, endDate, recurrenceType, untilDate, attendees);
342                
343                User organiserUser = _userManager.getUser(organiser);
344                Mailbox mailBox = new Mailbox(organiserUser.getEmail());
345                appointment.save(new FolderId(WellKnownFolderName.Calendar, mailBox), SendInvitationsMode.SendOnlyToAll);
346                
347                return appointment.getId().getUniqueId();
348            }
349        }
350        catch (Exception e)
351        {
352            throw new MessagingConnectorException("Failed to create event from organiser " + organiser.toString(), e);
353        }
354            
355        return null;
356    }
357    
358    @Override
359    public void updateEvent(String eventId, String title, String description, String place, boolean isAllDay, Date startDate, Date endDate, EventRecurrenceTypeEnum recurrenceType, Date untilDate, Map<String, Boolean> attendees, UserIdentity organiser) throws MessagingConnectorException
360    {
361        try
362        {
363            ExchangeService service = getService(organiser);
364            if (service != null)
365            {
366                ItemId itemId = new ItemId(eventId);
367                Appointment appointment = Appointment.bind(service, itemId);
368
369                _setDataEvent(service, appointment, title, description, place, isAllDay, startDate, endDate, recurrenceType, untilDate, attendees);
370                
371                appointment.update(ConflictResolutionMode.AlwaysOverwrite, SendInvitationsOrCancellationsMode.SendOnlyToAll);
372            }
373        }
374        catch (Exception e)
375        {
376            throw new MessagingConnectorException("Failed to update event from organiser " + organiser.toString(), e);
377        }
378    }
379    
380    private void _setDataEvent(ExchangeService service, Appointment appointment, String title, String description, String place, boolean isAllDay, Date startDate, Date endDate, EventRecurrenceTypeEnum recurrenceType, Date untilDate, Map<String, Boolean> attendees) throws Exception
381    {
382        TimeZone defaultTimeZone = TimeZone.getDefault();
383        Map<String, String> olsonTimeZoneToMsMap = TimeZoneUtils.createOlsonTimeZoneToMsMap();
384        String msTimeZoneId = olsonTimeZoneToMsMap.get(defaultTimeZone.getID());
385
386        Collection<TimeZoneDefinition> serverTimeZones = service.getServerTimeZones(Collections.singletonList(msTimeZoneId));
387        TimeZoneDefinition timeZone = serverTimeZones.iterator().next();
388        
389        appointment.setSubject(title);
390        appointment.setBody(new MessageBody(description));
391        appointment.setStart(startDate);
392        if (isAllDay)
393        {
394            DateTime date = new DateTime(endDate);
395            appointment.setEnd(date.plusDays(1).toDate());
396        }
397        else
398        {
399            appointment.setEnd(endDate);
400        }
401        appointment.setIsAllDayEvent(isAllDay);
402        appointment.setLocation(place);
403        appointment.setStartTimeZone(timeZone);
404        appointment.setEndTimeZone(timeZone);
405        
406        _setRecurrence(appointment, startDate, recurrenceType, untilDate);
407        
408        _setAttendees(appointment, attendees);
409    }
410    
411    private void _setRecurrence(Appointment appointment, Date startDate, EventRecurrenceTypeEnum recurrenceType, Date untilDate) throws Exception
412    {
413        Recurrence recurrence = null;
414        switch (recurrenceType)
415        {
416            case ALL_DAY:
417                recurrence = new Recurrence.DailyPattern(startDate, 1);
418                break;
419            case ALL_WORKING_DAY:
420                String workingDayAsString = Config.getInstance().getValueAsString("org.ametys.plugins.explorer.calendar.event.working.day");
421                
422                List<DayOfTheWeek> days = new ArrayList<>();
423                for (String idDay : StringUtils.split(workingDayAsString, ","))
424                {
425                    days.add(EnumUtils.getEnumList(DayOfTheWeek.class).get(Integer.parseInt(idDay) - 1));
426                }
427                
428                recurrence = new Recurrence.WeeklyPattern(startDate, 1, days.toArray(new DayOfTheWeek[days.size()]));
429                break;
430            case WEEKLY:
431                DateTime startWeeklyDateTime = new DateTime(startDate);
432                int dayOfWeekForWeekly = startWeeklyDateTime.getDayOfWeek();
433                
434                recurrence = new Recurrence.WeeklyPattern(startDate, 1, EnumUtils.getEnumList(DayOfTheWeek.class).get(dayOfWeekForWeekly % 7));
435                break;
436            case BIWEEKLY:
437                DateTime startBiWeeklyDateTime = new DateTime(startDate);
438                int dayOfWeekForBiWeekly = startBiWeeklyDateTime.getDayOfWeek();
439                
440                recurrence = new Recurrence.WeeklyPattern(startDate, 2, EnumUtils.getEnumList(DayOfTheWeek.class).get(dayOfWeekForBiWeekly % 7));
441                break;
442            case MONTHLY:
443                DateTime startMonthlyDateTime = new DateTime(startDate);
444                
445                recurrence = new Recurrence.MonthlyPattern(startDate, 1, startMonthlyDateTime.getDayOfMonth());
446                break;
447            case NEVER:
448            default:
449                //Still null
450                break;
451        }
452
453        if (untilDate != null && recurrence != null)
454        {
455            recurrence.setEndDate(untilDate);
456            appointment.setRecurrence(recurrence);
457        }
458    }
459
460    @Override
461    public void deleteEvent(String eventId, UserIdentity organiser) throws MessagingConnectorException
462    {
463        try
464        {
465            ExchangeService service = getService(organiser);
466            if (service != null)
467            {
468                ItemId itemId = new ItemId(eventId);
469                Appointment appointment = Appointment.bind(service, itemId);
470                appointment.delete(DeleteMode.MoveToDeletedItems);
471            }
472        }
473        catch (Exception e)
474        {
475            throw new MessagingConnectorException("Failed to delete event " + eventId + " with organiser " + organiser.toString(), e);
476        }
477            
478    }
479
480    @Override
481    public Map<String, AttendeeInformation> getAttendees(String eventId, UserIdentity organiser) throws MessagingConnectorException
482    {
483        Map<String, AttendeeInformation> attendees = new HashMap<>();
484        try
485        {
486            ExchangeService service = getService(organiser);
487            if (service != null)
488            {
489                ItemId itemId = new ItemId(eventId);
490                Appointment appointment = Appointment.bind(service, itemId);
491                
492                for (Attendee attendee : appointment.getRequiredAttendees())
493                {
494                    ResponseType responseStatus = _getResponseStatus(attendee.getResponseType());
495                    AttendeeInformation attendeeInformation = new AttendeeInformation(true, responseStatus);
496                    attendees.put(attendee.getAddress(), attendeeInformation);
497                }
498                
499                for (Attendee attendee : appointment.getOptionalAttendees())
500                {
501                    ResponseType responseStatus = _getResponseStatus(attendee.getResponseType());
502                    AttendeeInformation attendeeInformation = new AttendeeInformation(false, responseStatus);
503                    attendees.put(attendee.getAddress(), attendeeInformation);
504                }
505            }
506        }
507        catch (ServiceResponseException e)
508        {
509            return attendees; //Exchange doesn't find the event with id 'event Id'
510        }
511        catch (Exception e)
512        {
513            throw new MessagingConnectorException("Failed to get attendees from event " + eventId + " with organiser " + organiser.toString(), e);
514        }
515        
516        return attendees;
517    }
518
519    @Override
520    public void setAttendees(String eventId, Map<String, Boolean> attendees, UserIdentity organiser) throws MessagingConnectorException
521    {
522        try
523        {
524            ExchangeService service = getService(organiser);
525            if (service != null)
526            {
527                ItemId itemId = new ItemId(eventId);
528                Appointment appointment = Appointment.bind(service, itemId);
529                
530                _setAttendees(appointment, attendees);
531                
532                appointment.update(ConflictResolutionMode.AlwaysOverwrite, SendInvitationsOrCancellationsMode.SendOnlyToChanged);
533            }
534        }
535        catch (Exception e)
536        {
537            throw new MessagingConnectorException("Failed to get attendees from event " + eventId + " with organiser " + organiser.toString(), e);
538        }
539    }
540
541    @Override
542    public Map<String, FreeBusyStatus> getFreeBusy(Date startDate, Date endDate, boolean isAllDay, Set<String> attendees, UserIdentity organiser) throws MessagingConnectorException
543    {
544        Map<String, FreeBusyStatus> attendeesMap = new HashMap<>();
545        if (attendees.isEmpty())
546        {
547            return attendeesMap;
548        }
549        
550        try
551        {
552            ExchangeService service = getService(organiser);
553            if (service != null)
554            {
555                TimeWindow timeWindow = null;
556                if (isAllDay)
557                {
558                    DateTime endDateTime = new DateTime(endDate);
559                    timeWindow = new TimeWindow(startDate, endDateTime.plusDays(1).toDate());
560                }
561                else
562                {
563                    DateTime startDateTime = new DateTime(startDate);
564                    DateTime endDateTime = new DateTime(endDate);
565                    timeWindow = new TimeWindow(startDateTime.minusDays(1).toDate(), endDateTime.plusDays(1).toDate());
566                }
567
568                List<AttendeeInfo> attendeesInfo = new ArrayList<>();
569                for (String email : attendees)
570                {
571                    attendeesInfo.add(new AttendeeInfo(email));
572                }
573                
574                GetUserAvailabilityResults userAvailability = service.getUserAvailability(attendeesInfo, timeWindow, AvailabilityData.FreeBusy);
575                int index = 0;
576                for (AttendeeAvailability availability : userAvailability.getAttendeesAvailability())
577                {
578                    AttendeeInfo attendeeInfo = attendeesInfo.get(index);
579                    String email = attendeeInfo.getSmtpAddress();
580                    
581                    FreeBusyStatus freeBusyStatus = FreeBusyStatus.Unknown;
582                    if (!ServiceResult.Error.equals(availability.getResult()))
583                    {
584                        freeBusyStatus = FreeBusyStatus.Free;
585                        for (microsoft.exchange.webservices.data.property.complex.availability.CalendarEvent calEvent : availability.getCalendarEvents())
586                        {
587                            if (isAllDay)
588                            {
589                                if (calEvent.getFreeBusyStatus().equals(LegacyFreeBusyStatus.Busy))
590                                {
591                                    freeBusyStatus = FreeBusyStatus.Busy;
592                                }
593                            }
594                            else
595                            {
596                                if (calEvent.getFreeBusyStatus().equals(LegacyFreeBusyStatus.Busy) && startDate.before(calEvent.getEndTime()) && endDate.after(calEvent.getStartTime()))
597                                {
598                                    freeBusyStatus = FreeBusyStatus.Busy;
599                                }
600                            }
601                        }
602                    }
603                    
604                    attendeesMap.put(email, freeBusyStatus);
605                    index++;
606                }
607            }
608        }
609        catch (Exception e)
610        {
611            throw new MessagingConnectorException("Failed to get free/busy with organiser " + organiser.toString(), e);
612        }
613        
614        return attendeesMap;
615    }
616    
617    @Override
618    public boolean isUserExist(UserIdentity userIdentity) throws MessagingConnectorException
619    {
620        try
621        {
622            ExchangeService service = getService(userIdentity);
623            if (service != null)
624            {
625                Folder.bind(service, WellKnownFolderName.Inbox);
626                return true;
627            }
628            
629            return false;
630        }
631        catch (ServiceRequestException e)
632        {
633            return false;
634        }
635        catch (Exception e)
636        {
637            throw new MessagingConnectorException("Failed to know if user " + userIdentity.getLogin() + " exist in exchange", e);
638        }
639    }
640    
641    private ResponseType _getResponseStatus(MeetingResponseType meetingResponseType)
642    {
643        switch (meetingResponseType)
644        {
645            case Accept:
646                return ResponseType.Accept;
647            case Decline:
648                return ResponseType.Decline;
649            case Tentative:
650                return ResponseType.Maybe;
651            default:
652                return ResponseType.Unknown;
653        }
654    }
655    
656    private void _setAttendees(Appointment appointment, Map<String, Boolean> attendees) throws Exception
657    {
658        if (attendees != null)
659        {
660            AttendeeCollection requiredAttendees = appointment.getRequiredAttendees();
661            AttendeeCollection optionalAttendees = appointment.getOptionalAttendees();
662            
663            requiredAttendees.clear();
664            optionalAttendees.clear();
665            for (String email : attendees.keySet())
666            {
667                boolean isMandatory = attendees.get(email);
668                if (isMandatory)
669                {
670                    requiredAttendees.add(new Attendee(email));
671                }
672                else
673                {
674                    optionalAttendees.add(new Attendee(email));
675                }
676            }
677        }
678    }
679    
680    /**
681     * Converts a given html String into a plain text String
682     * @param html the html String that will be converted
683     * @return a String plain text of the given html
684     */
685    protected static String html2text(String html)
686    {
687        return Jsoup.parse(html).text();
688    }
689}