/**
 * Copyright (C) 2003-2007 eXo Platform SAS.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License
 * as published by the Free Software Foundation; either version 3
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see<http://www.gnu.org/licenses/>.
 **/
package org.exoplatform.calendar.service.impl;

import java.io.ByteArrayInputStream;
import java.io.OutputStream;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TimeZone;

import javax.jcr.AccessDeniedException;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.PathNotFoundException;
import javax.jcr.Property;
import javax.jcr.PropertyIterator;
import javax.jcr.Session;
import javax.jcr.Value;
import javax.jcr.nodetype.ConstraintViolationException;
import javax.jcr.query.Query;
import javax.jcr.query.QueryManager;
import javax.jcr.query.QueryResult;

import net.fortuna.ical4j.model.DateList;
import net.fortuna.ical4j.model.DateTime;
import net.fortuna.ical4j.model.NumberList;
import net.fortuna.ical4j.model.Period;
import net.fortuna.ical4j.model.PeriodList;
import net.fortuna.ical4j.model.Recur;
import net.fortuna.ical4j.model.TimeZoneRegistry;
import net.fortuna.ical4j.model.TimeZoneRegistryFactory;
import net.fortuna.ical4j.model.WeekDay;
import net.fortuna.ical4j.model.WeekDayList;
import net.fortuna.ical4j.model.component.VEvent;
import net.fortuna.ical4j.model.property.RRule;

import org.apache.commons.lang.StringUtils;
import org.exoplatform.calendar.service.Attachment;
import org.exoplatform.calendar.service.Calendar;
import org.exoplatform.calendar.service.CalendarCategory;
import org.exoplatform.calendar.service.CalendarEvent;
import org.exoplatform.calendar.service.CalendarImportExport;
import org.exoplatform.calendar.service.CalendarSetting;
import org.exoplatform.calendar.service.DataStorage;
import org.exoplatform.calendar.service.EventCategory;
import org.exoplatform.calendar.service.EventPageList;
import org.exoplatform.calendar.service.EventPageListQuery;
import org.exoplatform.calendar.service.EventQuery;
import org.exoplatform.calendar.service.FeedData;
import org.exoplatform.calendar.service.GroupCalendarData;
import org.exoplatform.calendar.service.Reminder;
import org.exoplatform.calendar.service.RemoteCalendar;
import org.exoplatform.calendar.service.RssData;
import org.exoplatform.calendar.service.Utils;
import org.exoplatform.commons.utils.ISO8601;
import org.exoplatform.container.ExoContainer;
import org.exoplatform.container.ExoContainerContext;
import org.exoplatform.container.PortalContainer;
import org.exoplatform.services.jcr.RepositoryService;
import org.exoplatform.services.jcr.access.AccessControlEntry;
import org.exoplatform.services.jcr.access.PermissionType;
import org.exoplatform.services.jcr.access.SystemIdentity;
import org.exoplatform.services.jcr.core.ExtendedNode;
import org.exoplatform.services.jcr.core.ManageableRepository;
import org.exoplatform.services.jcr.ext.app.SessionProviderService;
import org.exoplatform.services.jcr.ext.common.SessionProvider;
import org.exoplatform.services.jcr.ext.hierarchy.NodeHierarchyCreator;
import org.exoplatform.services.jcr.util.IdGenerator;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.exoplatform.services.organization.Group;
import org.exoplatform.services.organization.Membership;
import org.exoplatform.services.organization.OrganizationService;
import org.exoplatform.services.organization.User;

import com.sun.syndication.feed.synd.SyndContent;
import com.sun.syndication.feed.synd.SyndContentImpl;
import com.sun.syndication.feed.synd.SyndEntry;
import com.sun.syndication.feed.synd.SyndEntryImpl;
import com.sun.syndication.feed.synd.SyndFeed;
import com.sun.syndication.feed.synd.SyndFeedImpl;
import com.sun.syndication.io.SyndFeedInput;
import com.sun.syndication.io.SyndFeedOutput;
import com.sun.syndication.io.XmlReader;

/**
 * Created by The eXo Platform SARL
 * Author : Hung Nguyen Quang
 *          hung.nguyen@exoplatform.com
 * Jul 10, 2007  
 */
public class JCRDataStorage implements DataStorage {

  final private static String  CALENDARS           = "calendars".intern();

  final private static String  SHARED_CALENDAR     = "sharedCalendars".intern();

  final private static String  CALENDAR_CATEGORIES = "categories".intern();

  final private static String  FEED                = "eXoCalendarFeed".intern();

  final private static String  CALENDAR_EVENT      = "events".intern();

  final private static String  CALENDAR_SETTING    = "calendarSetting".intern();

  final private static String  EVENT_CATEGORIES    = "eventCategories".intern();

  private final static String  VALUE               = "value".intern();

  private NodeHierarchyCreator nodeHierarchyCreator_;

  private RepositoryService    repoService_;
  
  private SessionProviderService sessionProviderService_;

  private static final Log     log                 = ExoLogger.getLogger("cs.calendar.service");

  public JCRDataStorage(NodeHierarchyCreator nodeHierarchyCreator, RepositoryService repoService) throws Exception {
    nodeHierarchyCreator_ = nodeHierarchyCreator;
    repoService_ = repoService;
    ExoContainer container = ExoContainerContext.getCurrentContainer();
    sessionProviderService_ = (SessionProviderService) container.getComponentInstanceOfType(SessionProviderService.class);
  }

  /**
   * {@inheritDoc}
   */
  public Node getPublicCalendarServiceHome() throws Exception {
    SessionProvider sProvider = createSystemProvider();
    Node publicApp = getNodeByPath(nodeHierarchyCreator_.getPublicApplicationNode(sProvider).getPath(), sProvider);
    try {
      return publicApp.getNode(Utils.CALENDAR_APP);
    } catch (Exception e) {
      Node calendarApp = publicApp.addNode(Utils.CALENDAR_APP, Utils.NT_UNSTRUCTURED);
      publicApp.getSession().save();
      return calendarApp;
    }
  }

  /**
   * {@inheritDoc}
   */
  public Node getSharedCalendarHome() throws Exception {
    // TODO have to use system session
    Node calendarServiceHome = getPublicCalendarServiceHome();
    try {
      return calendarServiceHome.getNode(SHARED_CALENDAR);
    } catch (Exception e) {
      Node sharedCal = calendarServiceHome.addNode(SHARED_CALENDAR, Utils.NT_UNSTRUCTURED);
      calendarServiceHome.getSession().save();
      return sharedCal;
    }
  }

  /**
   * {@inheritDoc}
   */
  public Node getPublicRoot() throws Exception {
    SessionProvider sProvider = createSystemProvider();
    return getNodeByPath(nodeHierarchyCreator_.getPublicApplicationNode(sProvider).getPath(), sProvider);
  }

  /**
   * {@inheritDoc}
   */
  public Node getUserCalendarServiceHome(String username) throws Exception {
    // CS-2356
    // SessionProvider sProvider = createSessionProvider();
    SessionProvider sProvider = createSystemProvider();
    Node userNode = nodeHierarchyCreator_.getUserApplicationNode(sProvider, username);
    Node userApp = getNodeByPath(userNode.getPath(), sProvider);
    Node calendarRoot;
    try {
      return userApp.getNode(Utils.CALENDAR_APP);
    } catch (Exception e) {
      calendarRoot = userApp.addNode(Utils.CALENDAR_APP, Utils.NT_UNSTRUCTURED);
      if (!calendarRoot.hasNode(CALENDAR_SETTING)) {
        addCalendarSetting(calendarRoot, new CalendarSetting());
      }
      userApp.getSession().save();
      return calendarRoot;
    }
  }

  /**
   * {@inheritDoc}
   */
  public Node getPublicCalendarHome() throws Exception {
    Node calendarServiceHome = getPublicCalendarServiceHome();
    try {
      return calendarServiceHome.getNode(CALENDARS);
    } catch (Exception e) {
      Node cal = calendarServiceHome.addNode(CALENDARS, Utils.NT_UNSTRUCTURED);
      calendarServiceHome.getSession().save();
      return cal;
    }
  }

  /**
   * {@inheritDoc}
   */
  public Node getUserCalendarHome(String username) throws Exception {
    Node calendarServiceHome = getUserCalendarServiceHome(username);
    try {
      return calendarServiceHome.getNode(CALENDARS);
    } catch (Exception e) {
      Node calendars = calendarServiceHome.addNode(CALENDARS, Utils.NT_UNSTRUCTURED);
      calendarServiceHome.getSession().save();
      return calendars;
    }
  }

  /**
   * {@inheritDoc}
   */
  public Node getRssHome(String username) throws Exception {
    Node calendarServiceHome = getUserCalendarServiceHome(username);
    try {
      return calendarServiceHome.getNode(FEED);
    } catch (Exception e) {
      Node feed = calendarServiceHome.addNode(FEED, Utils.NT_UNSTRUCTURED);
      calendarServiceHome.getSession().save();
      return feed;
    }
  }

  /**
   * {@inheritDoc}
   */
  protected Node getCalendarCategoryHome(String username) throws Exception {
    Node calendarServiceHome = getUserCalendarServiceHome(username);
    try {
      return calendarServiceHome.getNode(CALENDAR_CATEGORIES);
    } catch (Exception e) {
      Node calCat = calendarServiceHome.addNode(CALENDAR_CATEGORIES, Utils.NT_UNSTRUCTURED);
      calendarServiceHome.getSession().save();
      return calCat;
    }
  }

  /**
   * 
   * @param username
   * @return
   * @throws Exception
   */
  protected Node getEventCategoryHome(String username) throws Exception {
    Node calendarServiceHome = getUserCalendarServiceHome(username);
    try {
      return calendarServiceHome.getNode(EVENT_CATEGORIES);
    } catch (Exception e) {
      Node eventCat = calendarServiceHome.addNode(EVENT_CATEGORIES, Utils.NT_UNSTRUCTURED);
      calendarServiceHome.getSession().save();
      return eventCat;
    }
  }

  /**
   * {@inheritDoc}
   */
  public Calendar getUserCalendar(String username, String calendarId) throws Exception {
    try {
      Node calendarNode = getUserCalendarHome(username).getNode(calendarId);
      return getCalendar(new String[] { calendarId }, username, calendarNode, true);
    } catch (PathNotFoundException e) {
      log.debug("Failed to get calendar, maybe the calendar has been removed or not created");
      return null;
    }
  }

  /**
   * {@inheritDoc}
   */
  public List<Calendar> getUserCalendars(String username, boolean isShowAll) throws Exception {
    Node userCalendarHome = getUserCalendarHome(username);
    NodeIterator iter = userCalendarHome.getNodes();
    List<Calendar> calList = new ArrayList<Calendar>();
    String[] defaultCalendars = getCalendarSetting(getUserCalendarServiceHome(username)).getFilterPrivateCalendars();
    while (iter.hasNext()) {
      calList.add(getCalendar(defaultCalendars, username, iter.nextNode(), isShowAll));
    }
    return calList;
  }

  /**
   * {@inheritDoc}
   */
  public List<Calendar> getUserCalendarsByCategory(String username, String calendarCategoryId) throws Exception {
    Node calendarHome = getUserCalendarHome(username);
    QueryManager qm = calendarHome.getSession().getWorkspace().getQueryManager();
    StringBuffer queryString = new StringBuffer("/jcr:root" + calendarHome.getPath() + "//element(*,exo:calendar)[@exo:categoryId='").append(calendarCategoryId).append("']");
    Query query = qm.createQuery(queryString.toString(), Query.XPATH);
    QueryResult result = query.execute();
    NodeIterator it = result.getNodes();
    List<Calendar> calendares = new ArrayList<Calendar>();
    String[] defaultCalendars = null;
    if (username != null) {
      defaultCalendars = getCalendarSetting(getUserCalendarServiceHome(username)).getFilterPrivateCalendars();
    }
    while (it.hasNext()) {
      calendares.add(getCalendar(defaultCalendars, username, it.nextNode(), true));
    }
    return calendares;
  }

  /**
   * {@inheritDoc}
   */
  public void saveUserCalendar(String username, Calendar calendar, boolean isNew) throws Exception {
    Node calendarHome = getUserCalendarHome(username);
    Node calendarNode;
    if (isNew) {
      try {
        calendarNode = calendarHome.getNode(calendar.getId());
      } catch (Exception e) {
        calendarNode = calendarHome.addNode(calendar.getId(), Utils.EXO_CALENDAR);
        calendarNode.setProperty(Utils.EXO_ID, calendar.getId());
      }
    } else {
      calendarNode = calendarHome.getNode(calendar.getId());
    }
    setCalendarProperties(calendarNode, calendar);
    Session session = calendarHome.getSession();
    session.save();
  }

  /**
   * {@inheritDoc}
   */
  public Calendar removeUserCalendar(String username, String calendarId) throws Exception {
    Node calendarHome = getUserCalendarHome(username);
    if (calendarHome.hasNode(calendarId)) {
      Node calNode = calendarHome.getNode(calendarId);
      Calendar calendar = getCalendar(new String[] { calendarId }, username, calNode, true);
      NodeIterator iter = calNode.getNodes();
      try {
        while (iter.hasNext()) {
          Node eventNode = iter.nextNode();
          Node eventFolder = getEventFolder(eventNode.getProperty(Utils.EXO_FROM_DATE_TIME).getDate().getTime());
          syncRemoveEvent(eventFolder, eventNode.getName());
          removeReminder(eventNode);
        }
        calNode.remove();
        calendarHome.save();
      } catch (Exception e) {
        log.error("Exception occurred when removing calendar " + calendarId, e);
      } finally {
        // provider.close() ;
      }
      try {
        removeFeed(username, calendarId);
      } catch (Exception e) {
        log.warn("Exception occurred when removing feeds from calendar " + calendarId, e);
      }
      return calendar;
    }
    return null;
  }

  /**
   * {@inheritDoc}
   */
  public Calendar getGroupCalendar(String calendarId) throws Exception {
    Node calendarNode = getPublicCalendarHome().getNode(calendarId);
    return getCalendar(new String[] { calendarId }, null, calendarNode, true);
  }

  /**
   * {@inheritDoc}
   */
  public List<GroupCalendarData> getGroupCalendars(String[] groupIds, boolean isShowAll, String username) throws Exception {
    List<Calendar> calendars;
    Node calendarHome = getPublicCalendarHome();
    List<GroupCalendarData> groupCalendars = new ArrayList<GroupCalendarData>();
    String[] defaultCalendars = null;
    if (username != null) {
      CalendarSetting calendarSetting = getCalendarSetting(getUserCalendarServiceHome(username));
      if (calendarSetting != null)
        defaultCalendars = calendarSetting.getFilterPublicCalendars();
    }
    QueryManager qm = calendarHome.getSession().getWorkspace().getQueryManager();

    for (String groupId : groupIds) {
      StringBuffer queryString = new StringBuffer("/jcr:root" + calendarHome.getPath() + "//element(*,exo:calendar)[@exo:groups='").append(groupId).append("']");
      Query query = qm.createQuery(queryString.toString(), Query.XPATH);
      QueryResult result = query.execute();
      NodeIterator it = result.getNodes();
      if (it.hasNext()) {
        calendars = new ArrayList<Calendar>();
        while (it.hasNext()) {
          Node calNode = it.nextNode();
          Calendar cal = getCalendar(defaultCalendars, null, calNode, isShowAll);
          if (cal != null)
            calendars.add(cal);
        }
        groupCalendars.add(new GroupCalendarData(groupId, groupId, calendars));
      }
    }
    return groupCalendars;
  }

  /**
   * {@inheritDoc}
   */
  public void savePublicCalendar(Calendar calendar, boolean isNew, String username) throws Exception {
    Node calendarHome = getPublicCalendarHome();
    Node calendarNode;
    if (isNew) {
      if (calendarHome.hasNode(calendar.getId()))
        throw new Exception("This calendar is already exists");
      calendarNode = calendarHome.addNode(calendar.getId(), Utils.EXO_CALENDAR);
      calendarNode.setProperty(Utils.EXO_ID, calendar.getId());
    } else {
      calendarNode = calendarHome.getNode(calendar.getId());
    }
    setCalendarProperties(calendarNode, calendar);
    calendarHome.getSession().save();
  }

  /**
   * {@inheritDoc}
   */
  public Calendar removeGroupCalendar(String calendarId) throws Exception {
    Node calendarHome = getPublicCalendarHome();
    if (calendarHome.hasNode(calendarId)) {
      Node calNode = calendarHome.getNode(calendarId);
      Calendar calendar = getCalendar(new String[] { calendarId }, null, calNode, true);
      NodeIterator iter = calNode.getNodes();
      while (iter.hasNext()) {
        Node eventNode = iter.nextNode();
        Node eventFolder = getEventFolder(eventNode.getProperty(Utils.EXO_FROM_DATE_TIME).getDate().getTime());
        removeReminder(eventNode);
        syncRemoveEvent(eventFolder, eventNode.getName());
      }
      calNode.remove();
      // calendarHome.save() ;
      calendarHome.getSession().save();
      return calendar;
    }
    return null;

  }

  /**
   * {@inheritDoc}
   */
  public Calendar getCalendar(String[] defaultFilterCalendars, String username, Node calNode, boolean isShowAll) throws Exception {
    Calendar calendar = null;

    if (!isShowAll && defaultFilterCalendars != null && Arrays.asList(defaultFilterCalendars).contains(calNode.getName()))
      return null;

    calendar = new Calendar();
    if (calNode.hasProperty(Utils.EXO_ID))
      calendar.setId(calNode.getProperty(Utils.EXO_ID).getString());
    if (calNode.hasProperty(Utils.EXO_NAME))
      calendar.setName(calNode.getProperty(Utils.EXO_NAME).getString());
    if (calNode.hasProperty(Utils.EXO_DESCRIPTION))
      calendar.setDescription(calNode.getProperty(Utils.EXO_DESCRIPTION).getString());
    if (calNode.hasProperty(Utils.EXO_CATEGORY_ID))
      calendar.setCategoryId(calNode.getProperty(Utils.EXO_CATEGORY_ID).getString());
    if (calNode.hasProperty(Utils.EXO_LOCALE))
      calendar.setLocale(calNode.getProperty(Utils.EXO_LOCALE).getString());
    if (calNode.hasProperty(Utils.EXO_TIMEZONE))
      calendar.setTimeZone(calNode.getProperty(Utils.EXO_TIMEZONE).getString());
    if (calNode.hasProperty(Utils.EXO_SHARED_COLOR))
      calendar.setCalendarColor(calNode.getProperty(Utils.EXO_SHARED_COLOR).getString());
    if (calNode.hasProperty(Utils.EXO_CALENDAR_COLOR))
      calendar.setCalendarColor(calNode.getProperty(Utils.EXO_CALENDAR_COLOR).getString());
    if (calNode.hasProperty(Utils.EXO_CALENDAR_OWNER))
      calendar.setCalendarOwner(calNode.getProperty(Utils.EXO_CALENDAR_OWNER).getString());
    if (calNode.hasProperty(Utils.EXO_PUBLIC_URL))
      calendar.setPublicUrl(calNode.getProperty(Utils.EXO_PUBLIC_URL).getString());
    if (calNode.hasProperty(Utils.EXO_PRIVATE_URL))
      calendar.setPrivateUrl(calNode.getProperty(Utils.EXO_PRIVATE_URL).getString());

    if (!calendar.isPublic()) {
      if (calNode.hasProperty(Utils.EXO_GROUPS)) {
        Value[] values = calNode.getProperty(Utils.EXO_GROUPS).getValues();
        List<String> groups = new ArrayList<String>();
        for (Value v : values) {
          groups.add(v.getString());
        }
        calendar.setGroups(groups.toArray(new String[groups.size()]));
      }
      if (calNode.hasProperty(Utils.EXO_VIEW_PERMISSIONS)) {
        calendar.setViewPermission(ValuesToStrings(calNode.getProperty(Utils.EXO_VIEW_PERMISSIONS).getValues()));
      }
      if (calNode.hasProperty(Utils.EXO_EDIT_PERMISSIONS)) {
        calendar.setEditPermission(ValuesToStrings(calNode.getProperty(Utils.EXO_EDIT_PERMISSIONS).getValues()));
      }
    }
    return calendar;
  }

  /**
   * {@inheritDoc}
   */
  public List<GroupCalendarData> getCalendarCategories(String username, boolean isShowAll) throws Exception {
    // try {
    Node calendarHome = getUserCalendarHome(username);
    NodeIterator iter = getCalendarCategoryHome(username).getNodes();
    List<GroupCalendarData> calendarCategories = new ArrayList<GroupCalendarData>();
    List<Calendar> calendars;
    calendarHome.getSession().refresh(false);
    String[] defaultCalendars = null;
    CalendarSetting calSetting = getCalendarSetting(getUserCalendarServiceHome(username));
    if (calSetting != null) {
      defaultCalendars = calSetting.getFilterPrivateCalendars();
    }
    QueryManager qm = calendarHome.getSession().getWorkspace().getQueryManager();
    while (iter.hasNext()) {
      Node categoryNode = iter.nextNode();
      String categoryId = categoryNode.getProperty(Utils.EXO_ID).getString();
      StringBuffer queryString = new StringBuffer("/jcr:root" + calendarHome.getPath() + "//element(*,exo:calendar)[@exo:categoryId='").append(categoryId).append("']");
      Query query = qm.createQuery(queryString.toString(), Query.XPATH);
      QueryResult result = query.execute();
      NodeIterator it = result.getNodes();
      calendars = new ArrayList<Calendar>();
      if (it.hasNext()) {
        while (it.hasNext()) {
          Calendar cal = getCalendar(defaultCalendars, username, it.nextNode(), isShowAll);
          if (cal != null)
            calendars.add(cal);
        }
      }
      calendarCategories.add(new GroupCalendarData(categoryId, categoryNode.getProperty(Utils.EXO_NAME).getString(), calendars));
    }
    return calendarCategories;
    /*
     * } finally { closeSessionProvider(sProvider); }
     */
  }

  /**
   * {@inheritDoc}
   */
  public List<CalendarCategory> getCategories(String username) throws Exception {
    Node calendarCategoryHome = getCalendarCategoryHome(username);
    NodeIterator iter = calendarCategoryHome.getNodes();
    List<CalendarCategory> calendarCategories = new ArrayList<CalendarCategory>();
    while (iter.hasNext()) {
      calendarCategories.add(getCalendarCategory(iter.nextNode()));
    }
    return calendarCategories;
  }

  /**
   * {@inheritDoc}
   */
  public CalendarCategory getCalendarCategory(String username, String calendarCategoryId) throws Exception {
    Node calendarCategoryHome = getCalendarCategoryHome(username);
    return getCalendarCategory(calendarCategoryHome.getNode(calendarCategoryId));
  }

  /**
   * {@inheritDoc}
   */
  public void saveCalendarCategory(String username, CalendarCategory calendarCategory, boolean isNew) throws Exception {
    Node calCategoryHome = getCalendarCategoryHome(username);
    Node calCategoryNode;
    if (isNew) {
      if (calCategoryHome.hasNode(calendarCategory.getId()))
        throw new Exception("This calendar category is already exists! ");
      calCategoryNode = calCategoryHome.addNode(calendarCategory.getId(), Utils.EXO_CALENDAR_CATEGORY);
      calCategoryNode.setProperty(Utils.EXO_ID, calendarCategory.getId());
    } else {
      calCategoryNode = calCategoryHome.getNode(calendarCategory.getId());
    }
    calCategoryNode.setProperty(Utils.EXO_NAME, calendarCategory.getName());
    calCategoryNode.setProperty(Utils.EXO_DESCRIPTION, calendarCategory.getDescription());
    Session session = calCategoryHome.getSession();
    session.save();
  }

