com.microsoft.exchange.impl.BaseExchangeCalendarDataDao.java Source code

Java tutorial

Introduction

Here is the source code for com.microsoft.exchange.impl.BaseExchangeCalendarDataDao.java

Source

/**
 * See the NOTICE file distributed with this work
 * for additional information regarding copyright ownership.
 * Board of Regents of the University of Wisconsin System
 * licenses this file to you under the Apache License,
 * Version 2.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a
 * copy of the License at:
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on
 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package com.microsoft.exchange.impl;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.xml.bind.JAXBContext;

import net.fortuna.ical4j.model.Calendar;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.Validate;
import org.apache.commons.lang.time.StopWatch;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.validator.routines.EmailValidator;
import org.joda.time.Interval;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.support.DataAccessUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StopWatch.TaskInfo;

import com.microsoft.exchange.DateHelp;
import com.microsoft.exchange.ExchangeRequestFactory;
import com.microsoft.exchange.ExchangeResponseUtils;
import com.microsoft.exchange.ExchangeWebServices;
import com.microsoft.exchange.exception.ExchangeExceededFindCountLimitRuntimeException;
import com.microsoft.exchange.exception.ExchangeInvalidUPNRuntimeException;
import com.microsoft.exchange.exception.ExchangeRuntimeException;
import com.microsoft.exchange.messages.CreateFolder;
import com.microsoft.exchange.messages.CreateFolderResponse;
import com.microsoft.exchange.messages.CreateItem;
import com.microsoft.exchange.messages.CreateItemResponse;
import com.microsoft.exchange.messages.DeleteFolder;
import com.microsoft.exchange.messages.DeleteFolderResponse;
import com.microsoft.exchange.messages.DeleteItem;
import com.microsoft.exchange.messages.DeleteItemResponse;
import com.microsoft.exchange.messages.EmptyFolderResponse;
import com.microsoft.exchange.messages.FindFolder;
import com.microsoft.exchange.messages.FindFolderResponse;
import com.microsoft.exchange.messages.FindItem;
import com.microsoft.exchange.messages.FindItemResponse;
import com.microsoft.exchange.messages.GetFolder;
import com.microsoft.exchange.messages.GetFolderResponse;
import com.microsoft.exchange.messages.GetItem;
import com.microsoft.exchange.messages.GetItemResponse;
import com.microsoft.exchange.messages.GetServerTimeZones;
import com.microsoft.exchange.messages.GetServerTimeZonesResponse;
import com.microsoft.exchange.messages.ResolveNames;
import com.microsoft.exchange.messages.ResolveNamesResponse;
import com.microsoft.exchange.types.BaseFolderIdType;
import com.microsoft.exchange.types.BaseFolderType;
import com.microsoft.exchange.types.CalendarFolderType;
import com.microsoft.exchange.types.CalendarItemCreateOrDeleteOperationType;
import com.microsoft.exchange.types.CalendarItemType;
import com.microsoft.exchange.types.ConnectingSIDType;
import com.microsoft.exchange.types.DefaultShapeNamesType;
import com.microsoft.exchange.types.DisposalType;
import com.microsoft.exchange.types.DistinguishedFolderIdNameType;
import com.microsoft.exchange.types.ExtendedPropertyType;
import com.microsoft.exchange.types.FolderIdType;
import com.microsoft.exchange.types.FolderQueryTraversalType;
import com.microsoft.exchange.types.ItemIdType;
import com.microsoft.exchange.types.ItemType;
import com.microsoft.exchange.types.TaskType;
import com.microsoft.exchange.types.TasksFolderType;
import com.microsoft.exchange.types.TimeZoneDefinitionType;
import com.microsoft.exchange.messages.EmptyFolder;

/**
 * TODO - what is the intent for this class? Does it belong within the client?
 * If so, is the intent that it be a base class for interacting with {@link ExchangeWebServices}?
 * Should it be abstract? 
 * 
 * @author Collin Cudd
 */
public class BaseExchangeCalendarDataDao {

