package org.exoplatform.extension.exchange.service; import java.io.Serializable; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.GregorianCalendar; import java.util.Iterator; import java.util.List; import java.util.TimeZone; import javax.jcr.Node; import javax.jcr.NodeIterator; import javax.jcr.query.Query; import javax.jcr.query.QueryManager; import javax.jcr.query.QueryResult; import microsoft.exchange.webservices.data.Appointment; import microsoft.exchange.webservices.data.AppointmentSchema; import microsoft.exchange.webservices.data.BasePropertySet; import microsoft.exchange.webservices.data.Folder; import microsoft.exchange.webservices.data.PropertySet; import org.exoplatform.calendar.service.Calendar; import org.exoplatform.calendar.service.CalendarEvent; import org.exoplatform.calendar.service.CalendarService; import org.exoplatform.calendar.service.impl.CalendarServiceImpl; import org.exoplatform.calendar.service.impl.JCRDataStorage; import org.exoplatform.calendar.util.Constants; import org.exoplatform.commons.utils.ISO8601; import org.exoplatform.extension.exchange.listener.CalendarCreateUpdateAction; import org.exoplatform.extension.exchange.service.util.CalendarConverterService; import org.exoplatform.services.jcr.ext.common.SessionProvider; import org.exoplatform.services.log.ExoLogger; import org.exoplatform.services.log.Log; import org.exoplatform.services.organization.OrganizationService; /** * * @author Boubaker Khanfir * */ public class ExoStorageService implements Serializable { private static final long serialVersionUID = 6614108102985034995L; private final static Log LOG = ExoLogger.getLogger(ExoStorageService.class); private final static DateFormat EXCLUDE_ID_FORMAT_FIRST_CHARS = new SimpleDateFormat("yyyyMMdd"); private JCRDataStorage storage; private OrganizationService organizationService; private CorrespondenceService correspondenceService; public ExoStorageService(OrganizationService organizationService, CalendarService calendarService, CorrespondenceService correspondenceService) { this.storage = ((CalendarServiceImpl) calendarService).getDataStorage(); this.organizationService = organizationService; this.correspondenceService = correspondenceService; } /** * * Deletes eXo Calendar Event that corresponds to given appointment Id. * * @param appointmentId * @param username * @throws Exception */ public void deleteEventByAppointmentID(String appointmentId, String username) throws Exception { CalendarEvent calendarEvent = getEventByAppointmentId(username, appointmentId); if (calendarEvent != null) { deleteEvent(username, calendarEvent); } } /** * * Deletes eXo Calendar Event. * * @param username * @param calendarEvent * @throws Exception */ public void deleteEvent(String username, CalendarEvent calendarEvent) throws Exception { if (calendarEvent == null) { LOG.warn("Event is null, can't delete it for username: " + username); return; } if ((calendarEvent.getRepeatType() == null || calendarEvent.getRepeatType().equals(CalendarEvent.RP_NOREPEAT)) && (calendarEvent.getIsExceptionOccurrence() == null || !calendarEvent.getIsExceptionOccurrence())) { if (LOG.isTraceEnabled()) { LOG.trace("Delete user calendar event: " + calendarEvent.getSummary()); } storage.removeUserEvent(username, calendarEvent.getCalendarId(), calendarEvent.getId()); // Remove correspondence between exo and exchange IDs correspondenceService.deleteCorrespondingId(username, calendarEvent.getId()); } else if (calendarEvent.getIsExceptionOccurrence() != null && calendarEvent.getIsExceptionOccurrence()) { if (LOG.isTraceEnabled()) { LOG.trace("Delete user calendar event exceptional occurence: " + calendarEvent.getSummary() + ", id=" + calendarEvent.getRecurrenceId()); } storage.removeUserEvent(username, calendarEvent.getCalendarId(), calendarEvent.getId()); correspondenceService.deleteCorrespondingId(username, calendarEvent.getId()); } else if (calendarEvent.getRecurrenceId() != null && !calendarEvent.getRecurrenceId().isEmpty()) { if (LOG.isTraceEnabled()) { LOG.trace("Delete user calendar event occurence from series: " + calendarEvent.getSummary() + " with id : " + calendarEvent.getRecurrenceId()); } storage.removeOccurrenceInstance(username, calendarEvent); } else { if (LOG.isTraceEnabled()) { LOG.trace("Delete user calendar event series: " + calendarEvent.getSummary()); } storage.removeRecurrenceSeries(username, calendarEvent); // Remove correspondence between exo and exchange IDs correspondenceService.deleteCorrespondingId(username, calendarEvent.getId()); } } /** * * Delete eXo Calendar. * * @param username * @param folderId * @return * @throws Exception */ public boolean deleteCalendar(String username, String folderId) throws Exception { String calendarId = correspondenceService.getCorrespondingId(username, folderId); if (calendarId == null) { calendarId = CalendarConverterService.getCalendarId(folderId); } List<CalendarEvent> events = getUserCalendarEvents(username, folderId); if (events == null) { return false; } for (CalendarEvent calendarEvent : events) { correspondenceService.deleteCorrespondingId(username, calendarEvent.getId()); } storage.removeUserCalendar(username, calendarId); correspondenceService.deleteCorrespondingId(username, folderId, calendarId); return true; } /** * * Gets User Calendar identified by Exchange folder Id. * * @param username * @param folderId * @return * @throws Exception */ public Calendar getUserCalendar(String username, String folderId) throws Exception { return getUserCalendar(username, folderId, true); } /** * * Gets User Calendar identified by Exchange folder Id. * * @param username * @param folderId * @param deleteIfCorrespondentExists * @return * @throws Exception */ public Calendar getUserCalendar(String username, String folderId, boolean deleteIfCorrespondentExists) throws Exception { String calendarId = correspondenceService.getCorrespondingId(username, folderId); Calendar calendar = null; if (calendarId != null) { calendar = storage.getUserCalendar(username, calendarId); if (calendar == null && deleteIfCorrespondentExists) { correspondenceService.deleteCorrespondingId(username, folderId); } } return calendar; } /** * * Gets User Calendar identified by Exchange folder Id, or creates it if not * existing. * * @param username * @param folderId * @return * @throws Exception */ public Calendar getOrCreateUserCalendar(String username, Folder folder) throws Exception { Calendar calendar = getUserCalendar(username, folder.getId().getUniqueId(), false); String calendarId = CalendarConverterService.getCalendarId(folder.getId().getUniqueId()); if (calendar == null) { Calendar tmpCalendar = storage.getUserCalendar(username, calendarId); if (tmpCalendar != null) { // Renew Calendar storage.removeUserCalendar(username, calendarId); } } if (calendar == null) { if (LOG.isTraceEnabled()) { LOG.trace("Create user calendar from Exchange: " + folder.getDisplayName()); } calendar = new Calendar(); calendar.setId(calendarId); calendar.setName(CalendarConverterService.getCalendarName(folder.getDisplayName())); calendar.setCalendarOwner(username); calendar.setDataInit(false); calendar.setEditPermission(new String[] { "any read" }); calendar.setCalendarColor(Constants.COLORS[(int) (Math.random() * Constants.COLORS.length)]); storage.saveUserCalendar(username, calendar, true); // Set IDs correspondence correspondenceService.setCorrespondingId(username, calendar.getId(), folder.getId().getUniqueId()); } return calendar; } /** * * Gets Events from User Calendar identified by Exchange folder Id. * * @param username * @param folderId * @return * @throws Exception */ public List<CalendarEvent> getUserCalendarEvents(String username, String folderId) throws Exception { List<CalendarEvent> userEvents = null; String calendarId = correspondenceService.getCorrespondingId(username, folderId); if (calendarId == null) { calendarId = CalendarConverterService.getCalendarId(folderId); } Calendar calendar = storage.getUserCalendar(username, calendarId); if (calendar != null) { List<String> calendarIds = new ArrayList<String>(); calendarIds.add(calendarId); userEvents = storage.getUserEventByCalendar(username, calendarIds); } return userEvents; } /** * * Updates existing eXo Calendar Event. * * @param appointment * @param folder * @param username * @param timeZone * @throws Exception */ public List<CalendarEvent> updateEvent(Appointment appointment, String username, TimeZone timeZone) throws Exception { return createOrUpdateEvent(appointment, username, false, timeZone); } /** * * Create non existing eXo Calendar Event. * * @param appointment * @param folder * @param username * @param timeZone * @throws Exception */ public List<CalendarEvent> createEvent(Appointment appointment, String username, TimeZone timeZone) throws Exception { return createOrUpdateEvent(appointment, username, true, timeZone); } /** * * Creates or updates eXo Calendar Event. * * @param appointment * @param folder * @param username * @param timeZone * @return * @throws Exception */ public List<CalendarEvent> createOrUpdateEvent(Appointment appointment, String username, TimeZone timeZone) throws Exception { boolean isNew = correspondenceService.getCorrespondingId(username, appointment.getId().getUniqueId()) == null; if (!isNew) { CalendarEvent event = getEventByAppointmentId(username, appointment.getId().getUniqueId()); if (event == null) { isNew = true; correspondenceService.deleteCorrespondingId(username, appointment.getId().getUniqueId()); } } return createOrUpdateEvent(appointment, username, isNew, timeZone); } /** * * @param username * @param appointmentId * @return * @throws Exception */ public CalendarEvent getEventByAppointmentId(String username, String appointmentId) throws Exception { String calEventId = correspondenceService.getCorrespondingId(username, appointmentId); CalendarEvent event = storage.getEvent(username, calEventId); if (event == null && calEventId != null) { correspondenceService.deleteCorrespondingId(username, appointmentId); } return event; } /** * * @param eventNode * @return * @throws Exception */ public CalendarEvent getExoEventByNode(Node eventNode) throws Exception { return storage.getEvent(eventNode); } /** * * @param uuid * @return * @throws Exception */ public String getExoEventMasterRecurenceByOriginalUUID(String uuid) throws Exception { Node node = storage.getSession(SessionProvider.createSystemProvider()).getNodeByUUID(uuid); if (node == null) { if (LOG.isTraceEnabled()) { LOG.trace("No original recurrent node was found with UUID: " + uuid); } return null; } else { return node.getName(); } } /** * * @param username * @param calendar * @return * @throws Exception */ public List<CalendarEvent> getAllExoEvents(String username, Calendar calendar) throws Exception { List<String> calendarIds = Collections.singletonList(calendar.getId()); return storage.getUserEventByCalendar(username, calendarIds); } /** * * @param username * @param calendar * @param date * @return * @throws Exception */ public List<CalendarEvent> findExoEventsModifiedSince(String username, Calendar calendar, Date date) throws Exception { Node calendarHome = storage.getUserCalendarHome(username); if (calendarHome.hasNode(calendar.getId())) { calendarHome = calendarHome.getNode(calendar.getId()); } java.util.Calendar dateCalendar = java.util.Calendar.getInstance(); dateCalendar.setTime(date); return getEventsByType(calendarHome, Calendar.TYPE_PRIVATE, dateCalendar); } private List<CalendarEvent> getEventsByType(Node calendarHome, int type, java.util.Calendar date) throws Exception { List<CalendarEvent> events = new ArrayList<CalendarEvent>(); QueryManager qm = calendarHome.getSession().getWorkspace().getQueryManager(); Query query = qm.createQuery("select * from exo:calendarEvent where (jcr:path like '" + calendarHome.getPath() + "/%') and (exo:lastModifiedDate > TIMESTAMP '" + ISO8601.format(date) + "')", Query.SQL); QueryResult result = query.execute(); NodeIterator it = result.getNodes(); CalendarEvent calEvent; while (it.hasNext()) { calEvent = storage.getEvent(it.nextNode()); calEvent.setCalType(String.valueOf(type)); events.add(calEvent); } return events; } public void updateModifiedDateOfEvent(String username, CalendarEvent event) throws Exception { Node node = storage.getCalendarEventNode(username, event.getCalType(), event.getCalendarId(), event.getId()); modifyUpdateDate(node); if (event.getOriginalReference() != null && !event.getOriginalReference().isEmpty()) { Node masterNode = storage.getSession(SessionProvider.createSystemProvider()).getNodeByUUID(event.getOriginalReference()); modifyUpdateDate(masterNode); } } private void modifyUpdateDate(Node node) throws Exception { if (!node.isNodeType("exo:datetime")) { if (node.canAddMixin("exo:datetime")) { node.addMixin("exo:datetime"); } node.setProperty("exo:dateCreated", new GregorianCalendar()); } node.setProperty("exo:dateModified", new GregorianCalendar()); node.save(); } private List<CalendarEvent> createOrUpdateEvent(Appointment appointment, String username, boolean isNew, TimeZone timeZone) throws Exception { Calendar calendar = getUserCalendar(username, appointment.getParentFolderId().getUniqueId()); if (calendar == null) { LOG.warn("Attempting to synchronize an event without existing associated eXo Calendar."); return null; } List<CalendarEvent> updatedEvents = new ArrayList<CalendarEvent>(); if (appointment.getAppointmentType() != null) { switch (appointment.getAppointmentType()) { case Single: { CalendarEvent event = null; if (isNew) { event = new CalendarEvent(); event.setCalendarId(calendar.getId()); updatedEvents.add(event); } else { event = getEventByAppointmentId(username, appointment.getId().getUniqueId()); updatedEvents.add(event); if (CalendarConverterService.verifyModifiedDatesConflict(event, appointment)) { if (LOG.isTraceEnabled()) { LOG.trace("Attempting to update eXo Event with Exchange Event, but modification date of eXo is after, ignore updating."); } return updatedEvents; } } if (LOG.isTraceEnabled()) { if (isNew) { LOG.trace("Create user calendar event: " + appointment.getSubject()); } else { LOG.trace("Update user calendar event: " + appointment.getSubject()); } } CalendarConverterService.convertExchangeToExoEvent(event, appointment, username, storage, organizationService.getUserHandler(), timeZone); event.setRepeatType(CalendarEvent.RP_NOREPEAT); CalendarCreateUpdateAction.IGNORE_UPDATE.set(true); try { storage.saveUserEvent(username, calendar.getId(), event, isNew); } finally { CalendarCreateUpdateAction.IGNORE_UPDATE.set(false); } correspondenceService.setCorrespondingId(username, event.getId(), appointment.getId().getUniqueId()); } break; case Exception: throw new IllegalStateException("The appointment is an exception occurence of this event >> '" + appointment.getSubject() + "'. start:" + appointment.getStart() + ", end : " + appointment.getEnd() + ", occurence: " + appointment.getAppointmentSequenceNumber()); case RecurringMaster: { // Master recurring event CalendarEvent masterEvent = null; Date orginialStartDate = null; if (isNew) { masterEvent = new CalendarEvent(); updatedEvents.add(masterEvent); } else { masterEvent = getEventByAppointmentId(username, appointment.getId().getUniqueId()); updatedEvents.add(masterEvent); orginialStartDate = masterEvent.getFromDateTime(); } // FIXME there is a bug in Exchange modification time of the server: // Adding a recurrent Item + delete last occurence => last modified // date isn't updated. So we test here if the last occurence was deleted // or not // // Begin workaround boolean isLastOccurenceDeleted = false; appointment = Appointment.bind(appointment.getService(), appointment.getId(), new PropertySet(AppointmentSchema.Recurrence)); if (appointment.getRecurrence().hasEnd()) { Date recEndDate = appointment.getRecurrence().getEndDate(); appointment = Appointment.bind(appointment.getService(), appointment.getId(), new PropertySet(BasePropertySet.FirstClassProperties)); if (recEndDate == null) { LOG.warn("Inconsistent data delivered by MS Exchange. The recurrent Event has end but end date is null: '" + appointment.getSubject() + "', start:" + appointment.getStart() + ", end : " + appointment.getEnd()); } else { Appointment tmpAppointment = Appointment.bind(appointment.getService(), appointment.getId(), new PropertySet(AppointmentSchema.LastOccurrence)); if (tmpAppointment.getLastOccurrence() == null) { LOG.warn("Can't find last occurence of recurrent Event : '" + appointment.getSubject() + "', start:" + appointment.getStart() + ", end : " + appointment.getEnd()); } else { isLastOccurenceDeleted = tmpAppointment.getLastOccurrence().getEnd().getTime() < recEndDate.getTime(); if (isLastOccurenceDeleted && masterEvent.getExceptionIds() != null) { String pattern = EXCLUDE_ID_FORMAT_FIRST_CHARS.format(recEndDate); int i = 0; while (isLastOccurenceDeleted && i < masterEvent.getExceptionIds().size()) { isLastOccurenceDeleted = !((String) masterEvent.getExceptionIds().toArray()[i]).startsWith(pattern); i++; } } } } } appointment = Appointment.bind(appointment.getService(), appointment.getId(), new PropertySet(BasePropertySet.FirstClassProperties)); // End workaround if (!isLastOccurenceDeleted && !isNew && CalendarConverterService.verifyModifiedDatesConflict(masterEvent, appointment)) { if (LOG.isTraceEnabled()) { LOG.trace("Attempting to update eXo Event with Exchange Event, but modification date of eXo is after, ignore updating."); } return updatedEvents; } else { if (LOG.isTraceEnabled()) { if (isNew) { LOG.trace("Create recurrent user calendar event: " + appointment.getSubject()); } else { LOG.trace("Update recurrent user calendar event: " + appointment.getSubject()); } } masterEvent.setCalendarId(calendar.getId()); CalendarConverterService.convertExchangeToExoMasterRecurringCalendarEvent(masterEvent, appointment, username, storage, organizationService.getUserHandler(), timeZone); if (isNew) { correspondenceService.setCorrespondingId(username, masterEvent.getId(), appointment.getId().getUniqueId()); } else if (!CalendarConverterService.isSameDate(orginialStartDate, masterEvent.getFromDateTime())) { if (masterEvent.getExceptionIds() == null) { masterEvent.setExceptionIds(new ArrayList<String>()); } } CalendarCreateUpdateAction.IGNORE_UPDATE.set(true); try { storage.saveUserEvent(username, calendar.getId(), masterEvent, isNew); } finally { CalendarCreateUpdateAction.IGNORE_UPDATE.set(false); } } List<CalendarEvent> exceptionalEventsToUpdate = new ArrayList<CalendarEvent>(); List<String> occAppointmentIDs = new ArrayList<String>(); // Deleted execptional occurences events. List<CalendarEvent> toDeleteEvents = CalendarConverterService.convertExchangeToExoOccurenceEvent(masterEvent, exceptionalEventsToUpdate, occAppointmentIDs, appointment, username, storage, organizationService.getUserHandler(), correspondenceService, timeZone); if (exceptionalEventsToUpdate != null && !exceptionalEventsToUpdate.isEmpty()) { storage.updateOccurrenceEvent(calendar.getId(), calendar.getId(), masterEvent.getCalType(), masterEvent.getCalType(), exceptionalEventsToUpdate, username); // Set correspondance IDs Iterator<CalendarEvent> eventsIterator = exceptionalEventsToUpdate.iterator(); Iterator<String> occAppointmentIdIterator = occAppointmentIDs.iterator(); while (eventsIterator.hasNext()) { CalendarEvent calendarEvent = eventsIterator.next(); String occAppointmentId = occAppointmentIdIterator.next(); correspondenceService.setCorrespondingId(username, calendarEvent.getId(), occAppointmentId); } updatedEvents.addAll(exceptionalEventsToUpdate); } if (toDeleteEvents != null && !toDeleteEvents.isEmpty()) { for (CalendarEvent calendarEvent : toDeleteEvents) { deleteEvent(username, calendarEvent); } } } break; case Occurrence: LOG.warn("The appointment is an occurence of this event >> '" + appointment.getSubject() + "'. start:" + appointment.getStart() + ", end : " + appointment.getEnd() + ", occurence: " + appointment.getAppointmentSequenceNumber()); } } return updatedEvents; } }