  /**
   * {@inheritDoc}
   */
  public CalendarCategory removeCalendarCategory(String username, String calendarCategoryId) throws Exception {
    Node calCategoryHome = getCalendarCategoryHome(username);
    Node calCategoryNode = calCategoryHome.getNode(calendarCategoryId);
    CalendarCategory calCategory = getCalendarCategory(calCategoryNode);
    calCategoryNode.remove();
    for (Calendar cal : getUserCalendarsByCategory(username, calendarCategoryId)) {
      removeUserCalendar(username, cal.getId());
    }
    calCategoryHome.save();
    calCategoryHome.getSession().save();
    return calCategory;
  }

  /**
   * {@inheritDoc}
   */
  public CalendarCategory getCalendarCategory(Node calCategoryNode) throws Exception {
    CalendarCategory calCategory = new CalendarCategory();
    if (calCategoryNode.hasProperty(Utils.EXO_ID))
      calCategory.setId(calCategoryNode.getProperty(Utils.EXO_ID).getString());
    if (calCategoryNode.hasProperty(Utils.EXO_NAME))
      calCategory.setName(calCategoryNode.getProperty(Utils.EXO_NAME).getString());
    if (calCategoryNode.hasProperty(Utils.EXO_DESCRIPTION))
      calCategory.setDescription(calCategoryNode.getProperty(Utils.EXO_DESCRIPTION).getString());
    return calCategory;
  }

  // Event Category APIs

  public List<EventCategory> getEventCategories(String username) throws Exception {
    Node eventCategoryHome = getEventCategoryHome(username);
    NodeIterator iter = eventCategoryHome.getNodes();
    List<EventCategory> categories = new ArrayList<EventCategory>();
    while (iter.hasNext()) {
      categories.add(getEventCategory(iter.nextNode()));
    }
    return categories;
  }

  public void saveEventCategory(String username, EventCategory eventCategory, String[] values, boolean isNew) throws Exception {
    saveEventCategory(username, eventCategory, isNew);
  }

  public void saveEventCategory(String username, EventCategory eventCategory, boolean isNew) throws Exception {
    Node eventCategoryHome = getEventCategoryHome(username);
    Node eventCategoryNode = null;
    if (isNew) {
      eventCategoryNode = eventCategoryHome.addNode(eventCategory.getId(), Utils.EXO_EVENT_CATEGORY);
    } else {

      eventCategoryNode = eventCategoryHome.getNode(eventCategory.getId());
      Node calendarHome = getUserCalendarHome(username);
      QueryManager qm = calendarHome.getSession().getWorkspace().getQueryManager();
      NodeIterator calIter = calendarHome.getNodes();
      Query query;
      QueryResult result;
      while (calIter.hasNext()) {
        StringBuffer queryString = new StringBuffer("/jcr:root" + calIter.nextNode().getPath() + "//element(*,exo:calendarEvent)[@exo:eventCategoryId='").append(eventCategory.getId()).append("']");
        query = qm.createQuery(queryString.toString(), Query.XPATH);
        result = query.execute();
        NodeIterator it = result.getNodes();
        while (it.hasNext()) {
          Node eventNode = it.nextNode();
          eventNode.setProperty(Utils.EXO_EVENT_CATEGORY_NAME, eventCategory.getName());
        }
      }

      // cs-2020
      if (getSharedCalendarHome().hasNode(username)) {
        PropertyIterator iterPro = getSharedCalendarHome().getNode(username).getReferences();
        while (iterPro.hasNext()) {
          try {
            Node calendar = iterPro.nextProperty().getParent();
            NodeIterator it = calendar.getNodes();
            while (it.hasNext()) {
              Node eventNode = it.nextNode();
              if (eventNode.hasProperty(Utils.EXO_EVENT_CATEGORYID))
                if (eventNode.getProperty(Utils.EXO_EVENT_CATEGORYID).getString().equals(eventCategory.getId()))
                  eventNode.setProperty(Utils.EXO_EVENT_CATEGORY_NAME, eventCategory.getName());
            }

          } catch (Exception e) {
            if (log.isDebugEnabled())
              log.debug(e);
          }
        }
      }
    }
    eventCategoryNode.setProperty(Utils.EXO_ID, eventCategory.getId());
    eventCategoryNode.setProperty(Utils.EXO_NAME, eventCategory.getName());
    eventCategoryNode.setProperty(Utils.EXO_DESCRIPTION, eventCategory.getDescription());
    eventCategoryHome.getSession().save();
  }

  public void removeEventCategory(String username, String eventCategoryId) throws Exception {
    Node eventCategoryHome = getEventCategoryHome(username);
    if (eventCategoryHome.hasNode(eventCategoryId)) {
      Node eventCategoryNode = eventCategoryHome.getNode(eventCategoryId);
      // CS-3482
      // SessionProvider systemSession = SessionProvider.createSystemProvider() ;
      for (CalendarEvent ce : getUserEventByCategory(username, eventCategoryId)) {
        ce.setEventCategoryId(NewUserListener.DEFAULT_EVENTCATEGORY_ID_ALL);
        ce.setEventCategoryName(NewUserListener.DEFAULT_EVENTCATEGORY_NAME_ALL);
        saveUserEvent(username, ce.getCalendarId(), ce, false);
      }
      for (CalendarEvent ce : getSharedEventByCategory(username, eventCategoryId)) {
        ce.setEventCategoryId(NewUserListener.DEFAULT_EVENTCATEGORY_ID_ALL);
        ce.setEventCategoryName(NewUserListener.DEFAULT_EVENTCATEGORY_NAME_ALL);
        saveEventToSharedCalendar(username, ce.getCalendarId(), ce, false);
      }
      for (CalendarEvent ce : getPublicEventByCategory(username, eventCategoryId)) {
        ce.setEventCategoryId(NewUserListener.DEFAULT_EVENTCATEGORY_ID_ALL);
        ce.setEventCategoryName(NewUserListener.DEFAULT_EVENTCATEGORY_NAME_ALL);
        savePublicEvent(ce.getCalendarId(), ce, false);
      }
      eventCategoryNode.remove();
      eventCategoryHome.save();
      eventCategoryHome.getSession().save();
    }
  }

  /**
   * {@inheritDoc}
   */
  public EventCategory getEventCategory(Node eventCatNode) throws Exception {
    EventCategory eventCategory = new EventCategory();
    if (eventCatNode.hasProperty(Utils.EXO_ID))
      eventCategory.setId(eventCatNode.getProperty(Utils.EXO_ID).getString());
    if (eventCatNode.hasProperty(Utils.EXO_NAME))
      eventCategory.setName(eventCatNode.getProperty(Utils.EXO_NAME).getString());
    if (eventCatNode.hasProperty(Utils.EXO_DESCRIPTION))
      eventCategory.setDescription(eventCatNode.getProperty(Utils.EXO_DESCRIPTION).getString());
    return eventCategory;
  }

  public EventCategory getEventCategory(String username, String eventcategoryId) throws Exception {
    Node eventCategoryHome = getEventCategoryHome(username);
    return getEventCategory(eventCategoryHome.getNode(eventcategoryId));
  }

  public CalendarEvent getUserEvent(String username, String calendarId, String eventId) throws Exception {
    Node calendarNode = getUserCalendarHome(username).getNode(calendarId);
    return getEvent(calendarNode.getNode(eventId));
  }

  public List<CalendarEvent> getUserEventByCalendar(String username, List<String> calendarIds) throws Exception {
    List<CalendarEvent> events = new ArrayList<CalendarEvent>();
    for (String calendarId : calendarIds) {
      Node calendarNode = getUserCalendarHome(username).getNode(calendarId);
      NodeIterator it = calendarNode.getNodes();
      while (it.hasNext()) {
        events.add(getEvent(it.nextNode()));
      }
    }
    return events;
  }

  public List<CalendarEvent> getPublicEventByCategory(String username, String eventCategoryId) throws Exception {
    Node publicCalendarHome = getPublicCalendarHome();
    QueryManager qm = publicCalendarHome.getSession().getWorkspace().getQueryManager();
    List<CalendarEvent> events = new ArrayList<CalendarEvent>();
    try {
      Query query;
      QueryResult result;
      NodeIterator calIter = publicCalendarHome.getNodes();
      while (calIter.hasNext()) {
        StringBuffer queryString = new StringBuffer("/jcr:root" + calIter.nextNode().getPath() + "//element(*,exo:calendarEvent)[@exo:eventCategoryId='").append(eventCategoryId).append("']");
        query = qm.createQuery(queryString.toString(), Query.XPATH);
        result = query.execute();
        NodeIterator it = result.getNodes();
        while (it.hasNext()) {
          events.add(getEvent(it.nextNode()));
        }
      }
    } catch (Exception e) {
      log.error("Error occurred when querying public events from event category " + eventCategoryId, e);
    }
    return events;
  }

  public List<CalendarEvent> getSharedEventByCategory(String username, String eventCategoryId) throws Exception {
    List<CalendarEvent> events = new ArrayList<CalendarEvent>();
    try {
      if (getSharedCalendarHome().hasNode(username)) {
        PropertyIterator iterPro = getSharedCalendarHome().getNode(username).getReferences();
        while (iterPro.hasNext()) {
          try {
            Node calendar = iterPro.nextProperty().getParent();
            NodeIterator it = calendar.getNodes();
            while (it.hasNext()) {
              Node eventNode = it.nextNode();
              if (eventNode.hasProperty(Utils.EXO_EVENT_CATEGORYID))
                if (eventNode.getProperty(Utils.EXO_EVENT_CATEGORYID).getString().equals(eventCategoryId)) {
                  events.add(getEvent(eventNode));
                }
            }

          } catch (Exception e) {
            if (log.isDebugEnabled())
              log.debug(e);
          }
        }
      }
    } catch (Exception e) {
      if (log.isDebugEnabled())
        log.debug(e);
    }
    return events;
  }

  /**
   * {@inheritDoc}
   */
  public List<CalendarEvent> getUserEventByCategory(String username, String eventCategoryId) throws Exception {
    Node calendarHome = getUserCalendarHome(username);
    QueryManager qm = calendarHome.getSession().getWorkspace().getQueryManager();
    List<CalendarEvent> events = new ArrayList<CalendarEvent>();
    Query query;
    QueryResult result;
    NodeIterator calIter = calendarHome.getNodes();
    while (calIter.hasNext()) {
      StringBuffer queryString = new StringBuffer("/jcr:root" + calIter.nextNode().getPath() + "//element(*,exo:calendarEvent)[@exo:eventCategoryId='").append(eventCategoryId).append("']");
      query = qm.createQuery(queryString.toString(), Query.XPATH);
      result = query.execute();
      NodeIterator it = result.getNodes();
      while (it.hasNext()) {
        events.add(getEvent(it.nextNode()));
      }
    }
    return events;
  }

  private CalendarEvent getEventById(Node calendarHome, String eventId) throws Exception {
    String queryString = new StringBuffer("/jcr:root" + calendarHome.getPath() + "//element(*,exo:calendarEvent)[@exo:id='").append(eventId).append("']").toString();
    QueryManager qm = calendarHome.getSession().getWorkspace().getQueryManager();
    Query query = qm.createQuery(queryString, Query.XPATH);
    QueryResult result = query.execute();
    NodeIterator it = result.getNodes();
    if (it.hasNext())
      return getEvent(it.nextNode());
    else
      return null;
  }

  /**
   * {@inheritDoc}
   */
  public CalendarEvent getEvent(String username, String eventId) throws Exception {
    Node calendarHome = getUserCalendarHome(username);
    return getEventById(calendarHome, eventId);
  }

  private List<CalendarEvent> getEventsByType(Node calendarHome, int type, EventQuery eventQuery) throws Exception {
    List<CalendarEvent> events = new ArrayList<CalendarEvent>();
    eventQuery.setCalendarPath(calendarHome.getPath());
    QueryManager qm = calendarHome.getSession().getWorkspace().getQueryManager();
    Query query = qm.createQuery(eventQuery.getQueryStatement(), eventQuery.getQueryType());
    QueryResult result = query.execute();
    NodeIterator it = result.getNodes();
    CalendarEvent calEvent;
    while (it.hasNext()) {
      calEvent = getEvent(it.nextNode());
      calEvent.setCalType(String.valueOf(type));
      events.add(calEvent);
      if (eventQuery.getLimitedItems() == it.getPosition())
        break;
    }
    return events;
  }

  /**
   * {@inheritDoc}
   */
  public List<CalendarEvent> getUserEvents(String username, EventQuery eventQuery) throws Exception {
    Node calendarHome = getUserCalendarHome(username);
    return getEventsByType(calendarHome, Calendar.TYPE_PRIVATE, eventQuery);
  }

  /**
   * {@inheritDoc}
   */
  public void saveUserEvent(String username, String calendarId, CalendarEvent event, boolean isNew) throws Exception {
    Node calendarNode = getUserCalendarHome(username).getNode(calendarId);
    event.setCalendarId(calendarId); // make sur the event is attached to the calendar
    if (event.getReminders() != null && event.getReminders().size() > 0) {
      // Need to use system session
      // SessionProvider systemSession = SessionProvider.createSystemProvider();
      try {
        Node reminderFolder = getReminderFolder(event.getFromDateTime());
        saveEvent(calendarNode, event, reminderFolder, isNew);
      } catch (Exception e) {
        if (log.isDebugEnabled())
          log.debug(e);
      } finally {
        // systemSession.close() ;
      }
    } else {
      saveEvent(calendarNode, event, null, isNew);
    }
  }

  /**
   * Save a occurrence event
   * @param username
   * @param calendarId
   * @param event
   * @param isNew
   * @throws Exception
   */
  public void saveOccurrenceEvent(String username, String calendarId, CalendarEvent event, boolean isNew) throws Exception {
    int calType = Integer.parseInt(event.getCalType());

    if (calType == Calendar.TYPE_PRIVATE) {
      Node calendarNode = getUserCalendarHome(username).getNode(calendarId);
      event.setCalendarId(calendarId);
      if (event.getReminders() != null && event.getReminders().size() > 0) {
        Node reminderFolder = getReminderFolder(event.getFromDateTime());
        saveOccurrenceEvent(calendarNode, event, reminderFolder, isNew);
      } else {
        saveOccurrenceEvent(calendarNode, event, null, isNew);
      }
      return;
    }

    if (calType == Calendar.TYPE_SHARED) {
      Node sharedCalendarHome = getSharedCalendarHome();
      if (sharedCalendarHome.hasNode(username)) {
        Node userNode = sharedCalendarHome.getNode(username);
        PropertyIterator iter = userNode.getReferences();
        Node calendar;
        while (iter.hasNext()) {
          calendar = iter.nextProperty().getParent();
          if (calendar.getProperty(Utils.EXO_ID).getString().equals(calendarId)) {
            if (!canEdit(calendar, username)) {
              if (log.isDebugEnabled())
                log.debug("\n Do not have edit permission. \n");
              throw new AccessDeniedException();
            }
            Node reminderFolder = getReminderFolder(event.getFromDateTime());
            saveOccurrenceEvent(calendar, event, reminderFolder, isNew);
            break;
          }
        }
      }
      return;
    }

    if (calType == Calendar.TYPE_PUBLIC) {
      Node calendarNode = getPublicCalendarHome().getNode(calendarId);
      Node reminderFolder = getReminderFolder(event.getFromDateTime());
      saveOccurrenceEvent(calendarNode, event, reminderFolder, isNew);
      return;
    }
  }

  public void saveOccurrenceEvent(Node calendarNode, CalendarEvent event, Node reminderFolder, boolean isNew) throws Exception {
    try {
      if (isNew) {
        // convert a 'virtual' occurrence to 'exception' occurrence
        Node originalNode = null;
        try {
          originalNode = calendarNode.getNode(event.getId());
        } catch (PathNotFoundException e) {
          if (log.isDebugEnabled())
            log.debug("Original recurrence node not found", e);
        }
        event.setId("Event" + IdGenerator.generate());
        event.setRepeatType(CalendarEvent.RP_NOREPEAT);

        // set reference to original node
        if (originalNode != null) {
          event.setOriginalReference(originalNode.getUUID());
        }

        event.setIsExceptionOccurrence(true);
        event.setRepeatInterval(0);
        event.setRepeatCount(0);
        event.setRepeatUntilDate(null);
        event.setRepeatByDay(null);
        event.setRepeatByMonthDay(null);

        saveEvent(calendarNode, event, reminderFolder, true);
      } else {
        event.setRepeatType(CalendarEvent.RP_NOREPEAT);
        event.setIsExceptionOccurrence(true);
        event.setRepeatByDay(null);
        event.setRepeatCount(0);
        event.setRepeatInterval(0);
        event.setRepeatUntilDate(null);
        event.setRepeatByMonthDay(null);
        saveEvent(calendarNode, event, reminderFolder, false);
      }
    } catch (Exception e) {
      log.error("Error occurred when saving occurrence event", e);
    }
  }

  public void removeOccurrenceInstance(String username, CalendarEvent occurrence) throws Exception {
    String eventId = occurrence.getId();
    String calendarId = occurrence.getCalendarId();
    int calType = Integer.parseInt(occurrence.getCalType());
    String recurId = occurrence.getRecurrenceId();
    CalendarEvent originalEvent = null;

    // get the original event
    if (calType == Calendar.TYPE_PRIVATE)
      originalEvent = getUserEvent(username, calendarId, eventId);
    else if (calType == Calendar.TYPE_PUBLIC)
      originalEvent = getGroupEvent(calendarId, eventId);
    else if (calType == Calendar.TYPE_SHARED)
      originalEvent = getSharedEvent(username, calendarId, eventId);

    // then update the exludeId property: add recurId to this list
    String[] excludeId;
    if (originalEvent.getExcludeId() == null) {
      excludeId = new String[] { recurId };
    } else {
      List<String> excId = new ArrayList<String>(Arrays.asList(originalEvent.getExcludeId()));
      excId.add(recurId);
      excludeId = excId.toArray(new String[0]);
    }
    originalEvent.setExcludeId(excludeId);

    if (calType == Calendar.TYPE_PRIVATE) {
      saveUserEvent(username, calendarId, originalEvent, false);
      return;
    }
    if (calType == Calendar.TYPE_PUBLIC) {
      savePublicEvent(calendarId, originalEvent, false);
      return;
    }
    if (calType == Calendar.TYPE_SHARED) {
      saveEventToSharedCalendar(username, calendarId, originalEvent, false);
      return;
    }
  }

  public void removeRecurrenceSeries(String username, CalendarEvent originalEvent) throws Exception {
    int calType = Integer.parseInt(originalEvent.getCalType());
    // get the list of exception node
    if (originalEvent.getRepeatType().equals(CalendarEvent.RP_NOREPEAT))
      return;
    List<CalendarEvent> exceptions = getExceptionEvents(username, originalEvent);
    if (exceptions != null && exceptions.size() > 0) {
      for (CalendarEvent exception : exceptions) {
        // remove mixin type or remove event?
        if (calType == Calendar.TYPE_PRIVATE)
          removeUserEvent(username, exception.getCalendarId(), exception.getId());
        else if (calType == Calendar.TYPE_PUBLIC)
          removePublicEvent(exception.getCalendarId(), exception.getId());
        else if (calType == Calendar.TYPE_SHARED)
          removeSharedEvent(username, exception.getCalendarId(), exception.getId());
      }
    }

    // delete original node
    if (calType == Calendar.TYPE_PRIVATE) {
      removeUserEvent(username, originalEvent.getCalendarId(), originalEvent.getId());
      return;
    }
    if (calType == Calendar.TYPE_PUBLIC) {
      removePublicEvent(originalEvent.getCalendarId(), originalEvent.getId());
      return;
    }
    if (calType == Calendar.TYPE_SHARED) {
      removeSharedEvent(username, originalEvent.getCalendarId(), originalEvent.getId());
      return;
    }
  }

  public Node getCalendarEventNode(String username, String calType, String calendarId, String eventId) throws Exception {
    Node eventNode = null;
    try {
      if (String.valueOf(Calendar.TYPE_PRIVATE).equals(calType))
        eventNode = getUserCalendarHome(username).getNode(calendarId).getNode(eventId);
      else if (String.valueOf(Calendar.TYPE_PUBLIC).equals(calType))
        eventNode = getPublicCalendarHome().getNode(calendarId).getNode(eventId);
      else if (String.valueOf(Calendar.TYPE_SHARED).equals(calType)) {
        Node sharedCalendarHome = getSharedCalendarHome();
        if (sharedCalendarHome.hasNode(username)) {
          PropertyIterator iter = sharedCalendarHome.getNode(username).getReferences();
          Node calendar;
          while (iter.hasNext()) {
            calendar = iter.nextProperty().getParent();
            if (!calendarId.equals(calendar.getProperty(Utils.EXO_ID)))
              continue;
            if (calendar.hasNode(eventId)) {
              eventNode = calendar.getNode(eventId);
              break;
            }
          }
        }
      }
      return eventNode;
    } catch (Exception e) {
      if (log.isDebugEnabled())
        log.debug("Exception occurs when get calendar event node", e);
      return null;
    }

  }

  /**
   * {@inheritDoc}
   */
  public CalendarEvent removeUserEvent(String username, String calendarId, String eventId) throws Exception {
    Node calendarNode = getUserCalendarHome(username).getNode(calendarId);
    if (calendarNode.hasNode(eventId)) {
      Node eventNode = calendarNode.getNode(eventId);
      CalendarEvent event = getEvent(eventNode);
      // Need to use system session
      try {
        Node eventFolder = getEventFolder(event.getFromDateTime());
        syncRemoveEvent(eventFolder, event.getId());
      } catch (Exception e) {
        if (log.isDebugEnabled())
          log.debug(e);
      }
      removeReminder(eventNode);
      eventNode.remove();
      calendarNode.save();
      calendarNode.getSession().save();
      calendarNode.refresh(true);
      return event;
    }
    return null;
  }

  /**
   * {@inheritDoc}
   */
  public void removeReminder(Node eventNode) throws Exception {
    if (eventNode.hasProperty(Utils.EXO_FROM_DATE_TIME)) {
      try {
        Node reminders = getReminderFolder(eventNode.getProperty(Utils.EXO_FROM_DATE_TIME).getDate().getTime());
        try {
          reminders.getNode(eventNode.getName()).remove();
          reminders.save();
        } catch (Exception e) {
        }
        Node events = reminders.getParent().getNode(Utils.CALENDAR_REMINDER);
        if (events != null && events.hasNode(eventNode.getName())) {
          if (events.hasNode(eventNode.getName())) {
            events.getNode(eventNode.getName()).remove();
            if (!reminders.isNew())
              reminders.save();
            else
              reminders.getSession().save();
          }
        }
      } catch (Exception e) {
        if (log.isDebugEnabled())
          log.debug(e);
      }
    }
  }

  /**
   * {@inheritDoc}
   */
  public CalendarEvent getGroupEvent(String calendarId, String eventId) throws Exception {
    Node calendarNode = getPublicCalendarHome().getNode(calendarId);
    CalendarEvent calEvent = getEvent(calendarNode.getNode(eventId));
    calEvent.setCalType(String.valueOf(Calendar.TYPE_PUBLIC));
    return calEvent;
  }

  /**
   * {@inheritDoc}
   */
  public CalendarEvent getGroupEvent(String eventId) throws Exception {
    Node calendarHome = getPublicCalendarHome();
    return getEventById(calendarHome, eventId);
  }