    protected final Log log = LogFactory.getLog(this.getClass());

    private JAXBContext jaxbContext;
    private ExchangeWebServices webServices;

    // TODO no references, needed internally?
    private ExchangeRequestFactory requestFactory = new ExchangeRequestFactory();
    // TODO no references, needed internally?
    private ExchangeResponseUtils responseUtils = new ExchangeResponseUtilsImpl();

    private int maxRetries = 10;

    @Value("${username}")
    private String adminUsername;

    public int getMaxRetries() {
        return maxRetries;
    }

    public void setMaxRetries(int maxRetries) {
        this.maxRetries = maxRetries;
    }

    public ExchangeWebServices getWebServices() {
        return webServices;
    }

    /**
     * @param exchangeWebServices the exchangeWebServices to set
     */
    @Autowired
    @Qualifier("ewsClient")
    public void setWebServices(ExchangeWebServices exchangeWebServices) {
        this.webServices = exchangeWebServices;
    }

    /**
     * @return the requestFactory
     */
    public ExchangeRequestFactory getRequestFactory() {
        return requestFactory;
    }

    /**
     * @param requestFactory the requestFactory to set
     */
    @Autowired(required = false)
    public void setRequestFactory(ExchangeRequestFactory exchangeRequestFactory) {
        this.requestFactory = exchangeRequestFactory;
    }

    public ExchangeResponseUtils getResponseUtils() {
        return responseUtils;
    }

    public void setResponseUtils(ExchangeResponseUtils exchangeResponseUtils) {
        this.responseUtils = exchangeResponseUtils;
    }

    /**
     * @return the jaxbContext
     */
    public JAXBContext getJaxbContext() {
        return jaxbContext;
    }

    /**
     * @param jaxbContext the jaxbContext to set
     */
    @Autowired
    public void setJaxbContext(JAXBContext jaxbContext) {
        this.jaxbContext = jaxbContext;
    }

    public static long getWaitTimeExp(int retryCount) {
        long waitTime = ((long) Math.pow(2, retryCount) * 100L);
        return waitTime;
    }

    protected void setContextCredentials(String upn) {
        Validate.isTrue(StringUtils.isNotBlank(upn), "upn argument cannot be blank");
        ConnectingSIDType connectingSID = new ConnectingSIDType();
        connectingSID.setPrincipalName(upn);
        ThreadLocalImpersonationConnectingSIDSourceImpl.setConnectingSID(connectingSID);
    }

    public CalendarFolderType getCalendarFolder(String upn, FolderIdType folderId) {
        BaseFolderType folder = getFolder(upn, folderId);
        CalendarFolderType calendarFolderType = null;
        if (folder instanceof CalendarFolderType) {
            calendarFolderType = (CalendarFolderType) folder;
        }
        return calendarFolderType;
    }

    public TasksFolderType getTaskFolder(String upn, FolderIdType folderId) {
        BaseFolderType folder = getFolder(upn, folderId);
        TasksFolderType taskFolderType = null;
        if (folder instanceof TasksFolderType) {
            taskFolderType = (TasksFolderType) folder;
        }
        return taskFolderType;
    }

    public BaseFolderType getFolder(String upn, FolderIdType folderIdType) {
        setContextCredentials(upn);
        GetFolder getFolderRequest = getRequestFactory().constructGetFolderById(folderIdType);
        GetFolderResponse getFolderResponse = getWebServices().getFolder(getFolderRequest);
        Set<BaseFolderType> response = getResponseUtils().parseGetFolderResponse(getFolderResponse);
        return DataAccessUtils.singleResult(response);
    }

    protected BaseFolderType getPrimaryFolder(String upn, DistinguishedFolderIdNameType parent) {
        setContextCredentials(upn);
        GetFolder getFolderRequest = getRequestFactory().constructGetFolderByName(parent);
        GetFolderResponse getFolderResponse = getWebServices().getFolder(getFolderRequest);
        Set<BaseFolderType> response = getResponseUtils().parseGetFolderResponse(getFolderResponse);
        return DataAccessUtils.singleResult(response);
    }