  /**
   * {@inheritDoc}
   */
  public List<CalendarEvent> getGroupEventByCalendar(List<String> calendarIds) throws Exception {
    List<CalendarEvent> events = new ArrayList<CalendarEvent>();
    for (String calendarId : calendarIds) {
      Node calendarNode = getPublicCalendarHome().getNode(calendarId);
      NodeIterator it = calendarNode.getNodes();
      while (it.hasNext()) {
        events.add(getEvent(it.nextNode()));
      }
    }
    return events;
  }

  /**
   * {@inheritDoc}
   */
  public List<CalendarEvent> getPublicEvents(EventQuery eventQuery) throws Exception {
    Node calendarHome = getPublicCalendarHome();
    List<CalendarEvent> events = getEventsByType(calendarHome, Calendar.TYPE_PUBLIC, eventQuery);
    return events;
  }

  /**
   * {@inheritDoc}
   */
  public void savePublicEvent(String calendarId, CalendarEvent event, boolean isNew) throws Exception {
    Node calendarNode = getPublicCalendarHome().getNode(calendarId);
    event.setCalendarId(calendarId);
    Node reminderFolder = getReminderFolder(event.getFromDateTime());
    saveEvent(calendarNode, event, reminderFolder, isNew);
  }

  /**
   * {@inheritDoc}
   */
  public CalendarEvent removePublicEvent(String calendarId, String eventId) throws Exception {
    Node calendarNode = getPublicCalendarHome().getNode(calendarId);
    if (calendarNode.hasNode(eventId)) {
      Node eventNode = calendarNode.getNode(eventId);
      CalendarEvent event = getEvent(eventNode);
      removeReminder(eventNode);
      eventNode.remove();
      calendarNode.save();
      calendarNode.getSession().save();
      calendarNode.refresh(true);
      try {
        Node eventFolder = getEventFolder(event.getFromDateTime());
        syncRemoveEvent(eventFolder, eventId);
      } catch (Exception e) {
        if (log.isDebugEnabled())
          log.debug(e.getMessage());
      }
      return event;
    }
    return null;
  }

  /**
   * {@inheritDoc}
   */
  public CalendarEvent getEvent(Node eventNode) throws Exception {
    CalendarEvent event = new CalendarEvent();
    event = EventPageListQuery.getEventFromNode(event, eventNode, getReminderFolder(eventNode.getProperty(Utils.EXO_FROM_DATE_TIME).getDate().getTime()));
    if (eventNode.hasProperty(Utils.EXO_RECURRENCE_ID))
      event.setRecurrenceId(eventNode.getProperty(Utils.EXO_RECURRENCE_ID).getString());
    if (eventNode.hasProperty(Utils.EXO_IS_EXCEPTION))
      event.setIsExceptionOccurrence(eventNode.getProperty(Utils.EXO_IS_EXCEPTION).getBoolean());
    if (eventNode.hasProperty(Utils.EXO_REPEAT_UNTIL))
      event.setRepeatUntilDate(eventNode.getProperty(Utils.EXO_REPEAT_UNTIL).getDate().getTime());
    if (eventNode.hasProperty(Utils.EXO_REPEAT_COUNT))
      event.setRepeatCount(eventNode.getProperty(Utils.EXO_REPEAT_COUNT).getLong());
    if (eventNode.hasProperty(Utils.EXO_ORIGINAL_REFERENCE))
      event.setOriginalReference(eventNode.getProperty(Utils.EXO_ORIGINAL_REFERENCE).getString());
    if (eventNode.hasProperty(Utils.EXO_REPEAT_INTERVAL))
      event.setRepeatInterval(eventNode.getProperty(Utils.EXO_REPEAT_INTERVAL).getLong());

    if (eventNode.hasProperty(Utils.EXO_EXCLUDE_ID)) {
      Value[] values = eventNode.getProperty(Utils.EXO_EXCLUDE_ID).getValues();
      if (values.length == 1) {
        event.setExcludeId(new String[] { values[0].getString() });
      } else {
        String[] excludeIds = new String[values.length];
        for (int i = 0; i < values.length; i++) {
          excludeIds[i] = values[i].getString();
        }
        event.setExcludeId(excludeIds);
      }
    }

    if (eventNode.hasProperty(Utils.EXO_REPEAT_BYDAY)) {
      Value[] values = eventNode.getProperty(Utils.EXO_REPEAT_BYDAY).getValues();
      if (values.length == 1) {
        event.setRepeatByDay(new String[] { values[0].getString() });
      } else {
        String[] byDays = new String[values.length];
        for (int i = 0; i < values.length; i++) {
          byDays[i] = values[i].getString();
        }
        event.setRepeatByDay(byDays);
      }
    }

    if (eventNode.hasProperty(Utils.EXO_REPEAT_BYMONTHDAY)) {
      Value[] values = eventNode.getProperty(Utils.EXO_REPEAT_BYMONTHDAY).getValues();
      if (values.length == 1) {
        event.setRepeatByMonthDay(new long[] { values[0].getLong() });
      } else {
        long[] byMonthDays = new long[values.length];
        for (int i = 0; i < values.length; i++) {
          byMonthDays[i] = values[i].getLong();
        }
        event.setRepeatByMonthDay(byMonthDays);
      }
    }
    return event;
  }

  /**
   * {@inheritDoc}
   */
  public void saveEvent(Node calendarNode, CalendarEvent event, Node reminderFolder, boolean isNew) throws Exception {
    Node eventNode;
    Boolean isRepeatNode = false;
    if (isNew) {
      eventNode = calendarNode.addNode(event.getId(), Utils.EXO_CALENDAR_EVENT);
      eventNode.setProperty(Utils.EXO_ID, event.getId());
      if (Utils.isRepeatEvent(event)) {
        eventNode.addMixin(Utils.EXO_REPEAT_CALENDAR_EVENT);
        eventNode.addMixin(Utils.MIX_REFERENCEABLE);
        isRepeatNode = true;
      }

      if (Utils.isExceptionOccurrence(event)) {
        eventNode.addMixin(Utils.EXO_REPEAT_CALENDAR_EVENT);
        isRepeatNode = true;
      }
    } else {
      try {
        eventNode = calendarNode.getNode(event.getId());
        if (Utils.isRepeatEvent(event)) {
          if (!eventNode.isNodeType(Utils.EXO_REPEAT_CALENDAR_EVENT))
            eventNode.addMixin(Utils.EXO_REPEAT_CALENDAR_EVENT);
          if (!eventNode.isNodeType(Utils.MIX_REFERENCEABLE))
            eventNode.addMixin(Utils.MIX_REFERENCEABLE);
          isRepeatNode = true;
        }

      } catch (Exception e) {
        eventNode = calendarNode.addNode(event.getId(), Utils.EXO_CALENDAR_EVENT);
        eventNode.setProperty(Utils.EXO_ID, event.getId());
      }
      try {
        removeReminder(eventNode);
      } catch (Exception e) {
        if (log.isDebugEnabled())
          log.debug(e);
      } finally {
        // systemSession.close() ;
      }
    }

    eventNode.setProperty(Utils.EXO_SUMMARY, event.getSummary());
    eventNode.setProperty(Utils.EXO_CALENDAR_ID, event.getCalendarId());
    eventNode.setProperty(Utils.EXO_EVENT_CATEGORYID, event.getEventCategoryId());
    eventNode.setProperty(Utils.EXO_EVENT_CATEGORY_NAME, event.getEventCategoryName());
    eventNode.setProperty(Utils.EXO_DESCRIPTION, event.getDescription());
    eventNode.setProperty(Utils.EXO_LOCATION, event.getLocation());
    eventNode.setProperty(Utils.EXO_TASK_DELEGATOR, event.getTaskDelegator());

    GregorianCalendar dateTime = Utils.getInstanceTempCalendar();
    // convert date time to GMT time zone before saving to database.
    // dateTime.setTimeZone(TimeZone.getTimeZone("GMT"));
    dateTime.setTime(event.getFromDateTime());
    eventNode.setProperty(Utils.EXO_FROM_DATE_TIME, dateTime);
    dateTime.setTime(event.getToDateTime());
    eventNode.setProperty(Utils.EXO_TO_DATE_TIME, dateTime);
    eventNode.setProperty(Utils.EXO_EVENT_TYPE, event.getEventType());
    eventNode.setProperty(Utils.EXO_REPEAT, event.getRepeatType());
    eventNode.setProperty(Utils.EXO_PRIORITY, event.getPriority());
    eventNode.setProperty(Utils.EXO_IS_PRIVATE, event.isPrivate());
    eventNode.setProperty(Utils.EXO_EVENT_STATE, event.getEventState());
    if (event.getInvitation() == null)
      event.setInvitation(new String[] {});
    eventNode.setProperty(Utils.EXO_INVITATION, event.getInvitation());
    if (event.getParticipant() == null)
      event.setParticipant(new String[] {});
    eventNode.setProperty(Utils.EXO_PARTICIPANT, event.getParticipant());
    List<Reminder> reminders = event.getReminders();
    if (reminders != null && !reminders.isEmpty()) {
      for (Reminder rm : reminders) {
        rm.setFromDateTime(event.getFromDateTime());
        addReminder(eventNode, reminderFolder, rm);
      }
    }
    if (eventNode.hasNode(Utils.ATTACHMENT_NODE)) {
      while (eventNode.getNodes().hasNext()) {
        eventNode.getNodes().nextNode().remove();
      }
      eventNode.save();
    }
    List<Attachment> attachments = event.getAttachment();
    if (attachments != null) {
      for (Attachment att : attachments) {
        addAttachment(eventNode, att, isNew);
      }
    }
    // TODO CS-764
    eventNode.setProperty(Utils.EXO_MESSAGE, event.getMessage());
    eventNode.setProperty(Utils.EXO_SEND_OPTION, event.getSendOption());
    if (event.getParticipantStatus() == null)
      event.setParticipantStatus(new String[] {});
    eventNode.setProperty(Utils.EXO_PARTICIPANT_STATUS, event.getParticipantStatus());

    if (isRepeatNode) {
      if (event.getRecurrenceId() != null)
        eventNode.setProperty(Utils.EXO_RECURRENCE_ID, event.getRecurrenceId());
      else
        eventNode.setProperty(Utils.EXO_RECURRENCE_ID, Utils.EMPTY_STR);

      if (event.getIsExceptionOccurrence() != null)
        eventNode.setProperty(Utils.EXO_IS_EXCEPTION, event.getIsExceptionOccurrence());
      else
        eventNode.setProperty(Utils.EXO_IS_EXCEPTION, (Value) null);

      if (event.getExcludeId() != null && event.getExcludeId().length > 0)
        eventNode.setProperty(Utils.EXO_EXCLUDE_ID, event.getExcludeId());
      else
        eventNode.setProperty(Utils.EXO_EXCLUDE_ID, (Value[]) null);

      if (event.getRepeatUntilDate() != null) {
        dateTime = Utils.getInstanceTempCalendar();
        dateTime.setTime(event.getRepeatUntilDate());
        eventNode.setProperty(Utils.EXO_REPEAT_UNTIL, dateTime);
      } else {
        eventNode.setProperty(Utils.EXO_REPEAT_UNTIL, (Value) null);
      }

      if (event.getOriginalReference() != null)
        eventNode.setProperty(Utils.EXO_ORIGINAL_REFERENCE, event.getOriginalReference());
      else
        eventNode.setProperty(Utils.EXO_ORIGINAL_REFERENCE, (Value) null);

      eventNode.setProperty(Utils.EXO_REPEAT_COUNT, event.getRepeatCount());
      eventNode.setProperty(Utils.EXO_REPEAT_INTERVAL, event.getRepeatInterval());

      if (event.getRepeatByDay() != null)
        eventNode.setProperty(Utils.EXO_REPEAT_BYDAY, event.getRepeatByDay());
      else
        eventNode.setProperty(Utils.EXO_REPEAT_BYDAY, (Value[]) null);

      if (event.getRepeatByMonthDay() != null) {
        long[] monthDays = event.getRepeatByMonthDay();
        String[] byMonthDay = new String[monthDays.length];
        for (int i = 0; i < monthDays.length; i++)
          byMonthDay[i] = String.valueOf(monthDays[i]);
        eventNode.setProperty(Utils.EXO_REPEAT_BYMONTHDAY, byMonthDay);
      } else {
        eventNode.setProperty(Utils.EXO_REPEAT_BYMONTHDAY, (Value[]) null);
      }

      // if event is original repeating event, calculate the end-date to save
      String repeatType = event.getRepeatType();
      if (!Utils.isEmpty(repeatType) && !repeatType.equals(CalendarEvent.RP_NOREPEAT)) {
        Date repeatFinish = calculateRecurrenceFinishDate(event);
        if (repeatFinish != null) {
          dateTime = Utils.getInstanceTempCalendar();
          dateTime.setTime(repeatFinish);
          eventNode.setProperty(Utils.EXO_REPEAT_FINISH_DATE, dateTime);
        }
      }
    }

    calendarNode.getSession().save();
    addEvent(event);
  }

  /**
   * {@inheritDoc}
   */
  public void addReminder(Node eventNode, Node reminderFolder, Reminder reminder) throws Exception {
    Node reminderNode;
    Node catNode;
    try {
      catNode = reminderFolder.getNode(eventNode.getName());
    } catch (Exception e) {
      catNode = reminderFolder.addNode(eventNode.getName(), Utils.NT_UNSTRUCTURED);
    }
    try {
      reminderNode = catNode.getNode(reminder.getId());
    } catch (Exception e) {
      reminderNode = catNode.addNode(reminder.getId(), Utils.EXO_REMINDER);
    }
    reminderNode.setProperty(Utils.EXO_EVENT_ID, eventNode.getName());
    reminderNode.setProperty(Utils.EXO_ALARM_BEFORE, reminder.getAlarmBefore());
    reminderNode.setProperty(Utils.EXO_TIME_INTERVAL, reminder.getRepeatInterval());
    reminderNode.setProperty(Utils.EXO_REMINDER_TYPE, reminder.getReminderType());
    reminderNode.setProperty(Utils.EXO_EMAIL, reminder.getEmailAddress());
    reminderNode.setProperty(Utils.EXO_IS_REPEAT, reminder.isRepeat());
    reminderNode.setProperty(Utils.EXO_IS_OVER, false);
    if (reminder.getReminderType().equals(Reminder.TYPE_POPUP)) {
      reminderNode.setProperty(Utils.EXO_OWNER, reminder.getReminderOwner());
    }
    java.util.Calendar cal = new GregorianCalendar();
    if (reminder.getFromDateTime() != null) {
      cal.setTime(reminder.getFromDateTime());
      reminderNode.setProperty(Utils.EXO_FROM_DATE_TIME, cal);
      long time = reminder.getFromDateTime().getTime() - (reminder.getAlarmBefore() * 60 * 1000);
      cal = new GregorianCalendar();
      cal.setTimeInMillis(time);
      reminderNode.setProperty(Utils.EXO_REMINDER_DATE, cal);
    }
    StringBuffer summary = new StringBuffer("Type      : ");
    summary.append(eventNode.getProperty(Utils.EXO_EVENT_TYPE).getString()).append("<br>");
    summary.append("Summary: ");
    summary.append(eventNode.getProperty(Utils.EXO_SUMMARY).getString()).append("<br>");
    summary.append("Description: ");
    if (eventNode.hasProperty(Utils.EXO_DESCRIPTION))
      summary.append(eventNode.getProperty(Utils.EXO_DESCRIPTION).getString());
    summary.append("<br>");
    summary.append("Location   : ");
    if (eventNode.hasProperty(Utils.EXO_LOCATION))
      summary.append(eventNode.getProperty(Utils.EXO_LOCATION).getString());
    summary.append("<br>");
    if (!Utils.isEmpty(reminder.getReminderOwner())) {
      try {
        cal.setTimeZone(TimeZone.getTimeZone(getCalendarSetting(reminder.getReminderOwner()).getTimeZone()));
      } catch (Exception e) {
        if (log.isDebugEnabled()) {
          log.debug(e);          
        }
      }
    }
    cal.setTime(eventNode.getProperty(Utils.EXO_FROM_DATE_TIME).getDate().getTime());
    appendDateToSummary("From       : ", cal, summary);
    
    cal.setTime(eventNode.getProperty(Utils.EXO_TO_DATE_TIME).getDate().getTime());
    appendDateToSummary("To         : ", cal, summary);
    
    reminderNode.setProperty(Utils.EXO_DESCRIPTION, summary.toString());
    reminderNode.setProperty(Utils.EXO_SUMMARY, eventNode.getProperty(Utils.EXO_SUMMARY).getString());
    if (!reminderFolder.isNew())
      reminderFolder.save();
    else
      reminderFolder.getSession().save();
  }

  private void appendDateToSummary(String label, java.util.Calendar cal, StringBuffer summary) {
    summary.append(label).append(cal.get(java.util.Calendar.HOUR_OF_DAY)).append(Utils.COLON);
    summary.append(cal.get(java.util.Calendar.MINUTE)).append(" - ");
    summary.append(cal.get(java.util.Calendar.DATE)).append(Utils.SLASH);
    summary.append(cal.get(java.util.Calendar.MONTH) + 1).append(Utils.SLASH);
    summary.append(cal.get(java.util.Calendar.YEAR)).append("<br>");
  }

  /**
   * {@inheritDoc}
   */
  public void addEvent(CalendarEvent event) throws Exception {
    Node eventFolder = getEventFolder(event.getFromDateTime());
    Node publicEvent;
    int fromDate;
    int toDate;
    syncRemoveEvent(eventFolder, event.getId());
    CalendarEvent ev = new CalendarEvent();
    publicEvent = eventFolder.addNode(ev.getId(), Utils.EXO_CALENDAR_PUBLIC_EVENT);
    publicEvent.setProperty(Utils.EXO_ID, ev.getId());
    publicEvent.setProperty(Utils.EXO_ROOT_EVENT_ID, event.getId());
    publicEvent.setProperty(Utils.EXO_EVENT_TYPE, event.getEventType());
    publicEvent.setProperty(Utils.EXO_CALENDAR_ID, event.getCalendarId());
    java.util.Calendar dateTime = Utils.getInstanceTempCalendar();
    dateTime.setTime(event.getFromDateTime());
    fromDate = dateTime.get(java.util.Calendar.DAY_OF_YEAR);
    publicEvent.setProperty(Utils.EXO_FROM_DATE_TIME, dateTime);
    publicEvent.setProperty(Utils.EXO_EVENT_STATE, event.getEventState());
    dateTime.setTime(event.getToDateTime());
    toDate = dateTime.get(java.util.Calendar.DAY_OF_YEAR);
    if (toDate > fromDate) {
      java.util.Calendar tmpTime = Utils.getInstanceTempCalendar();
      tmpTime.setTime(event.getFromDateTime());
      tmpTime.set(java.util.Calendar.HOUR_OF_DAY, 0);
      tmpTime.set(java.util.Calendar.MINUTE, 0);
      tmpTime.set(java.util.Calendar.SECOND, 0);
      tmpTime.set(java.util.Calendar.MILLISECOND, 0);
      tmpTime.setTimeInMillis(tmpTime.getTimeInMillis() + (24 * 60 * 60 * 1000) - 1000);
      publicEvent.setProperty(Utils.EXO_TO_DATE_TIME, tmpTime);
    } else {
      publicEvent.setProperty(Utils.EXO_TO_DATE_TIME, dateTime);
    }
    publicEvent.setProperty(Utils.EXO_PARTICIPANT, event.getParticipant());
    try {
      if (!eventFolder.isNew())
        eventFolder.save();
      else
        eventFolder.getSession().save();
    } catch (Exception e) {
      eventFolder.getSession().refresh(true);
      eventFolder.getSession().save();
    }
    try {
      if (toDate > fromDate) {
        java.util.Calendar cal = Utils.getInstanceTempCalendar();
        cal.setTime(event.getFromDateTime());
        cal.set(java.util.Calendar.HOUR_OF_DAY, 0);
        cal.set(java.util.Calendar.MINUTE, 0);
        cal.set(java.util.Calendar.SECOND, 0);
        cal.set(java.util.Calendar.MILLISECOND, 0);
        for (int i = fromDate + 1; i <= toDate; i++) {
          cal.roll(java.util.Calendar.DAY_OF_YEAR, true);
          Node dateFolder = getEventFolder(cal.getTime());
          ev = new CalendarEvent();
          eventFolder.getSession().getWorkspace().copy(publicEvent.getPath(), dateFolder.getPath() + Utils.SLASH + ev.getId());
          dateFolder.getSession().save();
          if (i <= toDate) {
            Node newEvent = dateFolder.getNode(ev.getId());
            newEvent.setProperty(Utils.EXO_ID, ev.getId());
            newEvent.setProperty(Utils.EXO_FROM_DATE_TIME, cal);
            java.util.Calendar tmpCal = Utils.getInstanceTempCalendar();
            if (i == toDate)
              tmpCal.setTime(event.getToDateTime());
            else
              tmpCal.setTimeInMillis(cal.getTimeInMillis() + (24 * 60 * 60 * 1000) - 1000);
            newEvent.setProperty(Utils.EXO_TO_DATE_TIME, tmpCal);
            newEvent.save();
          }
        }
      }
    } catch (Exception e) {
      if (log.isDebugEnabled())
        log.debug(e);
    }
  }

  /**
   * {@inheritDoc}
   */
  public void syncRemoveEvent(Node eventFolder, String rootEventId) throws Exception {
    QueryManager qm = eventFolder.getSession().getWorkspace().getQueryManager();
    StringBuffer queryString = new StringBuffer("/jcr:root" + eventFolder.getParent().getParent().getParent().getPath() + "//element(*,exo:calendarPublicEvent)[@exo:rootEventId='").append(rootEventId).append("']");
    Query query = qm.createQuery(queryString.toString(), Query.XPATH);
    QueryResult result = query.execute();
    NodeIterator it = result.getNodes();
    while (it.hasNext()) {
      it.nextNode().remove();
    }
    eventFolder.getSession().save();
    eventFolder.refresh(true);
  }

  /**
   * {@inheritDoc}
   */
  public Node getReminderFolder(Date fromDate) throws Exception {
    // CS-3165
    Node publicApp = getPublicCalendarServiceHome();
    Node dateFolder = getDateFolder(publicApp, fromDate);
    try {
      return dateFolder.getNode(Utils.CALENDAR_REMINDER);
    } catch (PathNotFoundException pnfe) {
      try {
        dateFolder.addNode(Utils.CALENDAR_REMINDER, Utils.NT_UNSTRUCTURED);
        if (dateFolder.isNew())
          dateFolder.getSession().save();
        else
          dateFolder.save();
      } catch (Exception e) {
        dateFolder.refresh(false);
      }
      return dateFolder.getNode(Utils.CALENDAR_REMINDER);
    }
  }

  /**
   * {@inheritDoc}
   */
  public Node getEventFolder(Date fromDate) throws Exception {
    Node publicApp = getPublicCalendarServiceHome();
    Node dateFolder = getDateFolder(publicApp, fromDate);
    try {
      return dateFolder.getNode(CALENDAR_EVENT);
    } catch (Exception e) {
      dateFolder.addNode(CALENDAR_EVENT, Utils.NT_UNSTRUCTURED);
      getPublicRoot().getSession().save();
      return dateFolder.getNode(CALENDAR_EVENT);

    }
  }