    public BaseFolderType getPrimaryCalendarFolder(String upn) {
        return getPrimaryFolder(upn, DistinguishedFolderIdNameType.CALENDAR);
    }

    public BaseFolderType getPrimaryTaskFolder(String upn) {
        return getPrimaryFolder(upn, DistinguishedFolderIdNameType.TASKS);
    }

    private List<BaseFolderType> getFoldersByType(String upn, DistinguishedFolderIdNameType parent) {
        List<BaseFolderType> folders = new ArrayList<BaseFolderType>();
        BaseFolderType baseFolderType = getPrimaryFolder(upn, parent);
        if (null != baseFolderType) {
            folders.add(baseFolderType);
        }
        List<BaseFolderType> seondaryFolders = getSeondaryFolders(upn, parent);
        if (!CollectionUtils.isEmpty(seondaryFolders)) {
            for (BaseFolderType b : seondaryFolders) {
                if (baseFolderType.getClass().equals(b.getClass())) {
                    folders.add(b);
                }
            }
        }
        return folders;
    }

    /**
     * Gets all secondary folders for given DistinguisheFolderName
     * @param upn
     * @param parent
     * @return
     */
    private List<BaseFolderType> getSeondaryFolders(String upn, DistinguishedFolderIdNameType parent) {
        Validate.notEmpty(upn, "upn cannnot be empty");
        setContextCredentials(upn);
        FindFolder findFolderRequest = getRequestFactory().constructFindFolder(parent,
                DefaultShapeNamesType.ALL_PROPERTIES, FolderQueryTraversalType.DEEP);
        FindFolderResponse findFolderResponse = getWebServices().findFolder(findFolderRequest);
        return getResponseUtils().parseFindFolderResponse(findFolderResponse);
    }

    /*
     * return all calendar folders
     */
    public List<BaseFolderType> getAllCalendarFolders(String upn) {
        Validate.notEmpty(upn, "upn cannnot be empty");
        return getFoldersByType(upn, DistinguishedFolderIdNameType.CALENDAR);
    }

    public List<BaseFolderType> getAllTaskFolders(String upn) {
        Validate.notEmpty(upn, "upn cannnot be empty");
        return getFoldersByType(upn, DistinguishedFolderIdNameType.TASKS);
    }

    public FolderIdType getCalendarFolderId(String upn, String calendarName) {
        Map<String, String> calendarFolderMap = getCalendarFolderMap(upn);
        if (!CollectionUtils.isEmpty(calendarFolderMap) && calendarFolderMap.containsValue(calendarName)) {
            for (String c_id : calendarFolderMap.keySet()) {
                String c_name = calendarFolderMap.get(c_id);
                if (calendarName.equals(c_name)) {
                    FolderIdType folderIdType = new FolderIdType();
                    folderIdType.setId(c_id);
                    return folderIdType;
                }
            }
        }
        throw new ExchangeRuntimeException("No calendar folder with name of '" + calendarName + "' for " + upn);
    }

    public Map<String, String> getCalendarFolderMap(String upn) {
        Map<String, String> calendarsMap = new HashMap<String, String>();
        List<BaseFolderType> allCalendarFolders = getAllCalendarFolders(upn);
        for (BaseFolderType folderType : allCalendarFolders) {
            String name = folderType.getDisplayName();
            String id = folderType.getFolderId().getId();
            calendarsMap.put(id, name);
        }
        return calendarsMap;
    }

    public Map<String, String> getTaskFolderMap(String upn) {
        Map<String, String> taskFolderMap = new HashMap<String, String>();
        List<BaseFolderType> allTaskFolders = getAllTaskFolders(upn);
        for (BaseFolderType b : allTaskFolders) {
            String displayName = b.getDisplayName();
            String id = b.getFolderId().getId();
            taskFolderMap.put(id, displayName);
        }
        return taskFolderMap;
    }

    public Set<ItemIdType> findCalendarItemIds(String upn, Date startDate, Date endDate) {
        return findCalendarItemIdsInternal(upn, startDate, endDate, null, 0);
    }

    public Set<ItemIdType> findCalendarItemIds(String upn, Date startDate, Date endDate,
            Collection<FolderIdType> calendarIds) {
        return findCalendarItemIdsInternal(upn, startDate, endDate, calendarIds, 0);
    }

    /**
     * This method uses a CalendarView...
     * 
     * The FindItem operation can return results in a CalendarView element. The
     * CalendarView element returns single calendar items and all occurrences.
     * If a CalendarView element is not used, single calendar items and
     * recurring master calendar items are returned. The occurrences must be
     * expanded from the recurring master if a CalendarView element is not used.
     * 
     * -- http://msdn.microsoft.com/en-us/library/office/aa566107(v=exchg.140).
     * aspx
     * 
     * @param upn
     * @param startDate
     * @param endDate
     * @param calendarIds
     * @param depth
     * @return
     */
    private Set<ItemIdType> findCalendarItemIdsInternal(String upn, Date startDate, Date endDate,
            Collection<FolderIdType> calendarIds, int depth) {
        Validate.isTrue(StringUtils.isNotBlank(upn), "upn argument cannot be blank");
        Validate.notNull(startDate, "startDate argument cannot be null");
        Validate.notNull(endDate, "endDate argument cannot be null");

        //folderIds can be null

        int newDepth = depth + 1;
        if (depth > getMaxRetries()) {
            throw new ExchangeRuntimeException("findCalendarItemIdsInternal(upn=" + upn + ",startDate=" + startDate
                    + ",+endDate=" + endDate + ",...) failed " + getMaxRetries() + " consecutive attempts.");
        } else {
            setContextCredentials(upn);
            FindItem request = getRequestFactory().constructFindCalendarItemIdsByDateRange(startDate, endDate,
                    calendarIds);
            try {
                FindItemResponse response = getWebServices().findItem(request);
                return getResponseUtils().parseFindItemIdResponseNoOffset(response);

            } catch (ExchangeInvalidUPNRuntimeException e0) {
                log.warn("findCalendarItemIdsInternal(upn=" + upn + ",startDate=" + startDate + ",+endDate="
                        + endDate
                        + ",...) ExchangeInvalidUPNRuntimeException.  Attempting to resolve valid upn... - failure #"
                        + newDepth);

                String resolvedUpn = resolveUpn(upn);
                if (StringUtils.isNotBlank(resolvedUpn) && (!resolvedUpn.equalsIgnoreCase(upn))) {
                    return findCalendarItemIdsInternal(resolvedUpn, startDate, endDate, calendarIds, newDepth);
                } else {
                    //rethrow
                    throw e0;
                }
            } catch (ExchangeExceededFindCountLimitRuntimeException e1) {
                log.warn("findCalendarItemIdsInternal(upn=" + upn + ",startDate=" + startDate + ",+endDate="
                        + endDate + ",...) ExceededFindCountLimit splitting request and trying again. - failure #"
                        + newDepth);
                Set<ItemIdType> foundItems = new HashSet<ItemIdType>();
                List<Interval> intervals = DateHelp.generateIntervals(startDate, endDate);
                for (Interval i : intervals) {
                    foundItems.addAll(findCalendarItemIdsInternal(upn, i.getStart().toDate(), i.getEnd().toDate(),
                            calendarIds, newDepth));
                }
                return foundItems;
            } catch (Exception e2) {
                long backoff = getWaitTimeExp(newDepth);
                log.warn("findCalendarItemIdsInternal(upn=" + upn + ",startDate=" + startDate + ",+endDate="
                        + endDate + ",...) - failure #" + newDepth + ". Sleeping for " + backoff + " before retry. "
                        + e2.getMessage());
                try {
                    Thread.sleep(backoff);
                } catch (InterruptedException e1) {
                    log.warn("InterruptedException=" + e1);
                }
                return findCalendarItemIdsInternal(upn, startDate, endDate, calendarIds, newDepth);
            }
        }
    }