  /**
   * {@inheritDoc}
   */
  public Node getDateFolder(Node publicApp, Date date) throws Exception {
    java.util.Calendar fromCalendar = Utils.getInstanceTempCalendar();
    fromCalendar.setTime(date);
    Node yearNode;
    Node monthNode;
    String year = "Y" + String.valueOf(fromCalendar.get(java.util.Calendar.YEAR));
    String month = "M" + String.valueOf(fromCalendar.get(java.util.Calendar.MONTH) + 1);
    String day = "D" + String.valueOf(fromCalendar.get(java.util.Calendar.DATE));
    try {
      yearNode = publicApp.getNode(year);
    } catch (Exception e) {
      yearNode = publicApp.addNode(year, Utils.NT_UNSTRUCTURED);
    }
    try {
      monthNode = yearNode.getNode(month);
    } catch (Exception e) {
      monthNode = yearNode.addNode(month, Utils.NT_UNSTRUCTURED);
    }
    try {
      return monthNode.getNode(day);
    } catch (Exception e) {
      return monthNode.addNode(day, Utils.NT_UNSTRUCTURED);
    }
  }

  /**
   * {@inheritDoc}
   */
  public void addAttachment(Node eventNode, Attachment attachment, boolean isNew) throws Exception {
    Node attachHome;
    Node attachNode;
    // fix load image on IE6 UI
    ExtendedNode extNode = (ExtendedNode) eventNode;
    if (extNode.canAddMixin("exo:privilegeable"))
      extNode.addMixin("exo:privilegeable");
    String[] arrayPers = { PermissionType.READ, PermissionType.ADD_NODE, PermissionType.SET_PROPERTY, PermissionType.REMOVE };
    extNode.setPermission(SystemIdentity.ANY, arrayPers);
    List<AccessControlEntry> permsList = extNode.getACL().getPermissionEntries();
    for (AccessControlEntry accessControlEntry : permsList) {
      extNode.setPermission(accessControlEntry.getIdentity(), arrayPers);
    }
    try {
      attachHome = eventNode.getNode(Utils.ATTACHMENT_NODE);
    } catch (Exception e) {
      attachHome = eventNode.addNode(Utils.ATTACHMENT_NODE, Utils.NT_UNSTRUCTURED);
    }
    String name = attachment.getId().substring(attachment.getId().lastIndexOf(Utils.SLASH) + 1);
    try {
      attachNode = attachHome.getNode(name);
    } catch (Exception e) {
      attachNode = attachHome.addNode(name, Utils.EXO_EVEN_TATTACHMENT);
    }
    attachNode.setProperty(Utils.EXO_FILE_NAME, attachment.getName());
    Node nodeContent = null;
    try {
      nodeContent = attachNode.getNode(Utils.JCR_CONTENT);
    } catch (Exception e) {
      nodeContent = attachNode.addNode(Utils.JCR_CONTENT, Utils.NT_RESOURCE);
    }
    nodeContent.setProperty(Utils.JCR_LASTMODIFIED, java.util.Calendar.getInstance().getTimeInMillis());
    nodeContent.setProperty(Utils.JCR_MIMETYPE, attachment.getMimeType());
    nodeContent.setProperty(Utils.JCR_DATA, attachment.getInputStream());
  }

  /**
   * {@inheritDoc}
   */
  public void saveCalendarSetting(String username, CalendarSetting setting) throws Exception {
    Node calendarHome = getUserCalendarServiceHome(username);
    addCalendarSetting(calendarHome, setting);
    Session session = calendarHome.getSession();
    session.save();
  }

  private void saveCalendarSetting(CalendarSetting setting, String username) throws Exception {
    Node calendarHome = getUserCalendarServiceHome(username);
    addCalendarSetting(calendarHome, setting);
    calendarHome.save();
  }

  /**
   * {@inheritDoc}
   */
  public void addCalendarSetting(Node calendarHome, CalendarSetting setting) throws Exception {
    Node settingNode;
    try {
      settingNode = calendarHome.getNode(CALENDAR_SETTING);
    } catch (Exception e) {
      settingNode = calendarHome.addNode(CALENDAR_SETTING, Utils.EXO_CALENDAR_SETTING);
    }
    settingNode.setProperty(Utils.EXO_VIEW_TYPE, setting.getViewType());
    settingNode.setProperty(Utils.EXO_TIME_INTERVAL, setting.getTimeInterval());
    settingNode.setProperty(Utils.EXO_WEEK_START_ON, setting.getWeekStartOn());
    settingNode.setProperty(Utils.EXO_DATE_FORMAT, setting.getDateFormat());
    settingNode.setProperty(Utils.EXO_TIME_FORMAT, setting.getTimeFormat());
    settingNode.setProperty(Utils.EXO_LOCATION, setting.getLocation());
    settingNode.setProperty(Utils.EXO_TIMEZONE, setting.getTimeZone());
    settingNode.setProperty(Utils.EXO_IS_SHOW_WORKING_TIME, setting.isShowWorkingTime());
    if (setting.isShowWorkingTime()) {
      settingNode.setProperty(Utils.EXO_WORKING_BEGIN, setting.getWorkingTimeBegin());
      settingNode.setProperty(Utils.EXO_WORKING_END, setting.getWorkingTimeEnd());
    }
    settingNode.setProperty(Utils.EXO_BASE_URL, setting.getBaseURL());
    settingNode.setProperty(Utils.EXO_PRIVATE_CALENDARS, setting.getFilterPrivateCalendars());
    settingNode.setProperty(Utils.EXO_PUBLIC_CALENDARS, setting.getFilterPublicCalendars());
    settingNode.setProperty(Utils.EXO_SHARED_CALENDARS, setting.getFilterSharedCalendars());
    settingNode.setProperty(Utils.EXO_SHARED_CALENDAR_COLORS, setting.getSharedCalendarsColors());
    settingNode.setProperty(Utils.EXO_SEND_OPTION, setting.getSendOption());
  }

  /**
   * {@inheritDoc}
   */
  public CalendarSetting getCalendarSetting(String username) throws Exception {
    Node calendarHome = getUserCalendarServiceHome(username);
    return getCalendarSetting(calendarHome);
  }

  /**
   * 
   * @param calendarHome
   * @return
   * @throws Exception
   */
  private CalendarSetting getCalendarSetting(Node calendarHome) throws Exception {
    if (calendarHome.hasNode(CALENDAR_SETTING)) {
      CalendarSetting calendarSetting = new CalendarSetting();
      Node settingNode = calendarHome.getNode(CALENDAR_SETTING);
      calendarSetting.setViewType(settingNode.getProperty(Utils.EXO_VIEW_TYPE).getString());
      calendarSetting.setTimeInterval(settingNode.getProperty(Utils.EXO_TIME_INTERVAL).getLong());
      if (settingNode.hasProperty(Utils.EXO_WEEK_START_ON))
        calendarSetting.setWeekStartOn(settingNode.getProperty(Utils.EXO_WEEK_START_ON).getString());
      if (settingNode.hasProperty(Utils.EXO_DATE_FORMAT))
        calendarSetting.setDateFormat(settingNode.getProperty(Utils.EXO_DATE_FORMAT).getString());
      if (settingNode.hasProperty(Utils.EXO_TIME_FORMAT))
        calendarSetting.setTimeFormat(settingNode.getProperty(Utils.EXO_TIME_FORMAT).getString());
      if (settingNode.hasProperty(Utils.EXO_SEND_OPTION))
        calendarSetting.setSendOption(settingNode.getProperty(Utils.EXO_SEND_OPTION).getString());
      if (settingNode.hasProperty(Utils.EXO_BASE_URL))
        calendarSetting.setBaseURL(settingNode.getProperty(Utils.EXO_BASE_URL).getString());
      if (settingNode.hasProperty(Utils.EXO_LOCATION))
        calendarSetting.setLocation(settingNode.getProperty(Utils.EXO_LOCATION).getString());
      if (settingNode.hasProperty(Utils.EXO_TIMEZONE))
        calendarSetting.setTimeZone(settingNode.getProperty(Utils.EXO_TIMEZONE).getString());
      if (settingNode.hasProperty(Utils.EXO_IS_SHOW_WORKING_TIME)) {
        calendarSetting.setShowWorkingTime(settingNode.getProperty(Utils.EXO_IS_SHOW_WORKING_TIME).getBoolean());
      }
      if (calendarSetting.isShowWorkingTime()) {
        if (settingNode.hasProperty(Utils.EXO_WORKING_BEGIN))
          calendarSetting.setWorkingTimeBegin(settingNode.getProperty(Utils.EXO_WORKING_BEGIN).getString());
        if (settingNode.hasProperty(Utils.EXO_WORKING_END))
          calendarSetting.setWorkingTimeEnd(settingNode.getProperty(Utils.EXO_WORKING_END).getString());
      }
      if (settingNode.hasProperty(Utils.EXO_PRIVATE_CALENDARS)) {
        Value[] values = settingNode.getProperty(Utils.EXO_PRIVATE_CALENDARS).getValues();
        String[] calendars = new String[values.length];
        for (int i = 0; i < values.length; i++) {
          calendars[i] = values[i].getString();
        }
        calendarSetting.setFilterPrivateCalendars(calendars);
      }
      if (settingNode.hasProperty(Utils.EXO_PUBLIC_CALENDARS)) {
        Value[] values = settingNode.getProperty(Utils.EXO_PUBLIC_CALENDARS).getValues();
        String[] calendars = new String[values.length];
        for (int i = 0; i < values.length; i++) {
          calendars[i] = values[i].getString();
        }
        calendarSetting.setFilterPublicCalendars(calendars);
      }

      if (settingNode.hasProperty(Utils.EXO_SHARED_CALENDARS)) {
        Value[] values = settingNode.getProperty(Utils.EXO_SHARED_CALENDARS).getValues();
        String[] calendars = new String[values.length];
        for (int i = 0; i < values.length; i++) {
          calendars[i] = values[i].getString();
        }
        calendarSetting.setFilterSharedCalendars(calendars);
      }
      if (settingNode.hasProperty(Utils.EXO_SHARED_CALENDAR_COLORS)) {
        Value[] values = settingNode.getProperty(Utils.EXO_SHARED_CALENDAR_COLORS).getValues();
        String[] calendarsColors = new String[values.length];
        for (int i = 0; i < values.length; i++) {
          calendarsColors[i] = values[i].getString();
        }
        calendarSetting.setSharedCalendarsColors(calendarsColors);
      }
      return calendarSetting;
    }
    return null;
  }

  /**
   * {@inheritDoc}
   */
  public void storeXML(String feedXML, Node rssHome, RssData rssData) throws Exception {
    Node rss;
    String rssNodeName = rssData.getName();
    if (rssHome.hasNode(rssNodeName))
      rss = rssHome.getNode(rssNodeName);
    else
      rss = rssHome.addNode(rssNodeName, Utils.EXO_RSS_DATA);
    rss.setProperty(Utils.EXO_BASE_URL, rssData.getUrl());
    rss.setProperty(Utils.EXO_TITLE, rssData.getTitle());
    rss.setProperty(Utils.EXO_CONTENT, new ByteArrayInputStream(feedXML.getBytes()));
  }

  /**
   * {@inheritDoc}
   */
  @Deprecated
  public int generateCalDav(String username, LinkedHashMap<String, Calendar> calendars, RssData rssData, CalendarImportExport importExport) throws Exception {
    Node rssHomeNode = getRssHome(username);
    Node iCalHome = null;
    try {
      iCalHome = rssHomeNode.getNode(Utils.CALDAV_NODE);
    } catch (Exception e) {
      iCalHome = rssHomeNode.addNode(Utils.CALDAV_NODE, Utils.NT_UNSTRUCTURED);
    }
    try {
      SyndFeed feed = new SyndFeedImpl();
      feed.setFeedType(rssData.getVersion());
      feed.setTitle(rssData.getTitle());
      feed.setLink(rssData.getLink());
      feed.setDescription(rssData.getDescription());
      List<SyndEntry> entries = new ArrayList<SyndEntry>();
      SyndEntry entry;
      SyndContent description;
      for (String calendarMap : calendars.keySet()) {
        String calendarId = calendarMap.split(Utils.SPLITTER)[0];
        String type = calendarMap.split(Utils.SPLITTER)[1];
        OutputStream out = importExport.exportCalendar(username, Arrays.asList(new String[] { calendarId }), type, -1);
        if (out != null) {
          ByteArrayInputStream is = new ByteArrayInputStream(out.toString().getBytes());
          Node ical = null;
          Node nodeContent = null;
          try {
            ical = iCalHome.getNode(calendarMap + Utils.ICS_EXT);
            nodeContent = ical.getNode(Utils.JCR_CONTENT);
          } catch (Exception e) {
            ical = iCalHome.addNode(calendarMap + Utils.ICS_EXT, Utils.NT_FILE);
            nodeContent = ical.addNode(Utils.JCR_CONTENT, Utils.NT_RESOURCE);
          }
          nodeContent.setProperty(Utils.JCR_LASTMODIFIED, java.util.Calendar.getInstance().getTimeInMillis());
          nodeContent.setProperty(Utils.JCR_MIMETYPE, Utils.MIMETYPE_ICALENDAR);
          nodeContent.setProperty(Utils.JCR_DATA, is);
          if (!iCalHome.isNew())
            iCalHome.save();
          else
            iCalHome.getSession().save();
          String link = rssData.getLink() + ical.getPath();
          Calendar exoCal = calendars.get(calendarMap);
          entry = new SyndEntryImpl();
          entry.setTitle(exoCal.getName());
          entry.setLink(link);
          description = new SyndContentImpl();
          description.setType(Utils.MIMETYPE_TEXTPLAIN);
          description.setValue(exoCal.getDescription());
          entry.setDescription(description);
          entry.setAuthor(username);
          entries.add(entry);
          entry.getEnclosures();
        }
      }
      if (!entries.isEmpty()) {
        feed.setEntries(entries);
        feed.setEncoding("UTF-8");
        SyndFeedOutput output = new SyndFeedOutput();
        String feedXML = output.outputString(feed);
        feedXML = StringUtils.replace(feedXML, "&amp;", "&");
        storeXML(feedXML, rssHomeNode, rssData);
        rssHomeNode.getSession().save();
      } else {
        log.info("No data to make caldav!");
        return -1;
      }
    } catch (Exception e) {
      if (log.isDebugEnabled())
        log.debug(e);
      return -1;
    }
    return 1;
  }

  /**
   * {@inheritDoc}
   */
  @SuppressWarnings("unchecked")
  public void removeFeed(String username, String calendarId) throws Exception {
    Node rssHome = getRssHome(username);
    NodeIterator iter = rssHome.getNodes();
    List<String> removedFeedNodes = new ArrayList<String>();
    while (iter.hasNext()) {
      Node feedNode = iter.nextNode();
      if (feedNode.isNodeType(Utils.EXO_RSS_DATA)) {
        FeedData feedData = new FeedData();
        feedData.setTitle(feedNode.getProperty("exo:title").getString());
        StringBuffer url = new StringBuffer(feedNode.getProperty(Utils.EXO_BASE_URL).getString());
        url.append("/").append(PortalContainer.getCurrentPortalContainerName());
        url.append("/").append(feedNode.getSession().getWorkspace().getName());
        url.append("/").append(username);
        url.append("/").append(feedNode.getName());
        feedData.setUrl(url.toString());

        URL feedUrl = new URL(feedData.getUrl());
        SyndFeedInput input = new SyndFeedInput();
        SyndFeed feed = input.build(new XmlReader(feedUrl));

        List entries = feed.getEntries();
        List<SyndEntry> listBefore = new ArrayList<SyndEntry>();
        listBefore.addAll(entries);
        for (int i = 0; i < listBefore.size(); i++) {
          SyndEntry entry = listBefore.get(i);
          String id = entry.getLink().substring(entry.getLink().lastIndexOf("/") + 1);
          if (id.contains(calendarId)) {
            listBefore.remove(i);
            i--;
          }
        }
        if (listBefore.size() == 0) {
          removedFeedNodes.add(feedNode.getName());
        } else {
          feed.setEntries(listBefore);
          SyndFeedOutput output = new SyndFeedOutput();
          String feedXML = output.outputString(feed);
          feedXML = StringUtils.replace(feedXML, "&amp;", "&");
          feedNode.setProperty(Utils.EXO_CONTENT, new ByteArrayInputStream(feedXML.getBytes()));
          feedNode.save();
        }
      }
    }
    if (removedFeedNodes.size() > 0) {
      for (String s : removedFeedNodes) {
        if (rssHome.getNode(s) != null) {
          rssHome.getNode(s).remove();
        }
      }
      rssHome.getSession().save();
    }

    // remove calDav
    if (rssHome.hasNode(Utils.CALDAV_NODE)) {
      iter = rssHome.getNode(Utils.CALDAV_NODE).getNodes();
      while (iter.hasNext()) {
        Node rssCal = iter.nextNode();
        if (rssCal.getPath().contains(calendarId)) {
          rssCal.remove();
        }
      }
    }
    // remove RSS
    if (rssHome.hasNode(Utils.RSS_NODE)) {
      iter = rssHome.getNode(Utils.RSS_NODE).getNodes();
      while (iter.hasNext()) {
        Node rssCal = iter.nextNode();
        if (rssCal.getPath().contains(calendarId)) {
          rssCal.remove();
        }
      }
    }
    rssHome.getSession().save();
  }

  /**
   * 
   * @param username
   * @param title
   */
  public void removeFeedData(String username, String title) {
    try {
      Node rssHome = getRssHome(username);
      NodeIterator iter = rssHome.getNodes();
      while (iter.hasNext()) {
        Node feedNode = iter.nextNode();
        if (feedNode.isNodeType(Utils.EXO_RSS_DATA)) {
          if (feedNode.getProperty(Utils.EXO_TITLE).getString().equals(title)) {
            feedNode.remove();
            break;
          }
        }
      }
      rssHome.getSession().save();
    } catch (Exception ex) {
      if (log.isDebugEnabled()) {
        log.debug("Fail to remove feed data", ex);
      }
    }

  }

  /**
   * 
   */
  public List<FeedData> getFeeds(String username) throws Exception {
    List<FeedData> feeds = new ArrayList<FeedData>();
    try {
      Node rssHome = getRssHome(username);
      NodeIterator iter = rssHome.getNodes();
      while (iter.hasNext()) {
        Node feedNode = iter.nextNode();
        if (feedNode.isNodeType(Utils.EXO_RSS_DATA)) {
          FeedData feed = new FeedData();
          feed.setTitle(feedNode.getProperty(Utils.EXO_TITLE).getString());
          StringBuffer url = new StringBuffer(feedNode.getProperty(Utils.EXO_BASE_URL).getString());

          feed.setUrl(url.toString());

          feed.setContent(feedNode.getProperty(Utils.EXO_CONTENT).getStream());

          feeds.add(feed);
        }
      }
    } catch (Exception e) {
      log.debug(e);
    }
    return feeds;
  }

  /**
   * {@inheritDoc}
   */
  @Deprecated
  public int generateRss(String username, List<String> calendarIds, RssData rssData, CalendarImportExport importExport) throws Exception {

    Node rssHomeNode = getRssHome(username);
    Node iCalHome = null;
    try {
      iCalHome = rssHomeNode.getNode(Utils.RSS_NODE);
    } catch (Exception e) {
      iCalHome = rssHomeNode.addNode(Utils.RSS_NODE, Utils.NT_UNSTRUCTURED);
    }
    try {
      SyndFeed feed = new SyndFeedImpl();
      feed.setFeedType(rssData.getVersion());
      feed.setTitle(rssData.getTitle());
      feed.setLink(rssData.getLink());
      feed.setDescription(rssData.getDescription());
      List<SyndEntry> entries = new ArrayList<SyndEntry>();
      SyndEntry entry;
      SyndContent description;
      String portalName = PortalContainer.getCurrentPortalContainerName();
      for (String calendarId : calendarIds) {
        OutputStream out = importExport.exportCalendar(username, Arrays.asList(new String[] { calendarId }), "0", -1);
        if (out != null) {
          ByteArrayInputStream is = new ByteArrayInputStream(out.toString().getBytes());
          try {
            iCalHome.getNode(calendarId + Utils.ICS_EXT).setProperty(Utils.EXO_DATA, is);
          } catch (Exception e) {
            Node ical = iCalHome.addNode(calendarId + Utils.ICS_EXT, Utils.EXO_ICAL_DATA);
            ical.setProperty(Utils.EXO_DATA, is);
          }
          StringBuffer path = new StringBuffer(Utils.SLASH);
          path.append(iCalHome.getName()).append(Utils.SLASH).append(iCalHome.getNode(calendarId + Utils.ICS_EXT).getName());
          String url = getEntryUrl(portalName, rssHomeNode.getSession().getWorkspace().getName(), username, path.toString(), rssData.getUrl());
          Calendar exoCal = getUserCalendar(username, calendarId);
          entry = new SyndEntryImpl();
          entry.setTitle(exoCal.getName());
          entry.setLink(url);
          entry.setAuthor(username);
          description = new SyndContentImpl();
          description.setType(Utils.MIMETYPE_TEXTPLAIN);
          description.setValue(exoCal.getDescription());
          entry.setDescription(description);
          entries.add(entry);
          entry.getEnclosures();
        }
      }
      if (!entries.isEmpty()) {
        feed.setEntries(entries);
        feed.setEncoding("UTF-8");
        SyndFeedOutput output = new SyndFeedOutput();
        String feedXML = output.outputString(feed);
        feedXML = StringUtils.replace(feedXML, "&amp;", "&");
        storeXML(feedXML, rssHomeNode, rssData);
        rssHomeNode.getSession().save();
      } else {
        log.info("No data to make rss!");
        return -1;
      }
    } catch (Exception e) {
      if (log.isDebugEnabled())
        log.debug(e);
      return -1;
    }
    return 1;
  }

  /**
   * {@inheritDoc}
   */
  @Deprecated
  public int generateRss(String username, LinkedHashMap<String, Calendar> calendars, RssData rssData, CalendarImportExport importExport) throws Exception {
    Node rssHomeNode = getRssHome(username);
    try {
      SyndFeed feed = new SyndFeedImpl();
      feed.setFeedType(rssData.getVersion());
      feed.setTitle(rssData.getTitle());
      feed.setLink(rssData.getLink());
      feed.setDescription(rssData.getDescription());
      List<SyndEntry> entries = new ArrayList<SyndEntry>();
      SyndEntry entry;
      SyndContent description;

      // String portalName = PortalContainer.getCurrentPortalContainerName();
      for (String calendarMap : calendars.keySet()) {
        String calendarId = calendarMap.split(Utils.COLON)[1];
        String type = calendarMap.split(Utils.COLON)[0];
        OutputStream out = importExport.exportCalendar(username, Arrays.asList(new String[] { calendarId }), type, -1);
        if (out != null) {
          Calendar exoCal = calendars.get(calendarMap);
          entry = new SyndEntryImpl();
          entry.setTitle(exoCal.getName());

          entry.setLink(rssHomeNode.getPath() + Utils.SLASH + calendarId);
          entry.setAuthor(username);
          description = new SyndContentImpl();
          description.setType(Utils.MIMETYPE_TEXTPLAIN);
          description.setValue(exoCal.getDescription());
          entry.setDescription(description);
          entries.add(entry);
          entry.getEnclosures();
        }
      }
      if (!entries.isEmpty()) {
        feed.setEntries(entries);
        feed.setEncoding("UTF-8");
        SyndFeedOutput output = new SyndFeedOutput();
        String feedXML = output.outputString(feed);
        feedXML = StringUtils.replace(feedXML, "&amp;", "&");
        storeXML(feedXML, rssHomeNode, rssData);
        rssHomeNode.getSession().save();
      } else {
        log.info("No data to make rss!");
        return -1;
      }
    } catch (Exception e) {
      if (log.isDebugEnabled())
        log.debug(e);
      return -1;
    }
    return 1;
  }