    public Set<ItemIdType> findindFirstItemIdSet(String upn, Collection<FolderIdType> folderIds) {
        FindItem request = getRequestFactory().constructFindFirstItemIdSet(folderIds);
        Pair<Set<ItemIdType>, Integer> pair = findItemIdsInternal(upn, request, 0);
        return pair.getLeft();
    }

    public Set<ItemIdType> findItemIds(String upn, Collection<FolderIdType> folderIds) {
        FindItem request = getRequestFactory().constructFindFirstItemIdSet(folderIds);
        Pair<Set<ItemIdType>, Integer> pair = findItemIdsInternal(upn, request, 0);
        return pair.getLeft();
    }

    public Set<ItemIdType> findAllItemIds(String upn, Collection<FolderIdType> folderIds) {
        FindItem request = getRequestFactory().constructFindFirstItemIdSet(folderIds);
        Pair<Set<ItemIdType>, Integer> pair = findItemIdsInternal(upn, request, 0);
        Set<ItemIdType> itemIds = pair.getLeft();
        Integer nextOffset = pair.getRight();
        while (nextOffset > 0) {
            request = getRequestFactory().constructFindNextItemIdSet(nextOffset, folderIds);
            pair = findItemIdsInternal(upn, request, 0);
            itemIds.addAll(pair.getLeft());
            nextOffset = pair.getRight();
        }
        return itemIds;
    }

    private Pair<Set<ItemIdType>, Integer> findItemIdsInternal(String upn, FindItem request, int depth) {
        Validate.isTrue(StringUtils.isNotBlank(upn), "upn argument cannot be blank");
        Validate.notNull(request, "request argument cannot be null");
        int newDepth = depth + 1;
        if (depth > getMaxRetries()) {
            throw new ExchangeRuntimeException("findCalendarItemIdsInternal(upn=" + upn + ",request=" + request
                    + ",...) failed " + getMaxRetries() + " consecutive attempts.");
        } else {
            setContextCredentials(upn);
            try {
                FindItemResponse response = getWebServices().findItem(request);
                Pair<Set<ItemIdType>, Integer> parsed = getResponseUtils().parseFindItemIdResponse(response);

                return parsed;
            } catch (ExchangeInvalidUPNRuntimeException e0) {
                log.warn("findCalendarItemIdsInternal(upn=" + upn + ",request=" + request
                        + ",...) ExchangeInvalidUPNRuntimeException.  Attempting to resolve valid upn... - failure #"
                        + newDepth);

                String resolvedUpn = resolveUpn(upn);
                if (StringUtils.isNotBlank(resolvedUpn) && (!resolvedUpn.equalsIgnoreCase(upn))) {
                    return findItemIdsInternal(resolvedUpn, request, newDepth);
                } else {
                    //rethrow
                    throw e0;
                }
                //         }catch(ExchangeExceededFindCountLimitRuntimeException e1) {
                //            log.warn("findCalendarItemIdsInternal(upn="+upn+",request="+request+",...) ExceededFindCountLimit splitting request and trying again. - failure #"+newDepth);
                //            Set<ItemIdType> foundItems = new HashSet<ItemIdType>();
                //            List<Interval> intervals = DateHelp.generateIntervals(startDate, endDate);
                //            for(Interval i: intervals) {
                //               foundItems.addAll(findCalendarItemIdsInternal(upn,i.getStart().toDate(), i.getEnd().toDate(),calendarIds,newDepth));
                //            }
                //            return foundItems;
            } catch (Exception e2) {
                long backoff = getWaitTimeExp(newDepth);
                log.warn("findCalendarItemIdsInternal(upn=" + upn + ",request=" + request + ",...) - failure #"
                        + newDepth + ". Sleeping for " + backoff + " before retry. " + e2.getMessage());
                try {
                    Thread.sleep(backoff);
                } catch (InterruptedException e1) {
                    log.warn("InterruptedException=" + e1);
                }
                return findItemIdsInternal(upn, request, newDepth);
            }
        }

    }