  /**
   * {@inheritDoc}
   */
  @Deprecated
  public void updateRss(String username, String calendarId, CalendarImportExport imp) throws Exception {
    calendarId = calendarId.substring(0, calendarId.lastIndexOf("."));
    String id = calendarId.split(Utils.SPLITTER)[0];
    String type = calendarId.split(Utils.SPLITTER)[1];
    Node rssHome = getRssHome(username);
    if (rssHome.hasNode(Utils.RSS_NODE)) {
      NodeIterator iter = rssHome.getNode(Utils.RSS_NODE).getNodes();
      while (iter.hasNext()) {
        Node rssCal = iter.nextNode();
        if (rssCal.getPath().contains(calendarId)) {
          OutputStream out = imp.exportCalendar(username, Arrays.asList(new String[] { id }), type, -1);
          if (out != null) {
            ByteArrayInputStream is = new ByteArrayInputStream(out.toString().getBytes());
            rssCal.setProperty(Utils.EXO_DATA, is);
            rssCal.save();
          } else {
            removeFeed(username, id);
            /*
             * rssCal.remove() ; rssHome.getSession().save() ;
             */
          }
          break;
        }
      }
    }
  }

  /**
   * {@inheritDoc}
   */
  @Deprecated
  public void updateRss(String username, String calendarId, CalendarImportExport imp, int number) throws Exception {
    calendarId = calendarId.substring(0, calendarId.lastIndexOf("."));
    String id = calendarId.split(Utils.SPLITTER)[0];
    String type = calendarId.split(Utils.SPLITTER)[1];
    Node rssHome = getRssHome(username);
    if (rssHome.hasNode(Utils.RSS_NODE)) {
      NodeIterator iter = rssHome.getNode(Utils.RSS_NODE).getNodes();
      while (iter.hasNext()) {
        Node rssCal = iter.nextNode();
        if (rssCal.getPath().contains(calendarId)) {
          OutputStream out = imp.exportCalendar(username, Arrays.asList(new String[] { id }), type, number);
          if (out != null) {
            ByteArrayInputStream is = new ByteArrayInputStream(out.toString().getBytes());
            rssCal.setProperty(Utils.EXO_DATA, is);
            rssCal.save();
          } else {
            removeFeed(username, id);
          }
          break;
        }
      }
    }
  }

  /**
   * {@inheritDoc}
   */
  @Deprecated
  public void updateCalDav(String username, String calendarId, CalendarImportExport imp) throws Exception {
    calendarId = calendarId.substring(0, calendarId.lastIndexOf("."));
    String id = calendarId.split(Utils.SPLITTER)[0];
    String type = calendarId.split(Utils.SPLITTER)[1];
    Node rssHome = getRssHome(username);
    if (rssHome.hasNode(Utils.CALDAV_NODE)) {
      NodeIterator iter = rssHome.getNode(Utils.CALDAV_NODE).getNodes();
      while (iter.hasNext()) {
        Node rssCal = iter.nextNode();
        Node nodeContent = rssCal.getNode(Utils.JCR_CONTENT);
        OutputStream out = imp.exportCalendar(username, Arrays.asList(new String[] { id }), type, -1);
        if (out != null) {
          ByteArrayInputStream is = new ByteArrayInputStream(out.toString().getBytes());
          nodeContent.setProperty(Utils.JCR_DATA, is);
        } else {
          removeFeed(username, id);
        }
      }
      rssHome.getSession().save();
    }
  }

  /**
   * {@inheritDoc}
   */
  @Deprecated
  public void updateCalDav(String username, String calendarId, CalendarImportExport imp, int number) throws Exception {
    calendarId = calendarId.substring(0, calendarId.lastIndexOf("."));
    String id = calendarId.split(Utils.SPLITTER)[0];
    String type = calendarId.split(Utils.SPLITTER)[1];
    Node rssHome = getRssHome(username);
    if (rssHome.hasNode(Utils.CALDAV_NODE)) {
      NodeIterator iter = rssHome.getNode(Utils.CALDAV_NODE).getNodes();
      while (iter.hasNext()) {
        Node rssCal = iter.nextNode();
        Node nodeContent = rssCal.getNode(Utils.JCR_CONTENT);
        OutputStream out = imp.exportCalendar(username, Arrays.asList(new String[] { id }), type, number);
        if (out != null) {
          ByteArrayInputStream is = new ByteArrayInputStream(out.toString().getBytes());
          nodeContent.setProperty(Utils.JCR_DATA, is);
        } else {
          removeFeed(username, id);
        }
      }
      rssHome.getSession().save();
    }
  }

  /**
   * {@inheritDoc}
   */
  @Deprecated
  public int generateCalDav(String username, List<String> calendarIds, RssData rssData, CalendarImportExport importExport) throws Exception {
    Node rssHomeNode = getRssHome(username);
    Node iCalHome = null;
    try {
      iCalHome = rssHomeNode.getNode(Utils.CALDAV_NODE);
    } catch (Exception e) {
      iCalHome = rssHomeNode.addNode(Utils.CALDAV_NODE, Utils.NT_UNSTRUCTURED);
    }
    try {
      SyndFeed feed = new SyndFeedImpl();
      feed.setFeedType(rssData.getVersion());
      feed.setTitle(rssData.getTitle());
      feed.setLink(rssData.getLink());
      feed.setDescription(rssData.getDescription());
      List<SyndEntry> entries = new ArrayList<SyndEntry>();
      SyndEntry entry;
      SyndContent description;
      for (String calendarId : calendarIds) {
        OutputStream out = importExport.exportCalendar(username, Arrays.asList(new String[] { calendarId }), "0", -1);
        if (out != null) {
          ByteArrayInputStream is = new ByteArrayInputStream(out.toString().getBytes());
          Node ical = null;
          Node nodeContent = null;
          try {
            ical = iCalHome.getNode(calendarId + Utils.ICS_EXT);
            nodeContent = ical.getNode(Utils.JCR_CONTENT);
          } catch (Exception e) {
            ical = iCalHome.addNode(calendarId + Utils.ICS_EXT, Utils.NT_FILE);
            nodeContent = ical.addNode(Utils.JCR_CONTENT, Utils.NT_RESOURCE);
          }
          nodeContent.setProperty(Utils.JCR_LASTMODIFIED, java.util.Calendar.getInstance().getTimeInMillis());
          nodeContent.setProperty(Utils.JCR_MIMETYPE, Utils.MIMETYPE_ICALENDAR);
          nodeContent.setProperty(Utils.JCR_DATA, is);
          if (!iCalHome.isNew())
            iCalHome.save();
          else
            iCalHome.getSession().save();
          String link = rssData.getLink() + ical.getPath();
          Calendar exoCal = getUserCalendar(username, calendarId);
          entry = new SyndEntryImpl();
          entry.setTitle(exoCal.getName());
          entry.setLink(link);
          description = new SyndContentImpl();
          description.setType(Utils.MIMETYPE_TEXTPLAIN);
          description.setValue(exoCal.getDescription());
          entry.setDescription(description);
          entry.setAuthor(username);
          entries.add(entry);
          entry.getEnclosures();
        }
      }
      if (!entries.isEmpty()) {
        feed.setEntries(entries);
        feed.setEncoding("UTF-8");
        SyndFeedOutput output = new SyndFeedOutput();
        String feedXML = output.outputString(feed);
        feedXML = StringUtils.replace(feedXML, "&amp;", "&");
        storeXML(feedXML, rssHomeNode, rssData);
        rssHomeNode.getSession().save();
      } else {
        log.info("No data to make caldav!");
        return -1;
      }

    } catch (Exception e) {
      if (log.isDebugEnabled())
        log.debug(e);
      return -1;
    }
    return 1;
  }

  /**
   * {@inheritDoc}
   */
  public String getEntryUrl(String portalName, String wsName, String username, String path, String baseUrl) throws Exception {
    StringBuilder url = new StringBuilder(baseUrl);
    url.append(Utils.SLASH).append(portalName).append(Utils.SLASH).append(wsName);
    if (username != null)
      url.append(Utils.SLASH).append(username);
    url.append(path);
    return url.toString();
  }

  /**
   * {@inheritDoc}
   */
  public EventPageList searchEvent(String username, EventQuery eventQuery, String[] publicCalendarIds) throws Exception {
    List<CalendarEvent> events = new ArrayList<CalendarEvent>();
    try {
      if (eventQuery.getCalendarId() == null) {
        events.addAll(getUserEvents(username, eventQuery));
        if (publicCalendarIds != null && publicCalendarIds.length > 0) {
          eventQuery.setCalendarId(publicCalendarIds);
          events.addAll(getPublicEvents(eventQuery));
          eventQuery.setCalendarId(null);
        }
        events.addAll(getSharedEvents(username, eventQuery));
      } else {
        String calFullId = eventQuery.getCalendarId()[0];
        if (calFullId.split(Utils.COLON).length > 0) {
          String[] calId = new String[] { calFullId.split(Utils.COLON)[1] };
          int type = Integer.parseInt(calFullId.split(Utils.COLON)[0]);
          eventQuery.setCalendarId(calId);
          switch (type) {
          case Calendar.TYPE_PRIVATE:
            events.addAll(getUserEvents(username, eventQuery));
            return new EventPageList(events, 10);
          case Calendar.TYPE_SHARED:
            events.addAll(getSharedEvents(username, eventQuery));
            return new EventPageList(events, 10);
          case Calendar.TYPE_PUBLIC:
            events.addAll(getPublicEvents(eventQuery));
            return new EventPageList(events, 10);
          default:
            break;
          }
        }
      }
    } catch (Exception e) {
      if (log.isDebugEnabled())
        log.debug(e);
    } finally {
      // systemSession.close() ;
    }
    return new EventPageList(events, 10);
  }

  /**
   * {@inheritDoc}
   */
  public Map<Integer, String> searchHightLightEvent(String username, EventQuery eventQuery, String[] publicCalendarIds) throws Exception {
    Map<Integer, String> mapData = new HashMap<Integer, String>();
    Query query;
    Node calendarHome = getUserCalendarHome(username);
    try {
      CalendarSetting calSetting = getCalendarSetting(getUserCalendarServiceHome(username));
      Node calendarShareNode = getSharedCalendarHome();
      Node publicCalHome = getPublicCalendarHome();
      QueryManager qm = publicCalHome.getSession().getWorkspace().getQueryManager();
      // public events
      if (username != null && username.length() > 0) {
        eventQuery.setCalendarPath(calendarHome.getPath());
        query = qm.createQuery(eventQuery.getQueryStatement(), Query.XPATH);
        NodeIterator it = query.execute().getNodes();
        mapData = updateMap(mapData, it, eventQuery.getFromDate(), eventQuery.getToDate(), calSetting.getFilterPrivateCalendars());
      }
      // shared events
      if (calendarShareNode.hasNode(username)) {
        PropertyIterator iter = calendarShareNode.getNode(username).getReferences();
        while (iter.hasNext()) {
          try {
            Node calendar = iter.nextProperty().getParent();
            eventQuery.setCalendarPath(calendar.getPath());
            query = qm.createQuery(eventQuery.getQueryStatement(), Query.XPATH);
            NodeIterator it = query.execute().getNodes();
            mapData = updateMap(mapData, it, eventQuery.getFromDate(), eventQuery.getToDate(), calSetting.getFilterSharedCalendars());
          } catch (Exception e) {
            if (log.isDebugEnabled()) {
              log.debug("Fail to update map of shared event", e);
            }
          }
        }
      }
      // public events
      eventQuery.setCalendarPath(publicCalHome.getPath());
      eventQuery.setCalendarId(publicCalendarIds);
      query = qm.createQuery(eventQuery.getQueryStatement(), Query.XPATH);
      NodeIterator it = query.execute().getNodes();
      mapData = updateMap(mapData, it, eventQuery.getFromDate(), eventQuery.getToDate(), calSetting.getFilterPublicCalendars());
    } catch (Exception e) {
      if (log.isDebugEnabled()) {
        log.debug("Fail to search hight light event", e);
      }
    }

    return mapData;
  }

  public Map<Integer, String> searchHighlightRecurrenceEvent(String username, EventQuery eventQuery, String[] publicCalendarIds, String timezone) throws Exception {
    Map<Integer, String> mapData = new HashMap<Integer, String>();
    int fromDayOfYear = eventQuery.getFromDate().get(java.util.Calendar.DAY_OF_YEAR);
    int toDayOfYear = eventQuery.getFromDate().get(java.util.Calendar.DAY_OF_YEAR);
    int daysOfYear = eventQuery.getFromDate().getMaximum(java.util.Calendar.DAY_OF_YEAR);
    if (eventQuery.getToDate().get(java.util.Calendar.DAY_OF_YEAR) > eventQuery.getFromDate().get(java.util.Calendar.DAY_OF_YEAR)) {
      toDayOfYear = toDayOfYear + daysOfYear;
    }
    java.util.Calendar tempCalendar = Utils.getInstanceTempCalendar();

    List<CalendarEvent> originalRecurEvents = getOriginalRecurrenceEvents(username, eventQuery.getFromDate(), eventQuery.getToDate(), publicCalendarIds);
    boolean isVictory = false;
    if (originalRecurEvents != null && originalRecurEvents.size() > 0) {
      Iterator<CalendarEvent> recurEventsIter = originalRecurEvents.iterator();
      while (recurEventsIter.hasNext() && !isVictory) {
        CalendarEvent recurEvent = recurEventsIter.next();
        Map<String, CalendarEvent> tempMap = getOccurrenceEvents(recurEvent, eventQuery.getFromDate(), eventQuery.getToDate(), timezone);
        if (tempMap != null) {
          for (CalendarEvent event : tempMap.values().toArray(new CalendarEvent[0])) {
            if (isVictory)
              break;
            tempCalendar.setTime(event.getFromDateTime());
            int eventFromDayOfYear = tempCalendar.get(java.util.Calendar.DAY_OF_YEAR);
            if (tempCalendar.get(java.util.Calendar.YEAR) < eventQuery.getFromDate().get(java.util.Calendar.YEAR)) {
              eventFromDayOfYear = 1;
            }

            tempCalendar.setTime(event.getToDateTime());
            int eventToDayOfYear = tempCalendar.get(java.util.Calendar.DAY_OF_YEAR);
            if (tempCalendar.get(java.util.Calendar.YEAR) > eventQuery.getToDate().get(java.util.Calendar.YEAR)) {
              eventToDayOfYear = 366;
            }

            Integer begin = -1;
            Integer end = -1;
            if (fromDayOfYear >= eventFromDayOfYear) {
              begin = fromDayOfYear;
              if (toDayOfYear <= eventToDayOfYear) {
                end = toDayOfYear;
                isVictory = true;
              } else {
                end = eventToDayOfYear;
              }
            } else {
              begin = eventFromDayOfYear;
              if (toDayOfYear <= eventToDayOfYear) {
                end = toDayOfYear;
              } else {
                end = eventToDayOfYear;
              }
            }
            if (begin > 0 && end > 0)
              for (Integer i = begin; i <= end; i++)
                mapData.put(i, VALUE);
          }

        }
      }
    }
    return mapData;
  }

  /**
   * {@inheritDoc}
   */
  @SuppressWarnings("deprecation")
  public Map<Integer, String> updateMap(Map<Integer, String> data, NodeIterator it, java.util.Calendar fromDate, java.util.Calendar toDate, String[] filterCalIds) throws Exception {
    int fromDayOfYear = fromDate.get(java.util.Calendar.DAY_OF_YEAR);
    int daysOfyer = fromDate.getMaximum(java.util.Calendar.DAY_OF_YEAR);
    int toDayOfYear = toDate.get(java.util.Calendar.DAY_OF_YEAR);
    if (toDate.get(java.util.Calendar.DAY_OF_YEAR) > fromDate.get(java.util.Calendar.DAY_OF_YEAR)) {
      toDayOfYear = toDayOfYear + daysOfyer;
    }
    boolean isVictory = false;
    while (it.hasNext() && !isVictory) {
      Node eventNode = it.nextNode();
      if (filterCalIds == null || !Arrays.asList(filterCalIds).contains(eventNode.getProperty(Utils.EXO_CALENDAR_ID).getString())) {
        java.util.Calendar eventFormDate = eventNode.getProperty(Utils.EXO_FROM_DATE_TIME).getDate();
        java.util.Calendar eventToDate = eventNode.getProperty(Utils.EXO_TO_DATE_TIME).getDate();
        int eventFromDayOfYear = eventFormDate.get(java.util.Calendar.DAY_OF_YEAR);
        int eventToDayOfYear = eventToDate.get(java.util.Calendar.DAY_OF_YEAR);

        // CS-911
        if (eventFormDate.getTime().getYear() < fromDate.getTime().getYear()) {
          eventFromDayOfYear = 1;
        }
        if (eventToDate.getTime().getYear() > toDate.getTime().getYear()) {
          eventToDayOfYear = 366;
        }
        Integer begin = -1;
        Integer end = -1;
        if (fromDayOfYear >= eventFromDayOfYear) {
          begin = fromDayOfYear;
          if (toDayOfYear <= eventToDayOfYear) {
            end = toDayOfYear;
            isVictory = true;
          } else {
            end = eventToDayOfYear;
          }
        } else {
          begin = eventFromDayOfYear;
          if (toDayOfYear <= eventToDayOfYear) {
            end = toDayOfYear;
          } else {
            end = eventToDayOfYear;
          }
        }
        if (begin > 0 && end > 0)
          for (Integer i = begin; i <= end; i++)
            data.put(i, VALUE);
      }
    }
    return data;
  }

  private List<Value> calculateSharedCalendar(String user, Node calendarNode, Value[] values, List<Value> valueList, Node sharedCalendarHome) throws Exception {
    CalendarSetting calSetting = getCalendarSetting(getUserCalendarServiceHome(user));
    if (calSetting == null)
      calSetting = new CalendarSetting();
    Map<String, String> map = new HashMap<String, String>();
    for (String key : calSetting.getSharedCalendarsColors()) {
      map.put(key.split(":")[0], key.split(":")[1]);
    }
    if (map.get(calendarNode.getProperty(Utils.EXO_ID).getString()) == null)
      map.put(calendarNode.getProperty(Utils.EXO_ID).getString(), calendarNode.getProperty("exo:calendarColor").getString());
    List<String> calColors = new ArrayList<String>();
    for (String key : map.keySet()) {
      calColors.add(key + ":" + map.get(key));
    }
    calSetting.setSharedCalendarsColors(calColors.toArray(new String[calColors.size()]));
    saveCalendarSetting(calSetting, user);

    Node userNode = null;
    Session systemSession = sharedCalendarHome.getSession();
    try {
      userNode = sharedCalendarHome.getNode(user);
    } catch (PathNotFoundException e) {
      userNode = sharedCalendarHome.addNode(user, Utils.NT_UNSTRUCTURED);
      if (userNode.canAddMixin(Utils.MIX_REFERENCEABLE)) {
        userNode.addMixin(Utils.MIX_REFERENCEABLE);
      }
    }
    boolean isExist = false;
    isExist = false;
    for (int i = 0; i < values.length; i++) {
      Value value = values[i];
      String uuid = value.getString();
      Node refNode = systemSession.getNodeByUUID(uuid);
      if (refNode.getPath().equals(userNode.getPath())) {
        isExist = true;
        break;
      }
    }
    if (!isExist) {
      Value value2add = calendarNode.getSession().getValueFactory().createValue(userNode);
      valueList.add(value2add);
    }
    return valueList;
  }

  /**
   * {@inheritDoc}
   */
  public void shareCalendar(String username, String calendarId, List<String> receiverUsers) throws Exception {
    Node sharedCalendarHome = getSharedCalendarHome();
    Node calendarNode = getUserCalendarHome(username).getNode(calendarId);
    Value[] values = {};
    if (calendarNode.isNodeType(Utils.EXO_SHARED_MIXIN)) {
      values = calendarNode.getProperty(Utils.EXO_SHARED_ID).getValues();
    }
    List<Value> valueList = new ArrayList<Value>();
    for (int i = 0; i < values.length; i++) {
      Value value = values[i];
      valueList.add(value);
    }
    for (String user : receiverUsers) {
      valueList = calculateSharedCalendar(user, calendarNode, values, valueList, sharedCalendarHome);
    }
    if (valueList.size() > 0) {
      if (!calendarNode.isNodeType(Utils.EXO_SHARED_MIXIN)) {
        calendarNode.addMixin(Utils.EXO_SHARED_MIXIN);
      }
      calendarNode.setProperty(Utils.EXO_SHARED_ID, valueList.toArray(new Value[valueList.size()]));
      calendarNode.save();
      sharedCalendarHome.getSession().save();
      calendarNode.getSession().save();
    }
  }

  /**
   * {@inheritDoc}
   */
  public GroupCalendarData getSharedCalendars(String username, boolean isShowAll) throws Exception {
    Node shareCalendarHome = getSharedCalendarHome();
    if (shareCalendarHome.hasNode(username)) {
      Node sharedNode = shareCalendarHome.getNode(username);
      List<Calendar> calendars = new ArrayList<Calendar>();
      PropertyIterator iter = sharedNode.getReferences();
      String[] defaultFilterCalendars = null;
      if (username != null) {
        defaultFilterCalendars = getCalendarSetting(getUserCalendarServiceHome(username)).getFilterSharedCalendars();
      }
      while (iter.hasNext()) {
        try {
          Calendar cal = getCalendar(defaultFilterCalendars, null, iter.nextProperty().getParent(), isShowAll);
          if (cal != null) {
            calendars.add(cal);
          }
        } catch (Exception e) {
          if (log.isDebugEnabled()) {
            log.debug("Fail to create new calendar", e);
          }
        }
      }
      if (calendars.size() > 0) {
        return new GroupCalendarData("Shared", "Shared", calendars);
      }
    }
    return null;
  }

  private Node setCalendarProperties(Node calendarNode, Calendar calendar) throws Exception {
    calendarNode.setProperty(Utils.EXO_NAME, calendar.getName());
    calendarNode.setProperty(Utils.EXO_DESCRIPTION, calendar.getDescription());
    calendarNode.setProperty(Utils.EXO_CATEGORY_ID, calendar.getCategoryId());
    calendarNode.setProperty(Utils.EXO_VIEW_PERMISSIONS, calendar.getViewPermission());
    calendarNode.setProperty(Utils.EXO_EDIT_PERMISSIONS, calendar.getEditPermission());
    calendarNode.setProperty(Utils.EXO_GROUPS, calendar.getGroups());
    calendarNode.setProperty(Utils.EXO_LOCALE, calendar.getLocale());
    calendarNode.setProperty(Utils.EXO_TIMEZONE, calendar.getTimeZone());
    calendarNode.setProperty(Utils.EXO_CALENDAR_COLOR, calendar.getCalendarColor());
    calendarNode.setProperty(Utils.EXO_CALENDAR_OWNER, calendar.getCalendarOwner());
    try {
      calendarNode.setProperty(Utils.EXO_PUBLIC_URL, calendar.getPublicUrl());
      calendarNode.setProperty(Utils.EXO_PRIVATE_URL, calendar.getPrivateUrl());
    } catch (ConstraintViolationException e) {
      log.debug("\n\n Need to add property definition in exo:calendar node type !", e);
    }
    return calendarNode;
  }