    public Set<CalendarItemType> getCalendarItems(String upn, Date startDate, Date endDate,
            Collection<FolderIdType> calendarFolderId) {
        Set<ItemIdType> itemIds = findCalendarItemIds(upn, startDate, endDate, calendarFolderId);
        return getCalendarItems(upn, itemIds);
    }

    public Set<CalendarItemType> getCalendarItems(String upn, Set<ItemIdType> itemIds) {
        Set<CalendarItemType> calendarItems = new HashSet<CalendarItemType>();
        Set<ItemType> items = getItemsInternal(upn, itemIds, 0);
        for (ItemType item : items) {
            if (item instanceof CalendarItemType) {
                calendarItems.add((CalendarItemType) item);
            } else {
                log.warn("non-calendarItemType will be excluded from result set");
            }
        }
        return calendarItems;
    }

    public Set<TaskType> getTaskItems(String upn, Set<ItemIdType> itemIds) {
        Set<TaskType> taskItems = new HashSet<TaskType>();
        Set<ItemType> items = getItemsInternal(upn, itemIds, 0);
        for (ItemType item : items) {
            if (item instanceof TaskType) {
                taskItems.add((TaskType) item);
            } else {
                log.warn("non-TaskType will be excluded from result set");
            }
        }
        return taskItems;
    }

    private Set<ItemType> getItemsInternal(String upn, Set<ItemIdType> itemIds, int depth) {
        Validate.isTrue(StringUtils.isNotBlank(upn), "upn argument cannot be blank");
        Validate.notEmpty(itemIds, "itemids argument cannot be empty");

        //folderIds can be null

        int newDepth = depth + 1;
        if (depth > getMaxRetries()) {
            throw new ExchangeRuntimeException(
                    "getItemsInternal(upn=" + upn + ",...) failed " + getMaxRetries() + " consecutive attempts.");
        } else {
            setContextCredentials(upn);
            GetItem request = getRequestFactory().constructGetItems(itemIds);
            try {
                GetItemResponse response = getWebServices().getItem(request);
                return getResponseUtils().parseGetItemResponse(response);
            } catch (Exception e) {
                long backoff = getWaitTimeExp(newDepth);
                log.warn("getItemsInternal - failure #" + newDepth + ". Sleeping for " + backoff + " before retry. "
                        + e.getMessage());
                try {
                    Thread.sleep(backoff);
                } catch (InterruptedException e1) {
                    log.warn("InterruptedException=" + e1);
                }
                return getItemsInternal(upn, itemIds, newDepth);
            }
        }
    }

    private ItemIdType createCalendarItemInternal(String upn, CalendarItemType calendarItem, int depth) {
        Validate.isTrue(StringUtils.isNotBlank(upn), "upn argument cannot be blank");
        Validate.notNull(calendarItem, "calendarItem argument cannot be empty");

        int newDepth = depth + 1;
        if (depth > getMaxRetries()) {
            throw new ExchangeRuntimeException("createCalendarItemInternal(upn=" + upn + ",...) failed "
                    + getMaxRetries() + " consecutive attempts.");
        } else {
            setContextCredentials(upn);

            Set<CalendarItemType> singleton = Collections.singleton(calendarItem);
            CalendarItemCreateOrDeleteOperationType sendTo = CalendarItemCreateOrDeleteOperationType.SEND_TO_ALL_AND_SAVE_COPY;
            CreateItem request = getRequestFactory().constructCreateCalendarItem(singleton, sendTo, null);

            try {
                CreateItemResponse response = getWebServices().createItem(request);
                List<ItemIdType> createdCalendarItems = getResponseUtils().parseCreateItemResponse(response);
                return DataAccessUtils.singleResult(createdCalendarItems);
            } catch (Exception e) {
                long backoff = getWaitTimeExp(newDepth);
                log.warn("createCalendarItemInternal - failure #" + newDepth + ". Sleeping for " + backoff
                        + " before retry. " + e.getMessage());
                try {
                    Thread.sleep(backoff);
                } catch (InterruptedException e1) {
                    log.warn("InterruptedException=" + e1);
                }
                return createCalendarItemInternal(upn, calendarItem, newDepth);
            }
        }

    }

    public ItemIdType createCalendarItem(String upn, CalendarItemType calendarItem) {
        return createCalendarItemInternal(upn, calendarItem, 0);
    }

    public FolderIdType createCalendarFolder(String upn, String displayName) {
        setContextCredentials(upn);
        log.debug("createCalendarFolder upn=" + upn + ", displayName=" + displayName);
        //default implementation - null for extendedProperties
        CreateFolder createCalendarFolderRequest = getRequestFactory().constructCreateCalendarFolder(displayName,
                null);
        CreateFolderResponse createFolderResponse = getWebServices().createFolder(createCalendarFolderRequest);
        Set<FolderIdType> folders = getResponseUtils().parseCreateFolderResponse(createFolderResponse);
        return DataAccessUtils.singleResult(folders);
    }

    private boolean deleteCalendarItemsInternal(String upn, Collection<ItemIdType> itemIds, int depth) {
        Validate.isTrue(StringUtils.isNotBlank(upn), "upn argument cannot be blank");
        Validate.notEmpty(itemIds, "itemIds argument cannot be empty");

        int newDepth = depth + 1;
        if (depth > getMaxRetries()) {
            throw new ExchangeRuntimeException("createCalendarItemInternal(upn=" + upn + ",...) failed "
                    + getMaxRetries() + " consecutive attempts.");
        } else {
            setContextCredentials(upn);
            DeleteItem request = getRequestFactory().constructDeleteCalendarItems(itemIds, DisposalType.HARD_DELETE,
                    CalendarItemCreateOrDeleteOperationType.SEND_TO_NONE);

            try {
                DeleteItemResponse response = getWebServices().deleteItem(request);
                boolean success = getResponseUtils().confirmSuccess(response);
                return success;

            } catch (Exception e) {
                long backoff = getWaitTimeExp(newDepth);
                log.warn("deleteCalendarItemsInternal - failure #" + newDepth + ". Sleeping for " + backoff
                        + " before retry. " + e.getMessage());
                try {
                    Thread.sleep(backoff);
                } catch (InterruptedException e1) {
                    log.warn("InterruptedException=" + e1);
                }
                return deleteCalendarItemsInternal(upn, itemIds, newDepth);
            }
        }
    }

    public boolean deleteCalendarItems(String upn, Collection<ItemIdType> itemIds) {
        return deleteCalendarItemsInternal(upn, itemIds, 0);
    }

    public Set<String> resolveEmailAddresses(String alias) {
        Validate.isTrue(StringUtils.isNotBlank(alias), "alias argument cannot be blank");
        setContextCredentials(adminUsername);
        ResolveNames request = getRequestFactory().constructResolveNames(alias);
        ResolveNamesResponse response = getWebServices().resolveNames(request);
        return getResponseUtils().parseResolveNamesResponse(response);

    }

    public String resolveUpn(String emailAddress) {
        Validate.isTrue(StringUtils.isNotBlank(emailAddress), "emailAddress argument cannot be blank");
        Validate.isTrue(EmailValidator.getInstance().isValid(emailAddress), "emailAddress argument must be valid");

        emailAddress = "smtp:" + emailAddress;

        Set<String> results = new HashSet<String>();
        Set<String> addresses = resolveEmailAddresses(emailAddress);
        for (String addr : addresses) {
            try {
                BaseFolderType primaryCalendarFolder = getPrimaryCalendarFolder(addr);
                if (null == primaryCalendarFolder) {
                    throw new ExchangeRuntimeException("CALENDAR NOT FOUND");
                } else {
                    results.add(addr);
                }
            } catch (Exception e) {
                log.warn("resolveUpn -- " + addr + " NOT VALID. " + e.getMessage());
            }
        }
        if (CollectionUtils.isEmpty(results)) {
            throw new ExchangeRuntimeException("resolveUpn(" + emailAddress + ") failed -- no results.");
        } else {
            if (results.size() > 1) {
                throw new ExchangeRuntimeException("resolveUpn(" + emailAddress + ") failed -- multiple results.");
            } else {
                return DataAccessUtils.singleResult(results);
            }
        }
    }