  /**
   * {@inheritDoc}
   */
  public void saveSharedCalendar(String username, Calendar calendar) throws Exception {
    Node sharedCalendarHome = getSharedCalendarHome();
    if (sharedCalendarHome.hasNode(username)) {
      Node userNode = sharedCalendarHome.getNode(username);
      String uuid = userNode.getProperty("jcr:uuid").getString();
      PropertyIterator iter = userNode.getReferences();
      Node calendarNode;
      List<Value> newValues = new ArrayList<Value>();
      while (iter.hasNext()) {
        calendarNode = iter.nextProperty().getParent();
        if (calendarNode.getProperty(Utils.EXO_ID).getString().equals(calendar.getId())) {
          Value[] values = calendarNode.getProperty(Utils.EXO_SHARED_ID).getValues();
          for (Value value : values) {
            if (!value.getString().equals(uuid)) {
              newValues.add(value);
            }
          }
          calendarNode = setCalendarProperties(calendarNode, calendar);
          CalendarSetting usCalSetting = getCalendarSetting(getUserCalendarServiceHome(username));
          Map<String, String> map = new HashMap<String, String>();
          for (String key : usCalSetting.getSharedCalendarsColors()) {
            map.put(key.split(Utils.COLON)[0], key.split(Utils.COLON)[1]);
          }
          map.put(calendar.getId(), calendar.getCalendarColor());
          List<String> calColors = new ArrayList<String>();
          for (String key : map.keySet()) {
            calColors.add(key + Utils.COLON + map.get(key));
          }
          calColors.add(calendar.getId() + Utils.COLON + calendar.getCalendarColor());
          usCalSetting.setSharedCalendarsColors(calColors.toArray(new String[calColors.size()]));
          saveCalendarSetting(usCalSetting, username);
          calendarNode.save();
          break;
        }
      }
    }
  }

  /**
   * {@inheritDoc}
   */
  public List<CalendarEvent> getSharedEvents(String username, EventQuery eventQuery) throws Exception {
    List<CalendarEvent> events = new ArrayList<CalendarEvent>();
    Node shareHome = getSharedCalendarHome();
    if (shareHome.hasNode(username)) {
      QueryManager qm = shareHome.getSession().getWorkspace().getQueryManager();
      PropertyIterator iter = getSharedCalendarHome().getNode(username).getReferences();
      CalendarEvent calEvent;
      while (iter.hasNext()) {
        try {
          Node calendar = iter.nextProperty().getParent();
          eventQuery.setCalendarPath(calendar.getPath());
          Query query = qm.createQuery(eventQuery.getQueryStatement(), eventQuery.getQueryType());
          NodeIterator it = query.execute().getNodes();
          while (it.hasNext()) {
            calEvent = getEvent(it.nextNode());
            calEvent.setCalType("1");
            events.add(calEvent);
            if (eventQuery.getLimitedItems() == it.getPosition())
              break;
          }
        } catch (Exception e) {
          if (log.isDebugEnabled()) {
            log.debug("Fail to get events from calendar", e);
          }
        }
      }
    }
    return events;
  }

  /**
   * {@inheritDoc}
   */
  public List<CalendarEvent> getSharedEventByCalendars(String username, List<String> calendarIds) throws Exception {
    List<CalendarEvent> events = new ArrayList<CalendarEvent>();
    if (getSharedCalendarHome().hasNode(username)) {
      PropertyIterator iter = getSharedCalendarHome().getNode(username).getReferences();
      while (iter.hasNext()) {
        try {
          Node calendar = iter.nextProperty().getParent();
          if (calendarIds.contains(calendar.getProperty(Utils.EXO_ID).getString())) {
            NodeIterator it = calendar.getNodes();
            while (it.hasNext()) {
              events.add(getEvent(it.nextNode()));
            }
          }
        } catch (Exception e) {
          if (log.isDebugEnabled()) {
            log.debug("Fail to get events from calendar", e);
          }
        }
      }
    }
    return events;
  }

  public CalendarEvent getSharedEvent(String username, String calendarId, String eventId) throws Exception {
    Node sharedCalendarHome = getSharedCalendarHome();
    if (sharedCalendarHome.hasNode(username)) {
      PropertyIterator iter = getSharedCalendarHome().getNode(username).getReferences();
      while (iter.hasNext()) {
        try {
          Node calendar = iter.nextProperty().getParent();
          if (!calendarId.equals(calendar.getProperty(Utils.EXO_ID)))
            continue;
          if (calendar.hasNode(eventId)) {
            CalendarEvent event = getEvent(calendar.getNode(eventId));
            event.setCalType(String.valueOf(Calendar.TYPE_SHARED));
          }
        } catch (Exception e) {
          log.error("Exception when get shared event", e);
          return null;
        }
      }
    }
    return null;
  }

  /**
   * {@inheritDoc}
   */
  public void saveEventToSharedCalendar(String username, String calendarId, CalendarEvent event, boolean isNew) throws Exception {
    Node sharedCalendarHome = getSharedCalendarHome();
    if (sharedCalendarHome.hasNode(username)) {
      Node userNode = sharedCalendarHome.getNode(username);
      PropertyIterator iter = userNode.getReferences();
      Node calendar;
      while (iter.hasNext()) {
        calendar = iter.nextProperty().getParent();
        if (calendar.getProperty(Utils.EXO_ID).getString().equals(calendarId)) {

          // CS-2389
          if (!canEdit(calendar, username)) {
            log.debug("\n Do not have edit permission. \n");
            throw new AccessDeniedException();
          }
          Node reminderFolder = getReminderFolder(event.getFromDateTime());
          saveEvent(calendar, event, reminderFolder, isNew);
          calendar.save();
          break;
        }
      }
    }
  }

  /**
   * {@inheritDoc}
   */
  @SuppressWarnings("unchecked")
  public boolean canEdit(Node calNode, String username) throws Exception {
    OrganizationService oService = (OrganizationService) ExoContainerContext.getCurrentContainer().getComponentInstanceOfType(OrganizationService.class);
    StringBuffer sb = new StringBuffer(username);
    if (oService != null) {
      Collection<Group> groups = oService.getGroupHandler().findGroupsOfUser(username);
      for (Group g : groups) {
        sb.append(Utils.COMMA).append(g.getId()).append(Utils.SLASH_COLON).append(Utils.ANY);
        sb.append(Utils.COMMA).append(g.getId()).append(Utils.SLASH_COLON).append(username);
        Collection<Membership> memberShipsType = oService.getMembershipHandler().findMembershipsByUserAndGroup(username, g.getId());
        for (Membership mp : memberShipsType) {
          sb.append(Utils.COMMA).append(g.getId()).append(Utils.SLASH_COLON).append(Utils.ANY_OF + mp.getMembershipType());
        }
      }
    }

    Value[] editValues = calNode.getProperty(Utils.EXO_EDIT_PERMISSIONS).getValues();
    List<String> editPerms = new ArrayList<String>();
    for (Value v : editValues) {
      String value = v.getString();
      if (value.contains(Utils.SLASH)) {
        if (oService.getGroupHandler().findGroupById(value) != null) {
          for (User user : oService.getUserHandler().findUsersByGroup(value).getAll()) {
            editPerms.add(user.getUserName());
          }
        }
      } else {
        editPerms.add(value);
      }
    }
    if (editPerms != null) {
      String[] checkPerms = sb.toString().split(Utils.COMMA);
      for (String sp : editPerms) {
        for (String cp : checkPerms) {
          if (sp.equals(cp)) {
            return true;
          }
        }
      }
    }
    return false;
  }

  /**
   * {@inheritDoc}
   */
  public List<CalendarEvent> getEvents(String username, EventQuery eventQuery, String[] publicCalendarIds) throws Exception {
    List<CalendarEvent> events = new ArrayList<CalendarEvent>();
    List<String> filterList = new ArrayList<String>();
    CalendarSetting calSetting = getCalendarSetting(getUserCalendarServiceHome(username));
    filterList.addAll(Arrays.asList(calSetting.getFilterPrivateCalendars()));
    filterList.addAll(Arrays.asList(calSetting.getFilterPublicCalendars()));
    filterList.addAll(Arrays.asList(calSetting.getFilterSharedCalendars()));
    eventQuery.setFilterCalendarIds(filterList.toArray(new String[] {}));
    events.addAll(getUserEvents(username, eventQuery));
    try {
      events.addAll(getSharedEvents(username, eventQuery));
      if (publicCalendarIds != null && publicCalendarIds.length > 0) {
        // add to fix bug CS-2728
        eventQuery.setCalendarId(publicCalendarIds);
        events.addAll(getPublicEvents(eventQuery));
      }

      // add recurrence events

    } catch (Exception e) {
      if (log.isDebugEnabled()) {
        log.debug("Fail to get events", e);
      }
    } finally {
      // systemSession.close() ;
    }
    return events;
  }

  /**
   * Get all active 'original' recurrence event <br/>
   * The result list includes all 'original' recurrence event in all three types: private, public and shared
   * @param username
   * @return
   * @throws Exception
   */
  public List<CalendarEvent> getOriginalRecurrenceEvents(String username, java.util.Calendar from, java.util.Calendar to, String[] publicCalendarIds) throws Exception {
    List<CalendarEvent> recurEvents = new ArrayList<CalendarEvent>();
    CalendarSetting calSetting = getCalendarSetting(getUserCalendarServiceHome(username));

    // get from user private calendars, with filter settings
    Node calendarHome = getUserCalendarHome(username);
    String[] filterIds = calSetting.getFilterPrivateCalendars();
    recurEvents.addAll(getOriginalRecurrenceEvents(calendarHome, String.valueOf(Calendar.TYPE_PRIVATE), from, to, null, filterIds));

    // get from share calendars, with filter settings
    Node sharedCalendarHome = getSharedCalendarHome();
    filterIds = calSetting.getFilterSharedCalendars();
    List<String> filterIdList = null;
    if (filterIds != null && filterIds.length > 0)
      filterIdList = Arrays.asList(filterIds);
    if (sharedCalendarHome.hasNode(username)) {
      Node userNode = sharedCalendarHome.getNode(username);
      PropertyIterator iter = userNode.getReferences();
      Node calendar;
      while (iter.hasNext()) {
        calendar = iter.nextProperty().getParent();
        if (filterIdList != null && filterIdList.contains(calendar.getProperty(Utils.EXO_ID).getString()))
          continue;
        recurEvents.addAll(getOriginalRecurrenceEvents(calendar, String.valueOf(Calendar.TYPE_SHARED), from, to, null, null));
      }
    }

    // get from public calendars, with filter settings
    Node publicCalendarHome = getPublicCalendarHome();
    filterIds = calSetting.getFilterPublicCalendars();
    recurEvents.addAll(getOriginalRecurrenceEvents(publicCalendarHome, String.valueOf(Calendar.TYPE_PUBLIC), from, to, publicCalendarIds, filterIds));
    return recurEvents;
  }

  public List<CalendarEvent> getOriginalRecurrenceEvents(Node calendar, String calType, java.util.Calendar from, java.util.Calendar to, String[] calendarIds, String[] filterCalendarIds) throws Exception {
    if (calendar == null)
      return null;
    List<CalendarEvent> recurEvents = new ArrayList<CalendarEvent>();
    StringBuffer queryString = new StringBuffer("/jcr:root" + calendar.getPath() + "//element(*,exo:repeatCalendarEvent)[@exo:repeat!='")
                                    .append(CalendarEvent.RP_NOREPEAT).append("' and @exo:recurrenceId=''");
    if (from != null) {
      queryString.append(" and (not(@exo:repeatUntil) or @exo:repeatUntil >= xs:dateTime('" + ISO8601.format(from) + "'))")
        .append(" and (not(@exo:repeatFinishDate) or @exo:repeatFinishDate >= xs:dateTime('"+ ISO8601.format(from) + "'))");      
    } else {
      queryString.append(" and (not(@exo:repeatUntil))")
      .append(" and (not(@exo:repeatFinishDate))");
    }
    if (calendarIds != null && calendarIds.length > 0) {
      queryString.append(" and (");
      for (int i = 0; i < calendarIds.length; i++) {
        if (i == 0)
          queryString.append("@exo:calendarId = '" + calendarIds[i] + "'");
        else
          queryString.append(" or @exo:calendarId = '" + calendarIds[i] + "'");
      }
      queryString.append(")");
    }

    if (filterCalendarIds != null && filterCalendarIds.length > 0) {
      queryString.append(" and (");
      for (int i = 0; i < filterCalendarIds.length; i++) {
        if (i == 0)
          queryString.append("@exo:calendarId != '" + filterCalendarIds[i] + "'");
        else
          queryString.append(" and @exo:calendarId != '" + filterCalendarIds[i] + "'");
      }
      queryString.append(")");
    }
    queryString.append("]");

    QueryManager qm = calendar.getSession().getWorkspace().getQueryManager();
    Query query = qm.createQuery(queryString.toString(), Query.XPATH);
    QueryResult result = query.execute();
    NodeIterator it = result.getNodes();
    while (it.hasNext()) {
      Node eventNode = it.nextNode();
      CalendarEvent event = getEvent(eventNode);
      event.setCalType(calType);
      recurEvents.add(event);
    }
    return recurEvents;
  }

  /**
   * Get all exception occurrence from original recurrence event
   * @param username
   * @param recurEvent
   * @return
   * @throws Exception
   */
  public List<CalendarEvent> getExceptionEvents(String username, CalendarEvent recurEvent) throws Exception {

    if (recurEvent == null || recurEvent.getRepeatType().equals(CalendarEvent.RP_NOREPEAT))
      return null;
    try {
      Node calendarHome = null;
      int calType = Integer.parseInt(recurEvent.getCalType());
      Node eventNode = null;
      if (calType == Calendar.TYPE_PRIVATE) {
        calendarHome = getUserCalendarHome(username);
        eventNode = calendarHome.getNode(recurEvent.getCalendarId()).getNode(recurEvent.getId());
      }
      if (calType == Calendar.TYPE_SHARED) {
        calendarHome = getSharedCalendarHome();
        if (calendarHome.hasNode(username)) {
          PropertyIterator iter = calendarHome.getNode(username).getReferences();
          while (iter.hasNext()) {
            Node calendar = iter.nextProperty().getParent();
            if (!recurEvent.getCalendarId().equals(calendar.getProperty(Utils.EXO_ID)))
              continue;
            if (calendar.hasNode(recurEvent.getId())) {
              eventNode = calendar.getNode(recurEvent.getId());
            }
          }
        }
      }
      if (calType == Calendar.TYPE_PUBLIC) {
        calendarHome = getPublicCalendarHome();
        eventNode = calendarHome.getNode(recurEvent.getCalendarId()).getNode(recurEvent.getId());
      }

      if (eventNode == null || !eventNode.isNodeType(Utils.MIX_REFERENCEABLE))
        return null;
      PropertyIterator propIter = eventNode.getReferences();
      if (propIter == null)
        return null;
      List<CalendarEvent> exceptions = new ArrayList<CalendarEvent>();
      while (propIter.hasNext()) {
        Property prop = propIter.nextProperty();
        Node exceptionNode = prop.getParent();
        CalendarEvent exception = getEvent(exceptionNode);
        exception.setCalType(String.valueOf(calType));
        exceptions.add(exception);
      }
      return exceptions;
    } catch (Exception e) {
      log.error("Exception occurred when finding all exception occurrences", e);
      return null;
    }
  }

  /**
   * Query all occurrences of recurrence event between from and to date. <br/>
   * The result contains only 'virtual' occurrence events, no exception occurrences
   * @param recurEvent the original recurrence event
   * @param from
   * @param to
   * @param timezone
   * @return
   * @throws Exception
   */
  public Map<String, CalendarEvent> getOccurrenceEvents(CalendarEvent recurEvent, java.util.Calendar from, java.util.Calendar to, String timezone) throws Exception {
    if (Utils.isEmpty(recurEvent.getRepeatType()))
      return null;
    if (from.after(to) || !recurEvent.getFromDateTime().before(to.getTime())) {
      return null;
    }

    // check if this recurEvent is expired
    if (recurEvent.getRepeatUntilDate() != null && recurEvent.getRepeatUntilDate().before(from.getTime())) {
      return null;
    }

    Map<String, CalendarEvent> occurrences = new HashMap<String, CalendarEvent>();

    int diffMinutes = (int) ((recurEvent.getToDateTime().getTime() - recurEvent.getFromDateTime().getTime()) / (60 * 1000));

    List<String> excludeIds = null;
    if (recurEvent.getExcludeId() != null && recurEvent.getExcludeId().length > 0) {
      excludeIds = new ArrayList<String>(Arrays.asList(recurEvent.getExcludeId()));
    }

    Recur recur = getICalendarRecur(recurEvent);
    if (recur == null)
      return null;

    // need to pass timezone as function param?
    TimeZoneRegistry registry = TimeZoneRegistryFactory.getInstance().createRegistry();
    net.fortuna.ical4j.model.TimeZone tz = registry.getTimeZone(timezone);
    DateTime ical4jEventFrom = new DateTime(recurEvent.getFromDateTime());
    ical4jEventFrom.setTimeZone(tz);

    java.util.Calendar occurenceFrom = java.util.Calendar.getInstance();
    occurenceFrom.setTime(from.getTime());
    // because occurrence event can begin before the 'from' but end after 'from', so it still intersects with [from, to] window
    // thus, we decrease the 'from' value (by duration of event) to get such occurrences
    occurenceFrom.add(java.util.Calendar.MINUTE, -(diffMinutes-1));
    DateTime ical4jFrom = new DateTime(occurenceFrom.getTime());
    DateTime ical4jTo = new DateTime(to.getTime());
    Period period = new Period(ical4jFrom, ical4jTo);
    DateList list = recur.getDates(ical4jEventFrom, period, net.fortuna.ical4j.model.parameter.Value.DATE_TIME);
    SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'");
    format.setTimeZone(TimeZone.getTimeZone("GMT"));
    for (Object dt : list) {
      DateTime ical4jStart = (DateTime) dt;
      ical4jStart.setUtc(true);

      // make occurrence
      CalendarEvent occurrence = new CalendarEvent(recurEvent);
      java.util.Calendar temp = Utils.getInstanceTempCalendar();
      temp.setTimeInMillis(ical4jStart.getTime());
      occurrence.setFromDateTime(temp.getTime());
      temp.add(java.util.Calendar.MINUTE, diffMinutes);
      occurrence.setToDateTime(temp.getTime());

      String recurId = format.format(occurrence.getFromDateTime());
      // if this occurrence was listed in the exclude list, skip
      if (excludeIds != null && excludeIds.contains(recurId)) continue;
      occurrence.setRecurrenceId(recurId);
      occurrences.put(recurId, occurrence);
    }
    return occurrences;

  }

  /**
   * Calculate the end-date of recurrence event
   * @param originalEvent
   * @return
   * @throws Exception
   */
  public Date calculateRecurrenceFinishDate(CalendarEvent originalEvent) throws Exception {
    try {
      if (originalEvent.getRepeatUntilDate() != null) {
        return originalEvent.getRepeatUntilDate();
      }
      
      // in case of repeat forever event
      if (originalEvent.getRepeatCount() <= 0) {
        return null;
      }

      DateTime ical4jEventFrom = new DateTime(originalEvent.getFromDateTime());
      VEvent vevent = new VEvent(ical4jEventFrom, Utils.EMPTY_STR);

      Recur recur = getICalendarRecur(originalEvent);

      vevent.getProperties().add(new RRule(recur));
      java.util.Calendar calendar = new GregorianCalendar(2011, 7, 1);
      calendar.setTimeZone(TimeZone.getTimeZone("GMT"));
      calendar.set(java.util.Calendar.YEAR, calendar.getMinimum(java.util.Calendar.YEAR));
      DateTime ical4jFrom = new DateTime(calendar.getTime());
      calendar.set(java.util.Calendar.YEAR, calendar.getMaximum(java.util.Calendar.YEAR));
      DateTime ical4jTo = new DateTime(calendar.getTime());
      Period period = new Period(ical4jFrom, ical4jTo);
      PeriodList list = vevent.calculateRecurrenceSet(period);
      if (list == null || list.size() == 0)
        return null;
      Period last = (Period) list.last();
      calendar.setTimeInMillis(last.getStart().getTime());
      calendar.add(java.util.Calendar.DATE, 1);
      calendar.set(java.util.Calendar.HOUR_OF_DAY, 0);
      calendar.set(java.util.Calendar.MINUTE, 0);
      return calendar.getTime();
    } catch (Exception e) {
      if (log.isDebugEnabled())
        log.debug("Exception occurred when calculating finish date of recurrence event", e);
      return null;
    }
  }

  public Recur getICalendarRecur(CalendarEvent recurEvent) throws Exception {
    String repeatType = recurEvent.getRepeatType();
    // get the repeat count property of recurrence event
    int count = (int) recurEvent.getRepeatCount();

    java.util.Calendar until = null;
    if (recurEvent.getRepeatUntilDate() != null) {
      until = Utils.getInstanceTempCalendar();
      until.setTime(recurEvent.getRepeatUntilDate());
    }

    int interval = (int) recurEvent.getRepeatInterval();
    if (interval <= 1)
      interval = 1;

    Recur recur = null;

    // daily recurrence
    if (repeatType.equals(CalendarEvent.RP_DAILY)) {
      if (until != null) {
        recur = new Recur(Recur.DAILY, new net.fortuna.ical4j.model.Date(until.getTime()));
      } else {
        if (count > 0) {
          recur = new Recur(Recur.DAILY, count);
        } else
          recur = new Recur("FREQ=DAILY");
      }
      recur.setInterval(interval);
      return recur;
    }

    // weekly recurrence
    if (repeatType.equals(CalendarEvent.RP_WEEKLY)) {
      if (until != null) {
        recur = new Recur(Recur.WEEKLY, new net.fortuna.ical4j.model.Date(until.getTime()));
      } else {
        if (count > 0) {
          recur = new Recur(Recur.WEEKLY, count);
        } else
          recur = new Recur("FREQ=WEEKLY");
      }
      recur.setInterval(interval);

      // byday property
      String[] repeatByDay = recurEvent.getRepeatByDay();
      if (repeatByDay == null || repeatByDay.length == 0)
        return null;
      WeekDayList weekDayList = new WeekDayList();
      for (String s : repeatByDay) {
        weekDayList.add(new WeekDay(s));
      }
      recur.getDayList().addAll(weekDayList);
      return recur;
    }

    // monthly recurrence
    if (repeatType.equals(CalendarEvent.RP_MONTHLY)) {
      if (until != null) {
        recur = new Recur(Recur.MONTHLY, new net.fortuna.ical4j.model.Date(until.getTime()));
      } else {
        if (count > 0) {
          recur = new Recur(Recur.MONTHLY, count);
        } else
          recur = new Recur("FREQ=MONTHLY");
      }
      recur.setInterval(interval);

      long[] repeatByMonthDay = recurEvent.getRepeatByMonthDay();
      // case 1: byMonthDay: day 1, 15, 26 of month
      if (repeatByMonthDay != null && repeatByMonthDay.length > 0) {
        NumberList numberList = new NumberList();
        for (long monthDay : repeatByMonthDay) {
          numberList.add(new Integer((int) monthDay));
        }
        recur.getMonthDayList().addAll(numberList);
      } else {
        // case 2: byDay: 1SU: first Sunday of month, -1TU: last Tuesday of month
        String[] repeatByDay = recurEvent.getRepeatByDay();
        if (repeatByDay != null && repeatByDay.length > 0) {
          WeekDayList weekDayList = new WeekDayList();
          for (String s : repeatByDay) {
            weekDayList.add(new WeekDay(s));
          }
          recur.getDayList().addAll(weekDayList);
        }
      }
      return recur;
    }

    // yearly recurrence
    if (repeatType.equals(CalendarEvent.RP_YEARLY)) {
      if (until != null) {
        recur = new Recur(Recur.YEARLY, new net.fortuna.ical4j.model.Date(until.getTime()));
      } else {
        if (count > 0) {
          recur = new Recur(Recur.YEARLY, count);
        } else
          recur = new Recur("FREQ=YEARLY");
      }
      recur.setInterval(interval);
      return recur;
    }

    return recur;
  }

  /**
   * {@inheritDoc}
   */
  public Map<String, String> checkFreeBusy(EventQuery eventQuery) throws Exception {
    Date fromDate = eventQuery.getFromDate().getTime();
    Date toDate = eventQuery.getToDate().getTime();   
    Map<String, String> participantMap = new HashMap<String, String>();
    participantMap.putAll(checkFreeBusy(eventQuery, fromDate));
    if (!Utils.isSameDate(fromDate, toDate)) {
      Map<String,String> remainingInfo = checkFreeBusy(eventQuery, toDate);
      if (remainingInfo.size() > 0) {
        for (String par : remainingInfo.keySet()) {
          String newValue = remainingInfo.get(par);
          if (participantMap.containsKey(par)) newValue += Utils.COMMA + participantMap.get(par);
          participantMap.put(par, newValue);
        }
      }
    }
    return participantMap;
  }
  
  public Map<String,String> checkFreeBusy(EventQuery eventQuery, Date date) throws Exception {
    Node eventFolder = getEventFolder(date);
    Map<String, String> participantMap = new HashMap<String, String>();
    eventQuery.setCalendarPath(eventFolder.getPath());
    eventQuery.setOrderBy(new String[] { Utils.EXO_FROM_DATE_TIME });
    QueryManager qm = getSession(createSystemProvider()).getWorkspace().getQueryManager();
    String[] pars = eventQuery.getParticipants();
    Query query;
    Node event;
    String from;
    String to;
    for (String par : pars) {
      eventQuery.setParticipants(new String[] { par });
      query = qm.createQuery(eventQuery.getQueryStatement(), Query.XPATH);
      QueryResult result = query.execute();
      NodeIterator it = result.getNodes();
      StringBuilder timeValues = new StringBuilder();
      while (it.hasNext()) {
        event = it.nextNode();
        if (event.hasProperty(Utils.EXO_EVENT_STATE) && !CalendarEvent.ST_AVAILABLE.equals(event.getProperty(Utils.EXO_EVENT_STATE).getValue().getString())) {
          java.util.Calendar fromCal = event.getProperty(Utils.EXO_FROM_DATE_TIME).getDate();
          java.util.Calendar toCal = event.getProperty(Utils.EXO_TO_DATE_TIME).getDate();
          if (fromCal.getTimeInMillis() < eventQuery.getFromDate().getTimeInMillis())
            from = String.valueOf(eventQuery.getFromDate().getTimeInMillis());
          else
            from = String.valueOf(fromCal.getTimeInMillis());
          if (toCal.getTimeInMillis() > eventQuery.getToDate().getTimeInMillis()) {
            GregorianCalendar cal = new GregorianCalendar();
            cal.setTimeInMillis(eventQuery.getToDate().getTimeInMillis() - 1000);
            to = String.valueOf(cal.getTimeInMillis());
          } else
            to = String.valueOf(toCal.getTimeInMillis());

          if (timeValues != null && timeValues.length() > 0)
            timeValues.append(",");
          timeValues.append(from).append(",").append(to);
          participantMap.put(par, timeValues.toString());
        }
      }
    }
    return participantMap;
  }
  
  /**
   * {@inheritDoc}
   */
  public void removeSharedEvent(String username, String calendarId, String eventId) throws Exception {
    Node sharedCalendarHome = getSharedCalendarHome();
    if (sharedCalendarHome.hasNode(username)) {
      Node userNode = sharedCalendarHome.getNode(username);
      PropertyIterator iter = userNode.getReferences();
      Node calendar;
      while (iter.hasNext()) {
        calendar = iter.nextProperty().getParent();
        if (calendar.getProperty(Utils.EXO_ID).getString().equals(calendarId)) {
          if (calendar.hasNode(eventId)) {
            Node event = calendar.getNode(eventId);
            Node eventFolder = getEventFolder(event.getProperty(Utils.EXO_FROM_DATE_TIME).getDate().getTime());
            syncRemoveEvent(eventFolder, eventId);
            removeReminder(event);
            event.remove();
          }
          calendar.save();
          calendar.refresh(true);
          break;
        }
      }
    }
  }

  /**
   * {@inheritDoc}
   */
  public void moveEvent(String formCalendar, String toCalendar, String fromType, String toType, List<CalendarEvent> calEvents, String username) throws Exception {
    try {
      switch (Integer.parseInt(fromType)) {
      case Calendar.TYPE_PRIVATE:
        if (getUserCalendarHome(username).hasNode(formCalendar)) {
          switch (Integer.parseInt(toType)) {
          case Calendar.TYPE_PRIVATE:
            // move events in side public calendars
            if (getUserCalendarHome(username).hasNode(toCalendar)) {
              for (CalendarEvent calEvent : calEvents) {
                if (!formCalendar.equals(toCalendar)) {
                  removeUserEvent(username, formCalendar, calEvent.getId());
                  calEvent.setCalendarId(toCalendar);
                  saveUserEvent(username, toCalendar, calEvent, getUserCalendarHome(username).getNode(toCalendar).hasNode(calEvent.getId()));
                } else {
                  saveUserEvent(username, toCalendar, calEvent, false);
                }
              }
            }
            break;
          case Calendar.TYPE_SHARED:
            // move events form public to shared calendar
            if (getSharedCalendarHome().hasNode(username)) {
              for (CalendarEvent calEvent : calEvents) {
                removeUserEvent(username, formCalendar, calEvent.getId());
                calEvent.setCalendarId(toCalendar);
                saveEventToSharedCalendar(username, toCalendar, calEvent, getSharedCalendarHome().getNode(username).hasNode(calEvent.getId()));
              }
            }
            break;
          case Calendar.TYPE_PUBLIC:
            // move events form public to public calendar
            if (getPublicCalendarHome().hasNode(toCalendar)) {
              for (CalendarEvent calEvent : calEvents) {
                removeUserEvent(username, formCalendar, calEvent.getId());
                calEvent.setCalendarId(toCalendar);
                savePublicEvent(toCalendar, calEvent, getPublicCalendarHome().getNode(toCalendar).hasNode(calEvent.getId()));
              }
            }
            break;
          default:
            break;
          }
        }
        break;
      case Calendar.TYPE_SHARED:
        if (getSharedCalendarHome().hasNode(username)) {
          switch (Integer.parseInt(toType)) {
          case Calendar.TYPE_PRIVATE:
            // move events form share to public calendar
            if (getUserCalendarHome(username).hasNode(toCalendar)) {
              for (CalendarEvent calEvent : calEvents) {
                removeSharedEvent(username, formCalendar, calEvent.getId());
                calEvent.setCalendarId(toCalendar);
                saveUserEvent(username, toCalendar, calEvent, getUserCalendarHome(username).getNode(toCalendar).hasNode(calEvent.getId()));
              }
            }
            break;
          case Calendar.TYPE_SHARED:
            // move events in side shared calendars
            if (getSharedCalendarHome().hasNode(username)) {
              for (CalendarEvent calEvent : calEvents) {
                if (!formCalendar.equals(toCalendar)) {
                  removeSharedEvent(username, formCalendar, calEvent.getId());
                  calEvent.setCalendarId(toCalendar);
                  saveEventToSharedCalendar(username, toCalendar, calEvent, getSharedCalendarHome().getNode(username).hasNode(calEvent.getId()));
                } else {
                  saveEventToSharedCalendar(username, toCalendar, calEvent, false);
                }
              }
            }
            break;
          case Calendar.TYPE_PUBLIC:
            // move events form share to public calendar
            if (getPublicCalendarHome().hasNode(toCalendar)) {
              for (CalendarEvent calEvent : calEvents) {
                removeSharedEvent(username, formCalendar, calEvent.getId());
                calEvent.setCalendarId(toCalendar);
                savePublicEvent(toCalendar, calEvent, getPublicCalendarHome().getNode(toCalendar).hasNode(calEvent.getId()));
              }
            }
            break;
          default:
            break;
          }
        }
        break;
      case Calendar.TYPE_PUBLIC:
        if (getPublicCalendarHome().hasNode(formCalendar)) {
          switch (Integer.parseInt(toType)) {
          case Calendar.TYPE_PRIVATE:
            // move events from public to public calendar
            if (getUserCalendarHome(username).hasNode(toCalendar)) {
              for (CalendarEvent calEvent : calEvents) {
                removePublicEvent(formCalendar, calEvent.getId());
                calEvent.setCalendarId(toCalendar);
                saveUserEvent(username, toCalendar, calEvent, getUserCalendarHome(username).getNode(toCalendar).hasNode(calEvent.getId()));
              }
            }
            break;
          case Calendar.TYPE_SHARED:
            // move events from public to shared calendar
            if (getSharedCalendarHome().hasNode(username)) {
              for (CalendarEvent calEvent : calEvents) {
                removePublicEvent(formCalendar, calEvent.getId());
                calEvent.setCalendarId(toCalendar);
                saveEventToSharedCalendar(username, toCalendar, calEvent, true);
              }
            }
            break;
          case Calendar.TYPE_PUBLIC:
            // move events in side public calendars
            if (getPublicCalendarHome().hasNode(toCalendar)) {
              for (CalendarEvent calEvent : calEvents) {
                if (!formCalendar.equals(toCalendar)) {
                  removePublicEvent(formCalendar, calEvent.getId());
                  calEvent.setCalendarId(toCalendar);
                  savePublicEvent(toCalendar, calEvent, getPublicCalendarHome().getNode(toCalendar).hasNode(calEvent.getId()));
                } else {
                  savePublicEvent(toCalendar, calEvent, false);
                }
              }
            }
            break;
          default:
            break;
          }
        }
        break;
      default:
        break;
      }
    } catch (Exception e) {
      if (log.isDebugEnabled()) {
        log.debug("Fail to move event", e);
      }
    } finally {
      // systemSession.close() ;
    }
  }

  /**
   * This function is used to update a occurrence event. <br/>
   * A occurrence event after update will be an exception occurrence from recurrence series and will 
   * be saved as an new event node refers to original node
   * @param fromCalendar
   * @param toCalendar
   * @param fromType
   * @param toType
   * @param events
   * @param username
   * @throws Exception
   */
  public void updateOccurrenceEvent(String fromCalendar, String toCalendar, String fromCalendarType, String toCalendarType, List<CalendarEvent> events, String username) throws Exception {
    try {
      int fromType = Integer.parseInt(fromCalendarType);
      int toType = Integer.parseInt(toCalendarType);
      for (CalendarEvent event : events) {
        if (event.getIsExceptionOccurrence() == null || !event.getIsExceptionOccurrence()) {
          // get the original recurrence event to update excludeId property
          CalendarEvent originalEvent = null;
          if (fromType == Calendar.TYPE_PRIVATE)
            originalEvent = getUserEvent(username, fromCalendar, event.getId());
          else if (fromType == Calendar.TYPE_SHARED)
            originalEvent = getSharedEvent(username, fromCalendar, event.getId());
          else if (fromType == Calendar.TYPE_PUBLIC)
            originalEvent = getGroupEvent(fromCalendar, event.getId());

          if (originalEvent != null) {
            List<String> excludeId;
            if (originalEvent.getExcludeId() == null) {
              excludeId = new ArrayList<String>();
            } else {
              excludeId = new ArrayList<String>(Arrays.asList(originalEvent.getExcludeId()));
            }
            excludeId.add(event.getRecurrenceId());
            originalEvent.setExcludeId(excludeId.toArray(new String[0]));

            if (fromType == Calendar.TYPE_PRIVATE)
              saveUserEvent(username, fromCalendar, originalEvent, false);
            else if (fromType == Calendar.TYPE_SHARED)
              saveEventToSharedCalendar(username, fromCalendar, originalEvent, false);
            else if (fromType == Calendar.TYPE_PUBLIC)
              savePublicEvent(fromCalendar, originalEvent, false);
          }
        }

        if (fromCalendar.equals(toCalendar)) {
          if (Utils.isExceptionOccurrence(event)) {
            saveOccurrenceEvent(username, toCalendar, event, false);
          } else {
            saveOccurrenceEvent(username, toCalendar, event, true);
          }
        } else {
          if (Utils.isExceptionOccurrence(event)) {
            if (fromType == Calendar.TYPE_PRIVATE)
              removeUserEvent(username, fromCalendar, event.getId());
            else if (fromType == Calendar.TYPE_SHARED)
              removeSharedEvent(username, fromCalendar, event.getId());
            else if (fromType == Calendar.TYPE_PUBLIC)
              removePublicEvent(fromCalendar, event.getId());
          }
          event.setCalendarId(toCalendar);
          event.setRepeatType(CalendarEvent.RP_NOREPEAT);
          event.setIsExceptionOccurrence(false);

          if (toType == Calendar.TYPE_PRIVATE)
            saveUserEvent(username, toCalendar, event, true);
          else if (toType == Calendar.TYPE_SHARED)
            saveEventToSharedCalendar(username, toCalendar, event, true);
          else if (toType == Calendar.TYPE_PUBLIC)
            savePublicEvent(toCalendar, event, true);
        }
      }
    } catch (Exception e) {
      log.error("Error occurred when updating occurrence event", e);
    }
  }

  /**
   * Update recurrence series
   * @param fromCalendar
   * @param toCalendar
   * @param fromType
   * @param toType
   * @param occurence
   * @param username
   * @throws Exception
   */
  public void updateRecurrenceSeries(String fromCalendar, String toCalendar, String fromType, String toType, CalendarEvent occurrence, String username) throws Exception {
    try {
      String eventId = occurrence.getId();
      int calType = Integer.parseInt(fromType);
      int toCalType = Integer.parseInt(toType);
      CalendarEvent originalEvent = null;

      // get the original event
      if (calType == Calendar.TYPE_PRIVATE)
        originalEvent = getUserEvent(username, fromCalendar, eventId);
      else if (calType == Calendar.TYPE_PUBLIC)
        originalEvent = getGroupEvent(fromCalendar, eventId);
      else if (calType == Calendar.TYPE_SHARED)
        originalEvent = getSharedEvent(username, fromCalendar, eventId);

      // do we need to get the list of exception events to update?
      List<CalendarEvent> exceptions = getExceptionEvents(username, originalEvent);
      if (exceptions != null && exceptions.size() > 0) {
        for (CalendarEvent exception : exceptions) {
          boolean change = false;
          if (exception.getSummary().equals(originalEvent.getSummary()) && !originalEvent.getSummary().equals(occurrence.getSummary())) {
            exception.setSummary(occurrence.getSummary());
            change = true;
          }
          if ((occurrence.getDescription() != null && !occurrence.getDescription().equals(originalEvent.getDescription())) || (occurrence.getDescription() == null && originalEvent.getDescription() != null)) {
            exception.setDescription(occurrence.getDescription());
            change = true;
          }
          if (!fromCalendar.equals(toCalendar)) {
            if (calType == Calendar.TYPE_PRIVATE)
              removeUserEvent(username, fromCalendar, exception.getId());
            else if (calType == Calendar.TYPE_PUBLIC)
              removePublicEvent(fromCalendar, exception.getId());
            else if (calType == Calendar.TYPE_SHARED)
              removeSharedEvent(username, fromCalendar, exception.getId());

            exception.setCalendarId(toCalendar);
            // when moving exception occurrence to another calendar, the exception occurrence will become 'normal' event, it dont' have reference to recurrence series
            exception.setIsExceptionOccurrence(false);
            if (toCalType == Calendar.TYPE_PRIVATE)
              saveUserEvent(username, toCalendar, exception, true);
            else if (toCalType == Calendar.TYPE_PUBLIC)
              savePublicEvent(toCalendar, exception, true);
            else if (toCalType == Calendar.TYPE_SHARED)
              saveEventToSharedCalendar(username, toCalendar, exception, true);
          } else {
            if (!change)
              continue;
            if (calType == Calendar.TYPE_PRIVATE)
              saveUserEvent(username, exception.getCalendarId(), exception, false);
            else if (calType == Calendar.TYPE_PUBLIC)
              savePublicEvent(exception.getCalendarId(), exception, false);
            else if (calType == Calendar.TYPE_SHARED)
              saveEventToSharedCalendar(username, exception.getCalendarId(), exception, false);
          }
        }
      }

      // update original event from occurrence
      java.util.Calendar fromDate = Utils.getInstanceTempCalendar();
      fromDate.setTime(originalEvent.getFromDateTime());
      java.util.Calendar newFromDate = Utils.getInstanceTempCalendar();
      newFromDate.setTime(occurrence.getFromDateTime());
      fromDate.set(java.util.Calendar.HOUR_OF_DAY, newFromDate.get(java.util.Calendar.HOUR_OF_DAY));
      fromDate.set(java.util.Calendar.MINUTE, newFromDate.get(java.util.Calendar.MINUTE));
      originalEvent.setFromDateTime(fromDate.getTime());

      // calculate time amount
      java.util.Calendar newToDate = Utils.getInstanceTempCalendar();
      newToDate.setTime(occurrence.getToDateTime());
      int diffMinutes = (int) (newToDate.getTimeInMillis() - newFromDate.getTimeInMillis()) / (60 * 1000);

      newToDate.setTime(fromDate.getTime());
      newToDate.add(java.util.Calendar.MINUTE, diffMinutes);
      originalEvent.setToDateTime(newToDate.getTime());

      originalEvent.setSummary(occurrence.getSummary());
      originalEvent.setDescription(occurrence.getDescription());
      originalEvent.setEventCategoryId(occurrence.getEventCategoryId());
      originalEvent.setEventCategoryName(occurrence.getEventCategoryName());
      originalEvent.setMessage(occurrence.getMessage());
      originalEvent.setLocation(occurrence.getLocation());
      List<Attachment> attachments = occurrence.getAttachment();
      originalEvent.setAttachment(attachments);
      originalEvent.setInvitation(occurrence.getInvitation());
      originalEvent.setParticipant(occurrence.getParticipant());
      originalEvent.setParticipantStatus(occurrence.getParticipantStatus());
      originalEvent.setReminders(occurrence.getReminders());
      originalEvent.setSendOption(occurrence.getSendOption());
      originalEvent.setStatus(occurrence.getStatus());
      // originalEvent.setLastUpdatedTime(Utils.getInstanceTempCalendar().getTime());
      originalEvent.setPriority(occurrence.getPriority());
      originalEvent.setRepeatType(occurrence.getRepeatType());
      originalEvent.setRepeatUntilDate(occurrence.getRepeatUntilDate());
      originalEvent.setRepeatCount(occurrence.getRepeatCount());
      originalEvent.setRepeatInterval(occurrence.getRepeatInterval());
      originalEvent.setRepeatByDay(occurrence.getRepeatByDay());
      originalEvent.setRepeatByMonthDay(occurrence.getRepeatByMonthDay());

      // if move occurrence to another calendar, same date
      if (!fromCalendar.equals(toCalendar)) {
        // remove original event from old calendar
        if (calType == Calendar.TYPE_PRIVATE)
          removeUserEvent(username, fromCalendar, eventId);
        else if (calType == Calendar.TYPE_PUBLIC)
          removePublicEvent(fromCalendar, eventId);
        else if (calType == Calendar.TYPE_SHARED)
          removeSharedEvent(username, fromCalendar, eventId);

        // save new original event to new calendar
        CalendarEvent newEvent = new CalendarEvent(originalEvent);
        newEvent.setCalendarId(toCalendar);
        newEvent.setExcludeId(originalEvent.getExcludeId());

        if (toCalType == Calendar.TYPE_PRIVATE)
          saveUserEvent(username, toCalendar, newEvent, true);
        else if (toCalType == Calendar.TYPE_SHARED)
          saveEventToSharedCalendar(username, toCalendar, newEvent, true);
        else if (toCalType == Calendar.TYPE_PUBLIC)
          savePublicEvent(toCalendar, newEvent, true);
      } else {
        // save original event
        if (calType == Calendar.TYPE_PRIVATE)
          saveUserEvent(username, toCalendar, originalEvent, false);
        else if (calType == Calendar.TYPE_SHARED)
          saveEventToSharedCalendar(username, toCalendar, originalEvent, false);
        else if (calType == Calendar.TYPE_PUBLIC)
          savePublicEvent(toCalendar, originalEvent, false);
      }
    } catch (Exception e) {
      log.error("Error when update recurrence series", e);
    }
  }

  /**
   * {@inheritDoc}
   */
  public void confirmInvitation(String fromUserId, String toUserId, int calType, String calendarId, String eventId, int answer) throws Exception {
    try {
      Map<String, String> pars = new HashMap<String, String>();
      CalendarEvent event = getInvitationEvent(calType, calendarId, eventId, fromUserId);
      if (event != null) {
        if (event.getParticipant() != null) {
          for (String id : event.getParticipant()) {
            pars.put(id, id);
          }
        }
        for (String s : toUserId.split(",")) {
          if (Utils.DENY == answer) {

            pars.remove(s);
          }
          if (Utils.ACCEPT == answer || Utils.NOTSURE == answer) {
            pars.put(s, s);
          }
        }
        event.setParticipant(pars.values().toArray(new String[pars.values().size()]));
        if (Calendar.TYPE_PRIVATE == calType) {
          saveUserEvent(fromUserId, calendarId, event, false);
        } else if (Calendar.TYPE_SHARED == calType) {
          saveEventToSharedCalendar(fromUserId, calendarId, event, false);
        } else if (Calendar.TYPE_PUBLIC == calType) {
          savePublicEvent(calendarId, event, false);
        }
      }
    } catch (Exception e) {
    } finally {
      // session.close() ;
    }
  }

  private CalendarEvent getInvitationEvent(int calType, String calendarId, String eventId, String fromUserId) throws Exception {
    CalendarEvent event = null;
    if (Calendar.TYPE_PRIVATE == calType) {
      event = getUserEvent(fromUserId, calendarId, eventId);
    } else if (Calendar.TYPE_SHARED == calType) {
      List<String> calendarIds = new ArrayList<String>();
      calendarIds.add(calendarId);
      for (CalendarEvent calEvent : getSharedEventByCalendars(fromUserId, calendarIds)) {
        if (calEvent.getId().equals(eventId)) {
          event = calEvent;
          break;
        }
      }
    } else if (Calendar.TYPE_PUBLIC == calType) {
      event = getGroupEvent(calendarId, eventId);
    }
    return event;
  }

  /**
   * {@inheritDoc}
   */
  public void confirmInvitation(String fromUserId, String confirmingEmail, String confirmingUser, int calType, String calendarId, String eventId, int answer) throws Exception {
    try {
      Map<String, String> pars = new HashMap<String, String>();
      CalendarEvent event = getInvitationEvent(calType, calendarId, eventId, fromUserId);
      if (event != null) {
        if (event.getParticipantStatus() != null) {
          for (String parStatus : event.getParticipantStatus()) {
            String[] entry = parStatus.split(":");
            if (entry.length > 1)
              pars.put(entry[0], entry[1]);
            else
              pars.put(entry[0], Utils.EMPTY_STR);
          }
        }
        String status = Utils.EMPTY_STR;
        switch (answer) {
        case Utils.DENY:
          status = Utils.STATUS_NO;
          break;
        case Utils.ACCEPT:
          status = Utils.STATUS_YES;
          break;
        case Utils.NOTSURE:
          status = Utils.STATUS_PENDING;
          break;
        default:
          break;
        }

        if (pars.containsKey(confirmingUser)) {
          pars.remove(confirmingUser);
          pars.put(confirmingUser, status);
        }
        if (pars.containsKey(confirmingEmail)) {
          pars.remove(confirmingEmail);
          pars.put(confirmingEmail, status);
        }
        Map<String, String> participant = new HashMap<String, String>();
        for (Entry<String, String> par : pars.entrySet()) {
          participant.put(par.getKey() + ":" + par.getValue(), Utils.EMPTY_STR);
        }
        event.setParticipantStatus(participant.keySet().toArray(new String[participant.keySet().size()]));
        if (Calendar.TYPE_PRIVATE == calType) {
          saveUserEvent(fromUserId, calendarId, event, false);
        } else if (Calendar.TYPE_SHARED == calType) {
          saveEventToSharedCalendar(fromUserId, calendarId, event, false);
        } else if (Calendar.TYPE_PUBLIC == calType) {
          savePublicEvent(calendarId, event, false);
        }
      }

    } catch (Exception e) {
      throw new Exception(e.getClass().toString(), e.fillInStackTrace());
    } finally {
      // session.close() ;
    }
  }