    public List<TimeZoneDefinitionType> getServerTimeZones(String tzid, boolean fullTimeZoneData) {
        GetServerTimeZones request = getRequestFactory().constructGetServerTimeZones(tzid, fullTimeZoneData);
        setContextCredentials(adminUsername);
        GetServerTimeZonesResponse response = getWebServices().getServerTimeZones(request);
        return getResponseUtils().parseGetServerTimeZonesResponse(response);
    }

    /**
     * The EmptyFolder operation empties folders in a mailbox. 
     * Optionally, this operation enables you to delete the subfolders of the specified folder. 
     * When a subfolder is deleted, the subfolder and the messages within the subfolder are deleted. 
     * 
     * *Note this method does not work for calendar or search folders: ERROR_CANNOT_EMPTY_FOLDER ... Emptying the calendar folder or search folder isn't permitted.
     * 
     * 
     * @param upn
     * @param folderId
     * @return
     */
    public boolean emptyFolder(String upn, boolean deleteSubFolders, DisposalType disposalType,
            BaseFolderIdType folderId) {
        EmptyFolder request = getRequestFactory().constructEmptyFolder(deleteSubFolders, disposalType,
                Collections.singleton(folderId));
        setContextCredentials(upn);
        EmptyFolderResponse response = getWebServices().emptyFolder(request);
        return getResponseUtils().parseEmptyFolderResponse(response);
    }

    /**
     * Deleting a calendarFolder with many (1k+) items is a problem.  You will always be throttled because the FindItemCount is 1000 and not configurable in Exchange Online.
     * More info on throttling http://msdn.microsoft.com/en-us/library/office/jj945066(v=exchg.150).aspx
     * 
     * This method will never attempt to delete more than 500 items at once.
     * 
     * @param upn
     * @param folderId
     * @return
     */
    public boolean emptyCalendarFolder(String upn, FolderIdType folderId) {
        Integer deleteRequestCount = 1;
        Set<ItemIdType> itemIds = findindFirstItemIdSet(upn, Collections.singleton(folderId));
        while (!itemIds.isEmpty()) {
            List<ItemIdType> itemIdList = new ArrayList<ItemIdType>(itemIds);
            if (itemIdList.size() > 500) {
                itemIdList = itemIdList.subList(0, 500);
            }
            StopWatch stopWatch = new StopWatch();
            stopWatch.start();
            log.info("emptyCalendarFolder(upn=" + upn + ") #" + deleteRequestCount + " deleting "
                    + itemIdList.size() + " calendar items");
            boolean result = deleteCalendarItems(upn, itemIdList);
            log.info("emptyCalendarFolder(upn=" + upn + ") #" + deleteRequestCount + " "
                    + (result ? "Success" : "Failure") + " in " + stopWatch);
            itemIds = findindFirstItemIdSet(upn, Collections.singleton(folderId));
            deleteRequestCount++;
        }
        return true;
    }

    public boolean deleteCalendarFolder(String upn, FolderIdType folderId) {
        boolean empty = emptyCalendarFolder(upn, folderId);
        if (empty) {
            return deleteFolder(upn, DisposalType.SOFT_DELETE, folderId);
        }
        return false;
    }

    public boolean deleteFolder(String upn, DisposalType disposalType, BaseFolderIdType folderId) {
        DeleteFolder request = getRequestFactory().constructDeleteFolder(folderId, disposalType);
        setContextCredentials(upn);
        DeleteFolderResponse response = getWebServices().deleteFolder(request);
        return getResponseUtils().parseDeleteFolderResponse(response);

    }
}