  /**
   * {@inheritDoc}
   */
  public int getTypeOfCalendar(String userName, String calendarId) {
    try {
      getUserCalendarHome(userName).getNode(calendarId);
      return Utils.PRIVATE_TYPE;
    } catch (Exception e) {
    }
    try {
      getPublicCalendarHome().getNode(calendarId);
      return Utils.PUBLIC_TYPE;
    } catch (Exception e) {
    }
    try {
      Node sharedCalendarHome = getSharedCalendarHome();
      if (sharedCalendarHome.hasNode(userName)) {
        Node userNode = sharedCalendarHome.getNode(userName);
        PropertyIterator iter = userNode.getReferences();
        Node calendar;
        while (iter.hasNext()) {
          calendar = iter.nextProperty().getParent();
          if (calendar.getProperty(Utils.EXO_ID).getString().equals(calendarId))
            return Utils.SHARED_TYPE;
        }
      }
    } catch (Exception e) {
    }
    return Utils.INVALID_TYPE;
  }

  /**
   * {@inheritDoc}
   */
  public SessionProvider createSessionProvider() {
    SessionProvider provider = sessionProviderService_.getSessionProvider(null);
    if (provider == null) {
      log.info("No user session provider was available, trying to use a system session provider");
      provider = sessionProviderService_.getSystemSessionProvider(null);
    }
    return provider;
  }

  /**
   * {@inheritDoc}
   */
  @SuppressWarnings("unused")
  public SessionProvider createUserProvider() {
    return sessionProviderService_.getSessionProvider(null);
  }

  /**
   * {@inheritDoc}
   */
  public SessionProvider createSystemProvider() {
    return sessionProviderService_.getSystemSessionProvider(null);
  }

  /**
   * {@inheritDoc}
   */
  @SuppressWarnings("unused")
  public void closeSessionProvider(SessionProvider sessionProvider) {
    if (sessionProvider != null) {
      // sessionProvider.close();
    }
  }

  /**
   * {@inheritDoc}
   */
  public Node getNodeByPath(String nodePath, SessionProvider sessionProvider) throws Exception {
    return (Node) getSession(sessionProvider).getItem(nodePath);
  }

  /**
   * {@inheritDoc}
   */
  public Session getSession(SessionProvider sprovider) throws Exception {
    ManageableRepository currentRepo = repoService_.getCurrentRepository();
    return sprovider.getSession(currentRepo.getConfiguration().getDefaultWorkspaceName(), currentRepo);
  }

  private String[] ValuesToStrings(Value[] Val) throws Exception {
    if (Val.length == 1)
      return new String[] { Val[0].getString() };
    String[] Str = new String[Val.length];
    for (int i = 0; i < Val.length; ++i) {
      Str[i] = Val[i].getString();
    }
    return Str;
  }

  public void autoShareCalendar(List<String> groupsOfUser, String reciever) throws Exception {
    Node sharedHome = getSharedCalendarHome();
    NodeIterator userNodes = sharedHome.getNodes();
    List<String> sharedCalendars = new ArrayList<String>();
    while (userNodes.hasNext()) {
      Node sharedNode = userNodes.nextNode();
      PropertyIterator iter = sharedNode.getReferences();
      while (iter.hasNext()) {
        try {
          Node calendarNode = iter.nextProperty().getParent();
          if (!sharedCalendars.contains(calendarNode.getProperty(Utils.EXO_ID).getString())) {
            sharedCalendars.add(calendarNode.getProperty(Utils.EXO_ID).getString());
            Value[] viewPers = calendarNode.getProperty(Utils.EXO_VIEW_PERMISSIONS).getValues();
            for (Value viewPer : viewPers) {
              for (String groupId : groupsOfUser) {
                if (viewPer.getString().equals(groupId)) {
                  Node sharedCalendarHome = getSharedCalendarHome();
                  Value[] values = {};
                  if (calendarNode.isNodeType(Utils.EXO_SHARED_MIXIN)) {
                    values = calendarNode.getProperty(Utils.EXO_SHARED_ID).getValues();
                  } else {
                    calendarNode.addMixin(Utils.EXO_SHARED_MIXIN);
                  }
                  List<Value> valueList = new ArrayList<Value>();
                  for (int i = 0; i < values.length; i++) {
                    Value value = values[i];
                    valueList.add(value);
                  }
                  valueList = calculateSharedCalendar(reciever, calendarNode, values, valueList, sharedCalendarHome);
                  if (valueList.size() > 0) {
                    calendarNode.setProperty(Utils.EXO_SHARED_ID, valueList.toArray(new Value[valueList.size()]));
                    calendarNode.save();
                    sharedCalendarHome.getSession().save();
                    calendarNode.getSession().save();
                  }
                }
              }
            }
          }
        } catch (Exception e) {
          if (log.isDebugEnabled()) {
            log.debug("Exception in method autoShareCalendar", e);
          }
        }
      }
    }
  }

  /**
   * {@inheritDoc}
   */
  public void removeSharedCalendar(String username, String calendarId) throws Exception {
    Node sharedCalendarHome = getSharedCalendarHome();
    if (sharedCalendarHome.hasNode(username)) {
      Node userNode = sharedCalendarHome.getNode(username);
      String uuid = userNode.getProperty("jcr:uuid").getString();
      PropertyIterator iter = userNode.getReferences();
      Node calendar;
      CalendarSetting calSetting = getCalendarSetting(getUserCalendarServiceHome(username));
      Map<String, String> map = new HashMap<String, String>();
      for (String key : calSetting.getSharedCalendarsColors()) {
        map.put(key.split(":")[0], key.split(":")[1]);
      }
      while (iter.hasNext()) {
        calendar = iter.nextProperty().getParent();
        if (calendar.getProperty(Utils.EXO_ID).getString().equals(calendarId)) {
          deleteSharedCalendar(username, uuid, calSetting, calendar, map);
          
          List<String> viewPerms = new ArrayList<String>();
          if (calendar.hasProperty(Utils.EXO_VIEW_PERMISSIONS)) {
            Value[] viewValues = calendar.getProperty(Utils.EXO_VIEW_PERMISSIONS).getValues();
            for (Value v : viewValues) {
              if (v.getString() != null && !v.getString().equals(username))
                viewPerms.add(v.getString());
            }
          }
          List<String> editPerms = new ArrayList<String>();
          if (calendar.hasProperty(Utils.EXO_EDIT_PERMISSIONS)) {
            Value[] editValues = calendar.getProperty(Utils.EXO_EDIT_PERMISSIONS).getValues();
            for (Value v : editValues) {
              if (v.getString() != null && !v.getString().equals(username))
                editPerms.add(v.getString());
            }
          }
          calendar.setProperty(Utils.EXO_VIEW_PERMISSIONS, viewPerms.toArray(new String[viewPerms.size()]));
          calendar.setProperty(Utils.EXO_EDIT_PERMISSIONS, editPerms.toArray(new String[editPerms.size()]));
          calendar.getSession().save();
          calendar.refresh(true);
          break;
        }
      }
      try {
        removeFeed(username, calendarId);
      } catch (Exception e) {
        if (log.isDebugEnabled()) {
          log.debug("Fail to remove feed", e);
        }
      }
    }
  }
  
  public void removeSharedCalendarFolder(String username) throws Exception {
    Node sharedCalendarHome = getSharedCalendarHome();
    if (sharedCalendarHome.hasNode(username)) {
      Node userNode = sharedCalendarHome.getNode(username);
      String uuid = userNode.getProperty("jcr:uuid").getString();
      PropertyIterator propIter = userNode.getReferences();
      while (propIter.hasNext()) {
        Property prop = propIter.nextProperty();
        Node calendar = prop.getParent();
        Value[] sharedIds = prop.getValues();
        List<Value> newValues = new ArrayList<Value>();
        for (Value value : sharedIds) {
          if (!value.getString().equals(uuid)) {
            newValues.add(value);
          }
        }
        calendar.setProperty(Utils.EXO_SHARED_ID, newValues.toArray(new Value[newValues.size()]));
        newValues.clear();
        
        if (calendar.hasProperty(Utils.EXO_VIEW_PERMISSIONS)) {
          Value[] viewPerms = calendar.getProperty(Utils.EXO_VIEW_PERMISSIONS).getValues();
          for (Value v : viewPerms) {
            if (!username.equals(v.getString())) {
              newValues.add(v);
            }
          }
          calendar.setProperty(Utils.EXO_VIEW_PERMISSIONS, newValues.toArray(new Value[newValues.size()]));
          newValues.clear();
        }
        
        if (calendar.hasProperty(Utils.EXO_EDIT_PERMISSIONS)) {
          Value[] editPerms = calendar.getProperty(Utils.EXO_EDIT_PERMISSIONS).getValues();
          for (Value v : editPerms) {
            if (!username.equals(v.getString())) {
              newValues.add(v);
            }
          }
          calendar.setProperty(Utils.EXO_EDIT_PERMISSIONS, newValues.toArray(new Value[newValues.size()]));
        }
        calendar.save();
      } 
      userNode.remove();
      sharedCalendarHome.save();
    }
  }

  public void autoRemoveShareCalendar(String groupId, String username) throws Exception {
    Node sharedCalendarHome = getSharedCalendarHome();
    if (sharedCalendarHome.hasNode(username)) {
      Node userNode = sharedCalendarHome.getNode(username);
      String uuid = userNode.getProperty("jcr:uuid").getString();
      PropertyIterator iter = userNode.getReferences();
      CalendarSetting calSetting = getCalendarSetting(getUserCalendarServiceHome(username));
      Node calendar;
      OrganizationService organizationService = (OrganizationService) ExoContainerContext.getCurrentContainer().getComponentInstanceOfType(OrganizationService.class);
      Map<String, String> map = new HashMap<String, String>();
      for (String key : calSetting.getSharedCalendarsColors()) {
        map.put(key.split(":")[0], key.split(":")[1]);
      }
      while (iter.hasNext()) {
        calendar = iter.nextProperty().getParent();
        List<String> viewPers = new ArrayList<String>();
        for (Value value : calendar.getProperty(Utils.EXO_VIEW_PERMISSIONS).getValues()) {
          viewPers.add(value.getString());
        }
        viewPers.remove(groupId);
        boolean deleteShared = true;
        if (viewPers.contains(username))
          deleteShared = false;
        else {
          Object[] groups = organizationService.getGroupHandler().findGroupsOfUser(username).toArray();
          for (Object object : groups) {
            if (viewPers.contains(((Group) object).getId())) {
              deleteShared = false;
              break;
            }
          }
        }
        if (deleteShared) {
          for (Value viewPer : calendar.getProperty(Utils.EXO_VIEW_PERMISSIONS).getValues())
            if (viewPer.getString().equals(groupId)) {
              deleteSharedCalendar(username, uuid, calSetting, calendar, map);
              
              calendar.getSession().save();
              calendar.refresh(true);
              break;
            }
          try {
            removeFeed(username, calendar.getProperty(Utils.EXO_ID).getString());
          } catch (Exception e) {
            if (log.isDebugEnabled()) {
              log.debug("Fail to remove feed", e);
            }
          }
        }
      }
    }
  }

  private void deleteSharedCalendar(String username,
                                    String userNodeUuid,
                                    CalendarSetting calSetting,
                                    Node calendarNode,
                                    Map<String, String> colorMap) throws Exception {
    colorMap.remove(calendarNode.getProperty(Utils.EXO_ID).getString());
    
    Value[] values = calendarNode.getProperty(Utils.EXO_SHARED_ID).getValues();
    List<Value> newValues = new ArrayList<Value>();
    for (Value value : values) {
      if (!value.getString().equals(userNodeUuid)) {
        newValues.add(value);
      }
    }
    List<String> calColors = new ArrayList<String>();
    for (String key : colorMap.keySet()) {
      calColors.add(key + Utils.COLON + colorMap.get(key));
    }
    calSetting.setSharedCalendarsColors(calColors.toArray(new String[calColors.size()]));
    saveCalendarSetting(calSetting, username);
    calendarNode.setProperty(Utils.EXO_SHARED_ID, newValues.toArray(new Value[newValues.size()]));
  }

  public void assignGroupTask(String taskId, String calendarId, String assignee) throws Exception {
    Node calendarNode = getPublicCalendarHome().getNode(calendarId);
    Node eventNode = calendarNode.getNode(taskId);
    String taskDelegator = eventNode.getProperty(Utils.EXO_TASK_DELEGATOR).getString();
    if (assignee != null && assignee.length() > 0) {
      if (taskDelegator == null || taskDelegator.trim().length() == 0) {
        taskDelegator = assignee;
      } else {
        taskDelegator += "," + assignee;
      }
      eventNode.setProperty(Utils.EXO_TASK_DELEGATOR, taskDelegator);
      eventNode.getSession().save();
    }
  }

  public void setGroupTaskStatus(String taskId, String calendarId, String status) throws Exception {
    Node calendarNode = getPublicCalendarHome().getNode(calendarId);
    Node eventNode = calendarNode.getNode(taskId);
    if (status != null && status.length() > 0) {
      eventNode.setProperty(Utils.EXO_EVENT_STATE, status);
      eventNode.getSession().save();
    }
  }

  public boolean isRemoteCalendar(String username, String calendarId) throws Exception {
    try {
      Node calendarNode = getUserCalendarHome(username).getNode(calendarId);
      if (calendarNode.isNodeType(Utils.EXO_REMOTE_MIXIN))
        return true;
      return false;
    } catch (Exception e) {
      log.debug(e.getMessage());
      return false;
    }
  }

  @Override
  public Calendar updateRemoteCalendarInfo(RemoteCalendar remoteCalendar) throws Exception {
    Node calendarNode = getUserCalendarHome(remoteCalendar.getUsername()).getNode(remoteCalendar.getCalendarId());
    updateRemoteCalendarInfo(calendarNode, remoteCalendar);
    return getUserCalendar(remoteCalendar.getUsername(), remoteCalendar.getCalendarId());
  }

  private void updateRemoteCalendarInfo(Node calendarNode, RemoteCalendar remoteCalendar) throws Exception {
    calendarNode.setProperty(Utils.EXO_REMOTE_TYPE, remoteCalendar.getType());
    calendarNode.setProperty(Utils.EXO_DESCRIPTION, remoteCalendar.getDescription());
    calendarNode.setProperty(Utils.EXO_REMOTE_URL, remoteCalendar.getRemoteUrl());
    calendarNode.setProperty(Utils.EXO_REMOTE_SYNC_PERIOD, remoteCalendar.getSyncPeriod());
    calendarNode.setProperty(Utils.EXO_REMOTE_USERNAME, (remoteCalendar.getRemoteUser()));
    calendarNode.setProperty(Utils.EXO_REMOTE_PASSWORD, (remoteCalendar.getRemotePassword()));
    calendarNode.setProperty(Utils.EXO_REMOTE_AFTER_DATE, (remoteCalendar.getAfterDateSave()));
    calendarNode.setProperty(Utils.EXO_REMOTE_BEFORE_DATE, (remoteCalendar.getBeforeDateSave()));
    if (remoteCalendar.getLastUpdated() != null)
      calendarNode.setProperty(Utils.EXO_REMOTE_LAST_UPDATED, remoteCalendar.getLastUpdated());
    else if (!calendarNode.hasProperty(Utils.EXO_REMOTE_LAST_UPDATED))
      calendarNode.setProperty(Utils.EXO_REMOTE_LAST_UPDATED, Utils.getInstanceTempCalendar());
    calendarNode.save();
  }

  public RemoteCalendar getRemoteCalendar(String username, String remoteCalendarId) throws Exception {
    RemoteCalendar remoteCalendar = new RemoteCalendar();
    try {
      Node calendarNode = getUserCalendarHome(username).getNode(remoteCalendarId);
      remoteCalendar.setUsername(username);
      remoteCalendar.setCalendarId(remoteCalendarId);
      remoteCalendar.setType(calendarNode.getProperty(Utils.EXO_REMOTE_TYPE).getString());
      remoteCalendar.setSyncPeriod(calendarNode.getProperty(Utils.EXO_REMOTE_SYNC_PERIOD).getString());
      remoteCalendar.setBeforeDateSave(calendarNode.getProperty(Utils.EXO_REMOTE_BEFORE_DATE).getString());
      remoteCalendar.setAfterDateSave(calendarNode.getProperty(Utils.EXO_REMOTE_AFTER_DATE).getString());
      remoteCalendar.setRemoteUrl(calendarNode.getProperty(Utils.EXO_REMOTE_URL).getString());
      remoteCalendar.setRemoteUser(calendarNode.getProperty(Utils.EXO_REMOTE_USERNAME).getString());
      remoteCalendar.setRemotePassword(calendarNode.getProperty(Utils.EXO_REMOTE_PASSWORD).getString());
      remoteCalendar.setLastUpdated(calendarNode.getProperty(Utils.EXO_REMOTE_LAST_UPDATED).getDate());
    } catch (Exception e) {
      log.debug("Failed to get RemoteCalendar by user.", e);
    }
    return remoteCalendar;
  }

  public Calendar createRemoteCalendar(RemoteCalendar remoteCalendar) throws Exception {
    Node cat = null;
    boolean isExists = false;
    String categoryId;

    // check if calendar category 'Remote' exists
    NodeIterator iter = getCalendarCategoryHome(remoteCalendar.getUsername()).getNodes();
    while (iter.hasNext()) {
      cat = iter.nextNode();
      if (cat.getProperty(Utils.EXO_NAME).getString().equals("Remote")) {
        isExists = true;
        break;
      }
    }

    if (!isExists) {
      CalendarCategory calendarCate = new CalendarCategory();
      calendarCate.setDescription("Remote Calendar");
      calendarCate.setName("Remote");
      categoryId = calendarCate.getId();
      saveCalendarCategory(remoteCalendar.getUsername(), calendarCate, true);
    } else {
      categoryId = cat.getProperty(Utils.EXO_ID).getString();
    }

    // create new eXo calendar in 'Remote' category
    Calendar eXoCalendar = new Calendar();
    eXoCalendar.setName(remoteCalendar.getCalendarName());
    // exoCalendar.setCalendarColor(org.exoplatform.calendar.service.Calendar.COLORS[new Random().nextInt(org.exoplatform.calendar.service.Calendar.COLORS.length -1)]) ;
    // exoCalendar.setDescription(iCalendar.getProductId().getValue()) ;
    eXoCalendar.setCategoryId(categoryId);
    eXoCalendar.setPublic(false);
    eXoCalendar.setCalendarOwner(remoteCalendar.getUsername());
    saveUserCalendar(remoteCalendar.getUsername(), eXoCalendar, true);

    // add mixin type exo:remoteCalendar to this calendar
    Node calendarNode = getUserCalendarHome(remoteCalendar.getUsername()).getNode(eXoCalendar.getId());
    if (!calendarNode.isNodeType(Utils.EXO_REMOTE_MIXIN)) {
      calendarNode.addMixin(Utils.EXO_REMOTE_MIXIN);
    }
    updateRemoteCalendarInfo(calendarNode, remoteCalendar);
    return eXoCalendar;
  }

  public void setRemoteEvent(String username, String calendarId, String eventId, String href, String etag) throws Exception {
    Node eventNode = getUserCalendarHome(username).getNode(calendarId).getNode(eventId);
    if (!eventNode.isNodeType(Utils.EXO_REMOTE_EVENT_MIXIN)) {
      eventNode.addMixin(Utils.EXO_REMOTE_EVENT_MIXIN);
    }
    if (href != null) {
      eventNode.setProperty(Utils.EXO_CALDAV_HREF, href);
    }
    eventNode.setProperty(Utils.EXO_CALDAV_ETAG, etag);
    eventNode.save();
  }

  @Override
  public void setRemoteCalendarLastUpdated(String username, String calendarId, java.util.Calendar timeGMT) throws Exception {
    Node calendarNode = getUserCalendarHome(username).getNode(calendarId);
    calendarNode.setProperty(Utils.EXO_REMOTE_LAST_UPDATED, timeGMT);
    calendarNode.save();
  }

  public Calendar getRemoteCalendar(String username, String remoteUrl, String remoteType) throws Exception {
    try {
      Node calendarHome = getUserCalendarHome(username);
      String queryString = new StringBuffer("/jcr:root" + calendarHome.getPath() + "//element(*,exo:remoteCalendar)[@exo:remoteUrl='").append(remoteUrl).append("' and @exo:remoteType='").append(remoteType).append("']").toString();
      QueryManager queryManager = calendarHome.getSession().getWorkspace().getQueryManager();
      Query query = queryManager.createQuery(queryString.toString(), Query.XPATH);
      QueryResult results = query.execute();
      NodeIterator iter = results.getNodes();
      if (iter.hasNext()) {
        Node calNode = iter.nextNode();
        Calendar calendar = getCalendar(null, username, calNode, true);
        return calendar;
      }
      return null;
    } catch (Exception e) {
      return null;
    }
  }

  public int getRemoteCalendarCount(String username) throws Exception {
    try {
      Node calendarHome = getUserCalendarHome(username);
      String queryString = new StringBuffer("/jcr:root" + calendarHome.getPath() + "//element(*,exo:remoteCalendar)").toString();
      QueryManager queryManager = calendarHome.getSession().getWorkspace().getQueryManager();
      Query query = queryManager.createQuery(queryString.toString(), Query.XPATH);
      QueryResult results = query.execute();
      NodeIterator iter = results.getNodes();
      int count = 0;
      while (iter.hasNext()) {
        iter.nextNode();
        count++;
      }
      return count;
    } catch (Exception e) {
      if (log.isDebugEnabled())
        log.debug(e);
      return 0;
    }
  }

  /*
   * (non-Javadoc)
   * @see org.exoplatform.calendar.service.DataStorage#setCalDavResourceHref(java.lang.String, java.lang.String, java.lang.String, java.lang.String)
   */
  @Override
  public void setCalDavResourceHref(String username, String calendarId, String eventId, String href) throws Exception {
    Node eventNode = getUserCalendarHome(username).getNode(calendarId).getNode(eventId);
    eventNode.setProperty(Utils.EXO_CALDAV_HREF, href);
  }

  /*
   * (non-Javadoc)
   * @see org.exoplatform.calendar.service.DataStorage#setCalDavResourceEtag(java.lang.String, java.lang.String, java.lang.String, java.lang.String)
   */
  @Override
  public void setCalDavResourceEtag(String username, String calendarId, String eventId, String etag) throws Exception {
    Node eventNode = getUserCalendarHome(username).getNode(calendarId).getNode(eventId);
    eventNode.setProperty(Utils.EXO_CALDAV_HREF, etag);
  }

}
