All Downloads are FREE. Search and download functionalities are using the official Maven repository.

microsoft.exchange.webservices.data.core.ExchangeService Maven / Gradle / Ivy

/*
 * The MIT License
 * Copyright (c) 2012 Microsoft Corporation
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package microsoft.exchange.webservices.data.core;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.EnumSet;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;

import microsoft.exchange.webservices.data.autodiscover.AutodiscoverService;
import microsoft.exchange.webservices.data.autodiscover.IAutodiscoverRedirectionUrl;
import microsoft.exchange.webservices.data.autodiscover.enumeration.UserSettingName;
import microsoft.exchange.webservices.data.autodiscover.exception.AutodiscoverLocalException;
import microsoft.exchange.webservices.data.autodiscover.request.ApplyConversationActionRequest;
import microsoft.exchange.webservices.data.autodiscover.response.GetUserSettingsResponse;
import microsoft.exchange.webservices.data.core.enumeration.availability.AvailabilityData;
import microsoft.exchange.webservices.data.core.enumeration.misc.ConversationActionType;
import microsoft.exchange.webservices.data.core.enumeration.misc.DateTimePrecision;
import microsoft.exchange.webservices.data.core.enumeration.misc.ExchangeVersion;
import microsoft.exchange.webservices.data.core.enumeration.misc.IdFormat;
import microsoft.exchange.webservices.data.core.enumeration.misc.TraceFlags;
import microsoft.exchange.webservices.data.core.enumeration.misc.UserConfigurationProperties;
import microsoft.exchange.webservices.data.core.enumeration.notification.EventType;
import microsoft.exchange.webservices.data.core.enumeration.property.BodyType;
import microsoft.exchange.webservices.data.core.enumeration.property.WellKnownFolderName;
import microsoft.exchange.webservices.data.core.enumeration.search.ResolveNameSearchLocation;
import microsoft.exchange.webservices.data.core.enumeration.service.ConflictResolutionMode;
import microsoft.exchange.webservices.data.core.enumeration.service.DeleteMode;
import microsoft.exchange.webservices.data.core.enumeration.service.MeetingRequestsDeliveryScope;
import microsoft.exchange.webservices.data.core.enumeration.service.MessageDisposition;
import microsoft.exchange.webservices.data.core.enumeration.service.SendCancellationsMode;
import microsoft.exchange.webservices.data.core.enumeration.service.SendInvitationsMode;
import microsoft.exchange.webservices.data.core.enumeration.service.SendInvitationsOrCancellationsMode;
import microsoft.exchange.webservices.data.core.enumeration.service.SyncFolderItemsScope;
import microsoft.exchange.webservices.data.core.enumeration.service.calendar.AffectedTaskOccurrence;
import microsoft.exchange.webservices.data.core.enumeration.service.error.ServiceErrorHandling;
import microsoft.exchange.webservices.data.core.exception.misc.ArgumentOutOfRangeException;
import microsoft.exchange.webservices.data.core.exception.service.local.ServiceLocalException;
import microsoft.exchange.webservices.data.core.exception.service.local.ServiceValidationException;
import microsoft.exchange.webservices.data.core.exception.service.remote.AccountIsLockedException;
import microsoft.exchange.webservices.data.core.exception.service.remote.ServiceRemoteException;
import microsoft.exchange.webservices.data.core.exception.service.remote.ServiceResponseException;
import microsoft.exchange.webservices.data.core.request.*;
import microsoft.exchange.webservices.data.core.response.*;
import microsoft.exchange.webservices.data.core.service.ServiceObject;
import microsoft.exchange.webservices.data.core.service.folder.Folder;
import microsoft.exchange.webservices.data.core.service.item.*;
import microsoft.exchange.webservices.data.messaging.UnifiedMessaging;
import microsoft.exchange.webservices.data.misc.AsyncCallback;
import microsoft.exchange.webservices.data.misc.AsyncRequestResult;
import microsoft.exchange.webservices.data.misc.ConversationAction;
import microsoft.exchange.webservices.data.misc.DelegateInformation;
import microsoft.exchange.webservices.data.misc.ExpandGroupResults;
import microsoft.exchange.webservices.data.misc.FolderIdWrapper;
import microsoft.exchange.webservices.data.misc.IAsyncResult;
import microsoft.exchange.webservices.data.misc.ImpersonatedUserId;
import microsoft.exchange.webservices.data.misc.NameResolutionCollection;
import microsoft.exchange.webservices.data.misc.OutParam;
import microsoft.exchange.webservices.data.misc.UserConfiguration;
import microsoft.exchange.webservices.data.misc.availability.AttendeeInfo;
import microsoft.exchange.webservices.data.misc.availability.AvailabilityOptions;
import microsoft.exchange.webservices.data.misc.availability.GetUserAvailabilityResults;
import microsoft.exchange.webservices.data.misc.availability.TimeWindow;
import microsoft.exchange.webservices.data.misc.id.AlternateIdBase;
import microsoft.exchange.webservices.data.notification.GetEventsResults;
import microsoft.exchange.webservices.data.notification.PullSubscription;
import microsoft.exchange.webservices.data.notification.PushSubscription;
import microsoft.exchange.webservices.data.notification.StreamingSubscription;
import microsoft.exchange.webservices.data.property.complex.Attachment;
import microsoft.exchange.webservices.data.property.complex.ConversationId;
import microsoft.exchange.webservices.data.property.complex.DelegateUser;
import microsoft.exchange.webservices.data.property.complex.EmailAddress;
import microsoft.exchange.webservices.data.property.complex.EmailAddressCollection;
import microsoft.exchange.webservices.data.property.complex.FolderId;
import microsoft.exchange.webservices.data.property.complex.ItemId;
import microsoft.exchange.webservices.data.property.complex.Mailbox;
import microsoft.exchange.webservices.data.property.complex.RuleCollection;
import microsoft.exchange.webservices.data.property.complex.RuleOperation;
import microsoft.exchange.webservices.data.property.complex.StringList;
import microsoft.exchange.webservices.data.property.complex.UserId;
import microsoft.exchange.webservices.data.property.complex.availability.OofSettings;
import microsoft.exchange.webservices.data.property.complex.time.TimeZoneDefinition;
import microsoft.exchange.webservices.data.property.definition.PropertyDefinitionBase;
import microsoft.exchange.webservices.data.search.CalendarView;
import microsoft.exchange.webservices.data.search.ConversationIndexedItemView;
import microsoft.exchange.webservices.data.search.FindFoldersResults;
import microsoft.exchange.webservices.data.search.FindItemsResults;
import microsoft.exchange.webservices.data.search.FolderView;
import microsoft.exchange.webservices.data.search.GroupedFindItemsResults;
import microsoft.exchange.webservices.data.search.Grouping;
import microsoft.exchange.webservices.data.search.ItemView;
import microsoft.exchange.webservices.data.search.ViewBase;
import microsoft.exchange.webservices.data.search.filter.SearchFilter;
import microsoft.exchange.webservices.data.sync.ChangeCollection;
import microsoft.exchange.webservices.data.sync.FolderChange;
import microsoft.exchange.webservices.data.sync.ItemChange;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.w3c.dom.Document;
import org.w3c.dom.Node;

/**
 * Represents a binding to the Exchange Web Services.
 */
public class ExchangeService extends ExchangeServiceBase implements IAutodiscoverRedirectionUrl {

  private static final Log LOG = LogFactory.getLog(ExchangeService.class);

  /**
   * The url.
   */
  private URI url;

  /**
   * The preferred culture.
   */
  private Locale preferredCulture;

  /**
   * The DateTimePrecision
   */
  private DateTimePrecision dateTimePrecision = DateTimePrecision.Default;

  /**
   * The impersonated user id.
   */
  private ImpersonatedUserId impersonatedUserId;
  // private Iterator Iterator;
  /**
   * The file attachment content handler.
   */
  private IFileAttachmentContentHandler fileAttachmentContentHandler;

  /**
   * The unified messaging.
   */
  private UnifiedMessaging unifiedMessaging;

  private boolean enableScpLookup = true;

  /**
   * When false, used to indicate that we should use "Exchange2007" as the server version String rather than
   * Exchange2007_SP1 (@see #getExchange2007CompatibilityMode).
   *
   */
  private boolean exchange2007CompatibilityMode = false;

  /**
   * Create response object.
   *
   * @param responseObject     the response object
   * @param parentFolderId     the parent folder id
   * @param messageDisposition the message disposition
   * @return The list of item created or modified as a result of the
   * "creation" of the response object.
   * @throws Exception the exception
   */
  public List internalCreateResponseObject(ServiceObject responseObject, FolderId parentFolderId,
      MessageDisposition messageDisposition) throws Exception {
    CreateResponseObjectRequest request = new CreateResponseObjectRequest(
        this, ServiceErrorHandling.ThrowOnError);
    Collection serviceList = new ArrayList();
    serviceList.add(responseObject);
    request.setParentFolderId(parentFolderId);
    request.setItems(serviceList);
    request.setMessageDisposition(messageDisposition);

    ServiceResponseCollection responses = request
        .execute();

    return responses.getResponseAtIndex(0).getItems();
  }

  /**
   * Creates a folder. Calling this method results in a call to EWS.
   *
   * @param folder         The folder.
   * @param parentFolderId The parent folder Id
   * @throws Exception the exception
   */
  public void createFolder(Folder folder, FolderId parentFolderId)
      throws Exception {
    CreateFolderRequest request = new CreateFolderRequest(this,
        ServiceErrorHandling.ThrowOnError);
    List folArry = new ArrayList();
    folArry.add(folder);
    request.setFolders(folArry);
    request.setParentFolderId(parentFolderId);

    request.execute();
  }

  /**
   * Updates a folder.
   *
   * @param folder The folder.
   * @throws Exception the exception
   */
  public void updateFolder(Folder folder) throws Exception {
    UpdateFolderRequest request = new UpdateFolderRequest(this,
        ServiceErrorHandling.ThrowOnError);

    request.getFolders().add(folder);

    request.execute();
  }

  /**
   * Copies a folder. Calling this method results in a call to EWS.
   *
   * @param folderId            The folderId.
   * @param destinationFolderId The destination folder id.
   * @return the folder
   * @throws Exception the exception
   */
  public Folder copyFolder(FolderId folderId, FolderId destinationFolderId)
      throws Exception {
    CopyFolderRequest request = new CopyFolderRequest(this,
        ServiceErrorHandling.ThrowOnError);

    request.setDestinationFolderId(destinationFolderId);
    request.getFolderIds().add(folderId);

    ServiceResponseCollection responses = request
        .execute();

    return responses.getResponseAtIndex(0).getFolder();
  }

  /**
   * Move a folder.
   *
   * @param folderId            The folderId.
   * @param destinationFolderId The destination folder id.
   * @return the folder
   * @throws Exception the exception
   */
  public Folder moveFolder(FolderId folderId, FolderId destinationFolderId)
      throws Exception {
    MoveFolderRequest request = new MoveFolderRequest(this,
        ServiceErrorHandling.ThrowOnError);

    request.setDestinationFolderId(destinationFolderId);
    request.getFolderIds().add(folderId);

    ServiceResponseCollection responses = request
        .execute();

    return responses.getResponseAtIndex(0).getFolder();
  }

  /**
   * Finds folder.
   *
   * @param parentFolderIds   The parent folder ids.
   * @param searchFilter      The search filter. Available search filter classes include
   *                          SearchFilter.IsEqualTo, SearchFilter.ContainsSubstring and
   *                          SearchFilter.SearchFilterCollection
   * @param view              The view controlling the number of folder returned.
   * @param errorHandlingMode Indicates the type of error handling should be done.
   * @return Collection of service response.
   * @throws Exception the exception
   */
  private ServiceResponseCollection internalFindFolders(
      Iterable parentFolderIds, SearchFilter searchFilter,
      FolderView view, ServiceErrorHandling errorHandlingMode)
      throws Exception {
    FindFolderRequest request = new FindFolderRequest(this,
        errorHandlingMode);

    request.getParentFolderIds().addRangeFolderId(parentFolderIds);
    request.setSearchFilter(searchFilter);
    request.setView(view);

    return request.execute();

  }

  /**
   * Obtains a list of folder by searching the sub-folder of the specified
   * folder.
   *
   * @param parentFolderId The Id of the folder in which to search for folder.
   * @param searchFilter   The search filter. Available search filter classes include
   *                       SearchFilter.IsEqualTo, SearchFilter.ContainsSubstring and
   *                       SearchFilter.SearchFilterCollection
   * @param view           The view controlling the number of folder returned.
   * @return An object representing the results of the search operation.
   * @throws Exception the exception
   */
  public FindFoldersResults findFolders(FolderId parentFolderId,
      SearchFilter searchFilter, FolderView view) throws Exception {
    EwsUtilities.validateParam(parentFolderId, "parentFolderId");
    EwsUtilities.validateParam(view, "view");
    EwsUtilities.validateParamAllowNull(searchFilter, "searchFilter");

    List folderIdArray = new ArrayList();
    folderIdArray.add(parentFolderId);
    ServiceResponseCollection responses = this
        .internalFindFolders(folderIdArray, searchFilter, view,
            ServiceErrorHandling.ThrowOnError);

    return responses.getResponseAtIndex(0).getResults();
  }

  /**
   * Obtains a list of folder by searching the sub-folder of the specified
   * folder.
   *
   * @param parentFolderId The Id of the folder in which to search for folder.
   * @param view           The view controlling the number of folder returned.
   * @return An object representing the results of the search operation.
   * @throws Exception the exception
   */
  public FindFoldersResults findFolders(FolderId parentFolderId,
      FolderView view) throws Exception {
    EwsUtilities.validateParam(parentFolderId, "parentFolderId");
    EwsUtilities.validateParam(view, "view");

    List folderIdArray = new ArrayList();
    folderIdArray.add(parentFolderId);

    ServiceResponseCollection responses = this
        .internalFindFolders(folderIdArray, null, /* searchFilter */
            view, ServiceErrorHandling.ThrowOnError);

    return responses.getResponseAtIndex(0).getResults();
  }

  /**
   * Obtains a list of folder by searching the sub-folder of the specified
   * folder.
   *
   * @param parentFolderName The name of the folder in which to search for folder.
   * @param searchFilter     The search filter. Available search filter classes include
   *                         SearchFilter.IsEqualTo, SearchFilter.ContainsSubstring and
   *                         SearchFilter.SearchFilterCollection
   * @param view             The view controlling the number of folder returned.
   * @return An object representing the results of the search operation.
   * @throws Exception the exception
   */
  public FindFoldersResults findFolders(WellKnownFolderName parentFolderName,
      SearchFilter searchFilter, FolderView view) throws Exception {
    return this.findFolders(new FolderId(parentFolderName), searchFilter,
        view);
  }

  /**
   * Obtains a list of folder by searching the sub-folder of the specified
   * folder.
   *
   * @param parentFolderName the parent folder name
   * @param view             the view
   * @return An object representing the results of the search operation.
   * @throws Exception the exception
   */
  public FindFoldersResults findFolders(WellKnownFolderName parentFolderName,
      FolderView view) throws Exception {
    return this.findFolders(new FolderId(parentFolderName), view);
  }

  /**
   * Load specified property for a folder.
   *
   * @param folder      The folder
   * @param propertySet The property set
   * @throws Exception the exception
   */
  public void loadPropertiesForFolder(Folder folder, PropertySet propertySet) throws Exception {
    EwsUtilities.validateParam(folder, "folder");
    EwsUtilities.validateParam(propertySet, "propertySet");

    GetFolderRequestForLoad request = new GetFolderRequestForLoad(this,
        ServiceErrorHandling.ThrowOnError);

    request.getFolderIds().add(folder);
    request.setPropertySet(propertySet);

    request.execute();
  }

  /**
   * Binds to a folder.
   *
   *
   * @param folderId    the folder id
   * @param propertySet the property set
   * @return Folder
   * @throws Exception the exception
   */
  public Folder bindToFolder(FolderId folderId, PropertySet propertySet)
      throws Exception {
    EwsUtilities.validateParam(folderId, "folderId");
    EwsUtilities.validateParam(propertySet, "propertySet");

    GetFolderRequest request = new GetFolderRequest(this,
        ServiceErrorHandling.ThrowOnError);

    request.getFolderIds().add(folderId);
    request.setPropertySet(propertySet);

    ServiceResponseCollection responses = request
        .execute();

    return responses.getResponseAtIndex(0).getFolder();

  }

  /**
   * Binds to folder.
   *
   * @param    The type of the folder.
   * @param cls         Folder class
   * @param folderId    The folder id.
   * @param propertySet The property set.
   * @return Folder
   * @throws Exception the exception
   */
  public  TFolder bindToFolder(Class cls, FolderId folderId,
      PropertySet propertySet) throws Exception {
    Folder result = this.bindToFolder(folderId, propertySet);

    if (cls.isAssignableFrom(result.getClass())) {
      return (TFolder) result;
    } else {
      throw new ServiceLocalException(String.format(
          "The folder type returned by the service (%s) isn't compatible with the requested folder type (%s).",
          result.getClass().getName(), cls.getName()));
    }
  }

  /**
   * Deletes a folder. Calling this method results in a call to EWS.
   *
   * @param folderId   The folder id
   * @param deleteMode The delete mode
   * @throws Exception the exception
   */
  public void deleteFolder(FolderId folderId, DeleteMode deleteMode)
      throws Exception {
    EwsUtilities.validateParam(folderId, "folderId");

    DeleteFolderRequest request = new DeleteFolderRequest(this,
        ServiceErrorHandling.ThrowOnError);

    request.getFolderIds().add(folderId);
    request.setDeleteMode(deleteMode);

    request.execute();
  }

  /**
   * Empties a folder. Calling this method results in a call to EWS.
   *
   * @param folderId         The folder id
   * @param deleteMode       The delete mode
   * @param deleteSubFolders if set to "true" empty folder should also delete sub folder.
   * @throws Exception the exception
   */
  public void emptyFolder(FolderId folderId, DeleteMode deleteMode, boolean deleteSubFolders) throws Exception {
    EwsUtilities.validateParam(folderId, "folderId");

    EmptyFolderRequest request = new EmptyFolderRequest(this,
        ServiceErrorHandling.ThrowOnError);

    request.getFolderIds().add(folderId);
    request.setDeleteMode(deleteMode);
    request.setDeleteSubFolders(deleteSubFolders);
    request.execute();
  }

  /**
   * Creates multiple item in a single EWS call. Supported item classes are
   * EmailMessage, Appointment, Contact, PostItem, Task and Item. CreateItems
   * does not support item that have unsaved attachments.
   *
   * @param items               the item
   * @param parentFolderId      the parent folder id
   * @param messageDisposition  the message disposition
   * @param sendInvitationsMode the send invitations mode
   * @param errorHandling       the error handling
   * @return A ServiceResponseCollection providing creation results for each
   * of the specified item.
   * @throws Exception the exception
   */
  private ServiceResponseCollection internalCreateItems(
      Collection items, FolderId parentFolderId,
      MessageDisposition messageDisposition,
      SendInvitationsMode sendInvitationsMode,
      ServiceErrorHandling errorHandling) throws Exception {
    CreateItemRequest request = new CreateItemRequest(this, errorHandling);
    request.setParentFolderId(parentFolderId);
    request.setItems(items);
    request.setMessageDisposition(messageDisposition);
    request.setSendInvitationsMode(sendInvitationsMode);
    return request.execute();
  }

  /**
   * Creates multiple item in a single EWS call. Supported item classes are
   * EmailMessage, Appointment, Contact, PostItem, Task and Item. CreateItems
   * does not support item that have unsaved attachments.
   *
   * @param items               the item
   * @param parentFolderId      the parent folder id
   * @param messageDisposition  the message disposition
   * @param sendInvitationsMode the send invitations mode
   * @return A ServiceResponseCollection providing creation results for each
   * of the specified item.
   * @throws Exception the exception
   */
  public ServiceResponseCollection createItems(
      Collection items, FolderId parentFolderId,
      MessageDisposition messageDisposition,
      SendInvitationsMode sendInvitationsMode) throws Exception {
    // All item have to be new.
    if (!EwsUtilities.trueForAll(items, new IPredicate() {
      @Override
      public boolean predicate(Item obj) throws ServiceLocalException {
        return obj.isNew();
      }
    })) {
      throw new ServiceValidationException(
          "This operation can't be performed because at least one item already has an ID.");
    }

    // E14:298274 Make sure that all item do *not* have unprocessed
    // attachments.
    if (!EwsUtilities.trueForAll(items, new IPredicate() {
      @Override
      public boolean predicate(Item obj) throws ServiceLocalException {
        return !obj.hasUnprocessedAttachmentChanges();
      }
    })) {
      throw new ServiceValidationException("This operation doesn't support item that have attachments.");
    }
    return this.internalCreateItems(items, parentFolderId,
        messageDisposition, sendInvitationsMode,
        ServiceErrorHandling.ReturnErrors);
  }

  /**
   * Creates an item. Calling this method results in a call to EWS.
   *
   * @param item                the item
   * @param parentFolderId      the parent folder id
   * @param messageDisposition  the message disposition
   * @param sendInvitationsMode the send invitations mode
   * @throws Exception the exception
   */
  public void createItem(Item item, FolderId parentFolderId, MessageDisposition messageDisposition,
      SendInvitationsMode sendInvitationsMode) throws Exception {
    ArrayList items = new ArrayList();
    items.add(item);
    internalCreateItems(items, parentFolderId, messageDisposition, sendInvitationsMode,
                        ServiceErrorHandling.ThrowOnError);
  }

  /**
   * Updates multiple item in a single EWS call. UpdateItems does not
   * support item that have unsaved attachments.
   *
   * @param items                              the item
   * @param savedItemsDestinationFolderId      the saved item destination folder id
   * @param conflictResolution                 the conflict resolution
   * @param messageDisposition                 the message disposition
   * @param sendInvitationsOrCancellationsMode the send invitations or cancellations mode
   * @param errorHandling                      the error handling
   * @return A ServiceResponseCollection providing update results for each of
   * the specified item.
   * @throws Exception the exception
   */
  private ServiceResponseCollection internalUpdateItems(
      Iterable items,
      FolderId savedItemsDestinationFolderId,
      ConflictResolutionMode conflictResolution,
      MessageDisposition messageDisposition,
      SendInvitationsOrCancellationsMode sendInvitationsOrCancellationsMode,
      ServiceErrorHandling errorHandling) throws Exception {
    UpdateItemRequest request = new UpdateItemRequest(this, errorHandling);

    request.getItems().addAll((Collection) items);
    request.setSavedItemsDestinationFolder(savedItemsDestinationFolderId);
    request.setMessageDisposition(messageDisposition);
    request.setConflictResolutionMode(conflictResolution);
    request
        .setSendInvitationsOrCancellationsMode(sendInvitationsOrCancellationsMode);

    return request.execute();
  }

  /**
   * Updates multiple item in a single EWS call. UpdateItems does not
   * support item that have unsaved attachments.
   *
   * @param items                              the item
   * @param savedItemsDestinationFolderId      the saved item destination folder id
   * @param conflictResolution                 the conflict resolution
   * @param messageDisposition                 the message disposition
   * @param sendInvitationsOrCancellationsMode the send invitations or cancellations mode
   * @return A ServiceResponseCollection providing update results for each of
   * the specified item.
   * @throws Exception the exception
   */
  public ServiceResponseCollection updateItems(
      Iterable items,
      FolderId savedItemsDestinationFolderId,
      ConflictResolutionMode conflictResolution,
      MessageDisposition messageDisposition,
      SendInvitationsOrCancellationsMode sendInvitationsOrCancellationsMode)
      throws Exception {

    // All item have to exist on the server (!new) and modified (dirty)
    if (!EwsUtilities.trueForAll(items, new IPredicate() {
      @Override
      public boolean predicate(Item obj) throws ServiceLocalException {
        return (!obj.isNew() && obj.isDirty());
      }
    })) {
      throw new ServiceValidationException(
          "This operation can't be performed because one or more item are new or unmodified.");
    }

    // E14:298274 Make sure that all item do *not* have unprocessed
    // attachments.
    if (!EwsUtilities.trueForAll(items, new IPredicate() {
      @Override
      public boolean predicate(Item obj) throws ServiceLocalException {
        return !obj.hasUnprocessedAttachmentChanges();
      }
    })) {
      throw new ServiceValidationException(
          "This operation can't be performed because attachments have been added or deleted for one or more item.");
    }

    return this.internalUpdateItems(items, savedItemsDestinationFolderId, conflictResolution,
                                    messageDisposition, sendInvitationsOrCancellationsMode,
                                    ServiceErrorHandling.ReturnErrors);
  }

  /**
   * Updates an item.
   *
   * @param item                               the item
   * @param savedItemsDestinationFolderId      the saved item destination folder id
   * @param conflictResolution                 the conflict resolution
   * @param messageDisposition                 the message disposition
   * @param sendInvitationsOrCancellationsMode the send invitations or cancellations mode
   * @return A ServiceResponseCollection providing deletion results for each
   * of the specified item Ids.
   * @throws Exception the exception
   */
  public Item updateItem(Item item, FolderId savedItemsDestinationFolderId,
      ConflictResolutionMode conflictResolution, MessageDisposition messageDisposition,
      SendInvitationsOrCancellationsMode sendInvitationsOrCancellationsMode)
      throws Exception {
    List itemIdArray = new ArrayList();
    itemIdArray.add(item);

    ServiceResponseCollection responses = this
        .internalUpdateItems(itemIdArray,
            savedItemsDestinationFolderId, conflictResolution,
            messageDisposition, sendInvitationsOrCancellationsMode,
            ServiceErrorHandling.ThrowOnError);

    return responses.getResponseAtIndex(0).getReturnedItem();
  }

  /**
   * Send item.
   *
   * @param item                         the item
   * @param savedCopyDestinationFolderId the saved copy destination folder id
   * @throws Exception the exception
   */
  public void sendItem(Item item, FolderId savedCopyDestinationFolderId)
      throws Exception {
    SendItemRequest request = new SendItemRequest(this,
        ServiceErrorHandling.ThrowOnError);

    List itemIdArray = new ArrayList();
    itemIdArray.add(item);

    request.setItems(itemIdArray);
    request.setSavedCopyDestinationFolderId(savedCopyDestinationFolderId);

    request.execute();
  }

  /**
   * Copies multiple item in a single call to EWS.
   *
   * @param itemIds             the item ids
   * @param destinationFolderId the destination folder id
   * @param returnNewItemIds    Flag indicating whether service should return new ItemIds or
   *                            not.
   * @param errorHandling       the error handling
   * @return A ServiceResponseCollection providing copy results for each of
   * the specified item Ids.
   * @throws Exception the exception
   */
  private ServiceResponseCollection internalCopyItems(
      Iterable itemIds, FolderId destinationFolderId,
      Boolean returnNewItemIds, ServiceErrorHandling errorHandling)
      throws Exception {
    CopyItemRequest request = new CopyItemRequest(this, errorHandling);
    request.getItemIds().addRange(itemIds);
    request.setDestinationFolderId(destinationFolderId);
    request.setReturnNewItemIds(returnNewItemIds);
    return request.execute();

  }

  /**
   * Copies multiple item in a single call to EWS.
   *
   * @param itemIds             the item ids
   * @param destinationFolderId the destination folder id
   * @return A ServiceResponseCollection providing copy results for each of
   * the specified item Ids.
   * @throws Exception the exception
   */
  public ServiceResponseCollection copyItems(
      Iterable itemIds, FolderId destinationFolderId)
      throws Exception {
    return this.internalCopyItems(itemIds, destinationFolderId, null,
        ServiceErrorHandling.ReturnErrors);
  }

  /**
   * Copies multiple item in a single call to EWS.
   *
   * @param itemIds             The Ids of the item to copy.
   * @param destinationFolderId The Id of the folder to copy the item to.
   * @param returnNewItemIds    Flag indicating whether service should return new ItemIds or
   *                            not.
   * @return A ServiceResponseCollection providing copy results for each of
   * the specified item Ids.
   * @throws Exception on error
   */
  public ServiceResponseCollection copyItems(
      Iterable itemIds, FolderId destinationFolderId,
      boolean returnNewItemIds) throws Exception {
    EwsUtilities.validateMethodVersion(this, ExchangeVersion.Exchange2010_SP1, "CopyItems");

    return this.internalCopyItems(itemIds, destinationFolderId, returnNewItemIds,
                                  ServiceErrorHandling.ReturnErrors);
  }

  /**
   * Copies an item. Calling this method results in a call to EWS.
   *
   * @param itemId              The Id of the item to copy.
   * @param destinationFolderId The folder in which to save sent messages, meeting invitations
   *                            or cancellations. If null, the message, meeting invitation or
   *                            cancellation is saved in the Sent Items folder
   * @return The copy of the item.
   * @throws Exception the exception
   */
  public Item copyItem(ItemId itemId, FolderId destinationFolderId)
      throws Exception {
    List itemIdArray = new ArrayList();
    itemIdArray.add(itemId);

    return this.internalCopyItems(itemIdArray, destinationFolderId, null,
        ServiceErrorHandling.ThrowOnError).getResponseAtIndex(0)
        .getItem();
  }

  /**
   * Moves multiple item in a single call to EWS.
   *
   * @param itemIds             the item ids
   * @param destinationFolderId the destination folder id
   * @param returnNewItemIds    Flag indicating whether service should return new ItemIds or
   *                            not.
   * @param errorHandling       the error handling
   * @return A ServiceResponseCollection providing copy results for each of
   * the specified item Ids.
   * @throws Exception the exception
   */
  private ServiceResponseCollection internalMoveItems(
      Iterable itemIds, FolderId destinationFolderId,
      Boolean returnNewItemIds, ServiceErrorHandling errorHandling)
      throws Exception {
    MoveItemRequest request = new MoveItemRequest(this, errorHandling);

    request.getItemIds().addRange(itemIds);
    request.setDestinationFolderId(destinationFolderId);
    request.setReturnNewItemIds(returnNewItemIds);
    return request.execute();
  }

  /**
   * Moves multiple item in a single call to EWS.
   *
   * @param itemIds             the item ids
   * @param destinationFolderId the destination folder id
   * @return A ServiceResponseCollection providing copy results for each of
   * the specified item Ids.
   * @throws Exception the exception
   */
  public ServiceResponseCollection moveItems(
      Iterable itemIds, FolderId destinationFolderId)
      throws Exception {
    return this.internalMoveItems(itemIds, destinationFolderId, null,
        ServiceErrorHandling.ReturnErrors);
  }

  /**
   * Moves multiple item in a single call to EWS.
   *
   * @param itemIds             The Ids of the item to move.
   * @param destinationFolderId The Id of the folder to move the item to.
   * @param returnNewItemIds    Flag indicating whether service should return new ItemIds or
   *                            not.
   * @return A ServiceResponseCollection providing copy results for each of
   * the specified item Ids.
   * @throws Exception on error
   */
  public ServiceResponseCollection moveItems(
      Iterable itemIds, FolderId destinationFolderId,
      boolean returnNewItemIds) throws Exception {
    EwsUtilities.validateMethodVersion(this, ExchangeVersion.Exchange2010_SP1, "MoveItems");

    return this.internalMoveItems(itemIds, destinationFolderId, returnNewItemIds,
                                  ServiceErrorHandling.ReturnErrors);
  }

  /**
   * Copies multiple item in a single call to EWS.
   *
   * @param itemId              the item id
   * @param destinationFolderId the destination folder id
   * @return A ServiceResponseCollection providing copy results for each of
   * the specified item Ids.
   * @throws Exception the exception
   */
  public Item moveItem(ItemId itemId, FolderId destinationFolderId)
      throws Exception {
    List itemIdArray = new ArrayList();
    itemIdArray.add(itemId);

    return this.internalMoveItems(itemIdArray, destinationFolderId, null,
        ServiceErrorHandling.ThrowOnError).getResponseAtIndex(0)
        .getItem();
  }

  /**
   * Finds item.
   *
   * @param            The type of item
   * @param parentFolderIds   The parent folder ids.
   * @param searchFilter      The search filter. Available search filter classes include
   *                          SearchFilter.IsEqualTo, SearchFilter.ContainsSubstring and
   *                          SearchFilter.SearchFilterCollection
   * @param queryString       the query string
   * @param view              The view controlling the number of folder returned.
   * @param groupBy           The group by.
   * @param errorHandlingMode Indicates the type of error handling should be done.
   * @return Service response collection.
   * @throws Exception the exception
   */
  public  ServiceResponseCollection> findItems(
      Iterable parentFolderIds, SearchFilter searchFilter, String queryString, ViewBase view,
      Grouping groupBy, ServiceErrorHandling errorHandlingMode) throws Exception {
    EwsUtilities.validateParamCollection(parentFolderIds.iterator(),
        "parentFolderIds");
    EwsUtilities.validateParam(view, "view");
    EwsUtilities.validateParamAllowNull(groupBy, "groupBy");
    EwsUtilities.validateParamAllowNull(queryString, "queryString");
    EwsUtilities.validateParamAllowNull(searchFilter, "searchFilter");

    FindItemRequest request = new FindItemRequest(this,
        errorHandlingMode);

    request.getParentFolderIds().addRangeFolderId(parentFolderIds);
    request.setSearchFilter(searchFilter);
    request.setQueryString(queryString);
    request.setView(view);
    request.setGroupBy(groupBy);

    return request.execute();
  }

  /**
   * Obtains a list of item by searching the contents of a specific folder.
   * Calling this method results in a call to EWS.
   *
   * @param parentFolderId the parent folder id
   * @param queryString    the query string
   * @param view           the view
   * @return An object representing the results of the search operation.
   * @throws Exception the exception
   */
  public FindItemsResults findItems(FolderId parentFolderId,
      String queryString, ItemView view) throws Exception {
    EwsUtilities.validateParamAllowNull(queryString, "queryString");

    List folderIdArray = new ArrayList();
    folderIdArray.add(parentFolderId);

    ServiceResponseCollection> responses = this
        .findItems(folderIdArray, null, /* searchFilter */
            queryString, view, null, /* groupBy */
            ServiceErrorHandling.ThrowOnError);

    return responses.getResponseAtIndex(0).getResults();
  }

  /**
   * Obtains a list of item by searching the contents of a specific folder.
   * Calling this method results in a call to EWS.
   *
   * @param parentFolderId the parent folder id
   * @param searchFilter   the search filter
   * @param view           the view
   * @return An object representing the results of the search operation.
   * @throws Exception the exception
   */
  public FindItemsResults findItems(FolderId parentFolderId,
      SearchFilter searchFilter, ItemView view) throws Exception {
    EwsUtilities.validateParamAllowNull(searchFilter, "searchFilter");
    List folderIdArray = new ArrayList();
    folderIdArray.add(parentFolderId);
    ServiceResponseCollection> responses = this
        .findItems(folderIdArray, searchFilter, null, /* queryString */
            view, null, /* groupBy */
            ServiceErrorHandling.ThrowOnError);

    return responses.getResponseAtIndex(0).getResults();
  }

  /**
   * Obtains a list of item by searching the contents of a specific folder.
   * Calling this method results in a call to EWS.
   *
   * @param parentFolderId the parent folder id
   * @param view           the view
   * @return An object representing the results of the search operation.
   * @throws Exception the exception
   */
  public FindItemsResults findItems(FolderId parentFolderId,
      ItemView view) throws Exception {
    List folderIdArray = new ArrayList();
    folderIdArray.add(parentFolderId);
    ServiceResponseCollection> responses = this
        .findItems(folderIdArray, null, /* searchFilter */
            null, /* queryString */
            view, null, /* groupBy */
            ServiceErrorHandling.ThrowOnError);

    return responses.getResponseAtIndex(0).getResults();
  }

  /**
   * Obtains a list of item by searching the contents of a specific folder.
   * Calling this method results in a call to EWS.
   *
   * @param parentFolderName the parent folder name
   * @param queryString      the query string
   * @param view             the view
   * @return An object representing the results of the search operation.
   * @throws Exception the exception
   */
  public FindItemsResults findItems(
      WellKnownFolderName parentFolderName, String queryString,
      ItemView view) throws Exception {
    return this
        .findItems(new FolderId(parentFolderName), queryString, view);
  }

  /**
   * Obtains a list of item by searching the contents of a specific folder.
   * Calling this method results in a call to EWS.
   *
   * @param parentFolderName the parent folder name
   * @param searchFilter     the search filter
   * @param view             the view
   * @return An object representing the results of the search operation.
   * @throws Exception the exception
   */
  public FindItemsResults findItems(
      WellKnownFolderName parentFolderName, SearchFilter searchFilter,
      ItemView view) throws Exception {
    return this.findItems(new FolderId(parentFolderName), searchFilter,
        view);
  }

  /**
   * Obtains a list of item by searching the contents of a specific folder.
   * Calling this method results in a call to EWS.
   *
   * @param parentFolderName the parent folder name
   * @param view             the view
   * @return An object representing the results of the search operation.
   * @throws Exception the exception
   */
  public FindItemsResults findItems(
      WellKnownFolderName parentFolderName, ItemView view)
      throws Exception {
    return this.findItems(new FolderId(parentFolderName), (SearchFilter) null, view);
  }

  /**
   * Obtains a grouped list of item by searching the contents of a specific
   * folder. Calling this method results in a call to EWS.
   *
   * @param parentFolderId the parent folder id
   * @param queryString    the query string
   * @param view           the view
   * @param groupBy        the group by
   * @return A list of item containing the contents of the specified folder.
   * @throws Exception the exception
   */
  public GroupedFindItemsResults findItems(FolderId parentFolderId,
      String queryString, ItemView view, Grouping groupBy)
      throws Exception {
    EwsUtilities.validateParam(groupBy, "groupBy");
    EwsUtilities.validateParamAllowNull(queryString, "queryString");

    List folderIdArray = new ArrayList();
    folderIdArray.add(parentFolderId);

    ServiceResponseCollection> responses = this
        .findItems(folderIdArray, null, /* searchFilter */
            queryString, view, groupBy, ServiceErrorHandling.ThrowOnError);

    return responses.getResponseAtIndex(0).getGroupedFindResults();
  }

  /**
   * Obtains a grouped list of item by searching the contents of a specific
   * folder. Calling this method results in a call to EWS.
   *
   * @param parentFolderId the parent folder id
   * @param searchFilter   the search filter
   * @param view           the view
   * @param groupBy        the group by
   * @return A list of item containing the contents of the specified folder.
   * @throws Exception the exception
   */
  public GroupedFindItemsResults findItems(FolderId parentFolderId,
      SearchFilter searchFilter, ItemView view, Grouping groupBy)
      throws Exception {
    EwsUtilities.validateParam(groupBy, "groupBy");
    EwsUtilities.validateParamAllowNull(searchFilter, "searchFilter");

    List folderIdArray = new ArrayList();
    folderIdArray.add(parentFolderId);

    ServiceResponseCollection> responses = this
        .findItems(folderIdArray, searchFilter, null, /* queryString */
            view, groupBy, ServiceErrorHandling.ThrowOnError);

    return responses.getResponseAtIndex(0).getGroupedFindResults();
  }

  /**
   * Obtains a grouped list of item by searching the contents of a specific
   * folder. Calling this method results in a call to EWS.
   *
   * @param parentFolderId the parent folder id
   * @param view           the view
   * @param groupBy        the group by
   * @return A list of item containing the contents of the specified folder.
   * @throws Exception the exception
   */
  public GroupedFindItemsResults findItems(FolderId parentFolderId,
      ItemView view, Grouping groupBy) throws Exception {
    EwsUtilities.validateParam(groupBy, "groupBy");

    List folderIdArray = new ArrayList();
    folderIdArray.add(parentFolderId);

    ServiceResponseCollection> responses = this
        .findItems(folderIdArray, null, /* searchFilter */
            null, /* queryString */
            view, groupBy, ServiceErrorHandling.ThrowOnError);

    return responses.getResponseAtIndex(0).getGroupedFindResults();
  }

  /**
   * Obtains a grouped list of item by searching the contents of a specific
   * folder. Calling this method results in a call to EWS.
   *
   * @param         the generic type
   * @param cls            the cls
   * @param parentFolderId the parent folder id
   * @param searchFilter   the search filter
   * @param view           the view
   * @param groupBy        the group by
   * @return A list of item containing the contents of the specified folder.
   * @throws Exception the exception
   */
  protected  ServiceResponseCollection> findItems(
      Class cls, FolderId parentFolderId,
      SearchFilter searchFilter, ViewBase view, Grouping groupBy)
      throws Exception {
    List folderIdArray = new ArrayList();
    folderIdArray.add(parentFolderId);

    return this.findItems(folderIdArray, searchFilter, null, /* queryString */
        view, groupBy, ServiceErrorHandling.ThrowOnError);
  }

  /**
   * Obtains a grouped list of item by searching the contents of a specific
   * folder. Calling this method results in a call to EWS.
   *
   * @param parentFolderName the parent folder name
   * @param queryString      the query string
   * @param view             the view
   * @param groupBy          the group by
   * @return A collection of grouped item containing the contents of the
   * specified.
   * @throws Exception the exception
   */
  public GroupedFindItemsResults findItems(
      WellKnownFolderName parentFolderName, String queryString,
      ItemView view, Grouping groupBy) throws Exception {
    EwsUtilities.validateParam(groupBy, "groupBy");
    return this.findItems(new FolderId(parentFolderName), queryString,
        view, groupBy);
  }

  /**
   * Obtains a grouped list of item by searching the contents of a specific
   * folder. Calling this method results in a call to EWS.
   *
   * @param parentFolderName the parent folder name
   * @param searchFilter     the search filter
   * @param view             the view
   * @param groupBy          the group by
   * @return A collection of grouped item containing the contents of the
   * specified.
   * @throws Exception the exception
   */
  public GroupedFindItemsResults findItems(
      WellKnownFolderName parentFolderName, SearchFilter searchFilter,
      ItemView view, Grouping groupBy) throws Exception {
    return this.findItems(new FolderId(parentFolderName), searchFilter, view, groupBy);
  }

  /**
   * Obtains a list of appointments by searching the contents of a specific
   * folder. Calling this method results in a call to EWS.
   *
   * @param parentFolderId the parent folder id
   * @param calendarView   the calendar view
   * @return A collection of appointments representing the contents of the
   * specified folder.
   * @throws Exception the exception
   */
  public FindItemsResults findAppointments(
      FolderId parentFolderId, CalendarView calendarView)
      throws Exception {
    List folderIdArray = new ArrayList();
    folderIdArray.add(parentFolderId);

    ServiceResponseCollection> response = this
        .findItems(folderIdArray, null, /* searchFilter */
            null /* queryString */, calendarView, null, /* groupBy */
            ServiceErrorHandling.ThrowOnError);

    return response.getResponseAtIndex(0).getResults();
  }

  /**
   * Obtains a list of appointments by searching the contents of a specific
   * folder. Calling this method results in a call to EWS.
   *
   * @param parentFolderName the parent folder name
   * @param calendarView     the calendar view
   * @return A collection of appointments representing the contents of the
   * specified folder.
   * @throws Exception the exception
   */
  public FindItemsResults findAppointments(
      WellKnownFolderName parentFolderName, CalendarView calendarView)
      throws Exception {
    return this.findAppointments(new FolderId(parentFolderName), calendarView);
  }

  /**
   * Loads the property of multiple item in a single call to EWS.
   *
   * @param items       the item
   * @param propertySet the property set
   * @return A ServiceResponseCollection providing results for each of the
   * specified item.
   * @throws Exception the exception
   */
  public ServiceResponseCollection loadPropertiesForItems(
      Iterable items, PropertySet propertySet) throws Exception {
    EwsUtilities.validateParamCollection(items.iterator(), "item");
    EwsUtilities.validateParam(propertySet, "propertySet");

    return this.internalLoadPropertiesForItems(items, propertySet, ServiceErrorHandling.ReturnErrors);
  }

  /**
   * Loads the property of multiple item in a single call to EWS.
   *
   * @param items         the item
   * @param propertySet   the property set
   * @param errorHandling the error handling
   * @return A ServiceResponseCollection providing results for each of the
   * specified item.
   * @throws Exception the exception
   */
  public ServiceResponseCollection internalLoadPropertiesForItems(Iterable items,
      PropertySet propertySet, ServiceErrorHandling errorHandling) throws Exception {
    GetItemRequestForLoad request = new GetItemRequestForLoad(this,
        errorHandling);
    // return null;

    request.getItemIds().addRangeItem(items);
    request.setPropertySet(propertySet);

    return request.execute();
  }

  /**
   * Binds to multiple item in a single call to EWS.
   *
   * @param itemIds       the item ids
   * @param propertySet   the property set
   * @param errorHandling the error handling
   * @return A ServiceResponseCollection providing results for each of the
   * specified item Ids.
   * @throws Exception the exception
   */
  private ServiceResponseCollection internalBindToItems(
      Iterable itemIds, PropertySet propertySet,
      ServiceErrorHandling errorHandling) throws Exception {
    GetItemRequest request = new GetItemRequest(this, errorHandling);
    request.getItemIds().addRange(itemIds);
    request.setPropertySet(propertySet);
    return request.execute();
  }

  /**
   * Binds to multiple item in a single call to EWS.
   *
   * @param itemIds     the item ids
   * @param propertySet the property set
   * @return A ServiceResponseCollection providing results for each of the
   * specified item Ids.
   * @throws Exception the exception
   */
  public ServiceResponseCollection bindToItems(
      Iterable itemIds, PropertySet propertySet) throws Exception {
    EwsUtilities.validateParamCollection(itemIds.iterator(), "itemIds");
    EwsUtilities.validateParam(propertySet, "propertySet");

    return this.internalBindToItems(itemIds, propertySet, ServiceErrorHandling.ReturnErrors);
  }

  /**
   * Binds to multiple item in a single call to EWS.
   *
   * @param itemId      the item id
   * @param propertySet the property set
   * @return A ServiceResponseCollection providing results for each of the
   * specified item Ids.
   * @throws Exception the exception
   */
  public Item bindToItem(ItemId itemId, PropertySet propertySet)
      throws Exception {
    EwsUtilities.validateParam(itemId, "itemId");
    EwsUtilities.validateParam(propertySet, "propertySet");
    List itmLst = new ArrayList();
    itmLst.add(itemId);
    ServiceResponseCollection responses = this
        .internalBindToItems(itmLst, propertySet, ServiceErrorHandling.ThrowOnError);

    return responses.getResponseAtIndex(0).getItem();
  }

  /**
   * Bind to item.
   *
   * @param      The type of the item.
   * @param c           the c
   * @param itemId      the item id
   * @param propertySet the property set
   * @return the t item
   * @throws Exception the exception
   */
  public  TItem bindToItem(Class c, ItemId itemId, PropertySet propertySet) throws Exception {
    Item result = this.bindToItem(itemId, propertySet);
    if (c.isAssignableFrom(result.getClass())) {
      return (TItem) result;
    } else {
      throw new ServiceLocalException(String.format(
          "The item type returned by the service (%s) isn't compatible with the requested item type (%s).", result.getClass().getName(),
          c.getName()));
    }
  }

  /**
   * Deletes multiple item in a single call to EWS.
   *
   * @param itemIds                 the item ids
   * @param deleteMode              the delete mode
   * @param sendCancellationsMode   the send cancellations mode
   * @param affectedTaskOccurrences the affected task occurrences
   * @param errorHandling           the error handling
   * @return A ServiceResponseCollection providing deletion results for each
   * of the specified item Ids.
   * @throws Exception the exception
   */
  private ServiceResponseCollection internalDeleteItems(
      Iterable itemIds, DeleteMode deleteMode,
      SendCancellationsMode sendCancellationsMode,
      AffectedTaskOccurrence affectedTaskOccurrences,
      ServiceErrorHandling errorHandling) throws Exception {
    DeleteItemRequest request = new DeleteItemRequest(this, errorHandling);

    request.getItemIds().addRange(itemIds);
    request.setDeleteMode(deleteMode);
    request.setSendCancellationsMode(sendCancellationsMode);
    request.setAffectedTaskOccurrences(affectedTaskOccurrences);

    return request.execute();
  }

  /**
   * Deletes multiple item in a single call to EWS.
   *
   * @param itemIds                 the item ids
   * @param deleteMode              the delete mode
   * @param sendCancellationsMode   the send cancellations mode
   * @param affectedTaskOccurrences the affected task occurrences
   * @return A ServiceResponseCollection providing deletion results for each
   * of the specified item Ids.
   * @throws Exception the exception
   */
  public ServiceResponseCollection deleteItems(
      Iterable itemIds, DeleteMode deleteMode,
      SendCancellationsMode sendCancellationsMode,
      AffectedTaskOccurrence affectedTaskOccurrences) throws Exception {
    EwsUtilities.validateParamCollection(itemIds.iterator(), "itemIds");

    return this.internalDeleteItems(itemIds, deleteMode,
        sendCancellationsMode, affectedTaskOccurrences,
        ServiceErrorHandling.ReturnErrors);
  }

  /**
   * Deletes an item. Calling this method results in a call to EWS.
   *
   * @param itemId                  the item id
   * @param deleteMode              the delete mode
   * @param sendCancellationsMode   the send cancellations mode
   * @param affectedTaskOccurrences the affected task occurrences
   * @throws Exception the exception
   */
  public void deleteItem(ItemId itemId, DeleteMode deleteMode, SendCancellationsMode sendCancellationsMode,
      AffectedTaskOccurrence affectedTaskOccurrences) throws Exception {
    List itemIdArray = new ArrayList();
    itemIdArray.add(itemId);

    EwsUtilities.validateParam(itemId, "itemId");
    this.internalDeleteItems(itemIdArray, deleteMode,
        sendCancellationsMode, affectedTaskOccurrences,
        ServiceErrorHandling.ThrowOnError);
  }

  /**
   * Gets an attachment.
   *
   * @param attachments          the attachments
   * @param bodyType             the body type
   * @param additionalProperties the additional property
   * @param errorHandling        the error handling
   * @throws Exception the exception
   */
  private ServiceResponseCollection internalGetAttachments(
      Iterable attachments, BodyType bodyType,
      Iterable additionalProperties, ServiceErrorHandling errorHandling)
      throws Exception {
    GetAttachmentRequest request = new GetAttachmentRequest(this, errorHandling);

    Iterator it = attachments.iterator();
    while (it.hasNext()) {
      request.getAttachments().add(it.next());
    }
    request.setBodyType(bodyType);

    if (additionalProperties != null) {
      List propsArray = new ArrayList();
      for (PropertyDefinitionBase propertyDefinitionBase : additionalProperties) {
        propsArray.add(propertyDefinitionBase);
      }
      request.getAdditionalProperties().addAll(propsArray);
    }

    return request.execute();
  }

  /**
   * Gets attachments.
   *
   * @param attachments          the attachments
   * @param bodyType             the body type
   * @param additionalProperties the additional property
   * @return service response collection
   * @throws Exception on error
   */
  protected ServiceResponseCollection getAttachments(
      Attachment[] attachments, BodyType bodyType,
      Iterable additionalProperties)
      throws Exception {
    return this.internalGetAttachments(Arrays.asList(attachments), bodyType,
        additionalProperties, ServiceErrorHandling.ReturnErrors);
  }

  /**
   * Gets the attachment.
   *
   * @param attachment           the attachment
   * @param bodyType             the body type
   * @param additionalProperties the additional property
   * @throws Exception the exception
   */
  public void getAttachment(Attachment attachment, BodyType bodyType,
      Iterable additionalProperties)
      throws Exception {

    List attachmentArray = new ArrayList();
    attachmentArray.add(attachment);

    this.internalGetAttachments(attachmentArray, bodyType, additionalProperties,
                                ServiceErrorHandling.ThrowOnError);

  }

  /**
   * Creates attachments.
   *
   * @param parentItemId the parent item id
   * @param attachments  the attachments
   * @return Service response collection.
   * @throws ServiceResponseException the service response exception
   * @throws Exception                the exception
   */
  public ServiceResponseCollection createAttachments(String parentItemId,
      Iterable attachments)
      throws ServiceResponseException, Exception {
    CreateAttachmentRequest request = new CreateAttachmentRequest(this,
        ServiceErrorHandling.ReturnErrors);

    request.setParentItemId(parentItemId);
                /*
		 * if (null != attachments) { while (attachments.hasNext()) {
		 * request.getAttachments().add(attachments.next()); } }
		 */
    request.getAttachments().addAll(
        (Collection) attachments);

    return request.execute();
  }

  /**
   * Deletes attachments.
   *
   * @param attachments the attachments
   * @return the service response collection
   * @throws ServiceResponseException the service response exception
   * @throws Exception                the exception
   */
  public ServiceResponseCollection deleteAttachments(
      Iterable attachments) throws ServiceResponseException,
      Exception {
    DeleteAttachmentRequest request = new DeleteAttachmentRequest(this,
        ServiceErrorHandling.ReturnErrors);

    request.getAttachments().addAll(
        (Collection) attachments);

    return request.execute();
  }

  /**
   * Finds contacts in the user's Contacts folder and the Global Address
   * List (in that order) that have names that match the one passed as a
   * parameter. Calling this method results in a call to EWS.
   *
   * @param nameToResolve the name to resolve
   * @return A collection of name resolutions whose names match the one passed
   * as a parameter.
   * @throws Exception the exception
   */
  public NameResolutionCollection resolveName(String nameToResolve)
      throws Exception {
    return this.resolveName(nameToResolve, ResolveNameSearchLocation.ContactsThenDirectory, false);
  }

  /**
   * Finds contacts in the user's Contacts folder and the Global Address
   * List (in that order) that have names that match the one passed as a
   * parameter. Calling this method results in a call to EWS.
   *
   * @param nameToResolve        the name to resolve
   * @param parentFolderIds      the parent folder ids
   * @param searchScope          the search scope
   * @param returnContactDetails the return contact details
   * @return A collection of name resolutions whose names match the one passed
   * as a parameter.
   * @throws Exception the exception
   */
  public NameResolutionCollection resolveName(String nameToResolve,
      Iterable parentFolderIds,
      ResolveNameSearchLocation searchScope, boolean returnContactDetails)
      throws Exception {
    return resolveName(nameToResolve, parentFolderIds, searchScope, returnContactDetails, null);

  }

  /**
   * Finds contacts in the Global Address List and/or in specific contact
   * folder that have names that match the one passed as a parameter. Calling
   * this method results in a call to EWS.
   *
   * @param nameToResolve          The name to resolve.
   * @param parentFolderIds        The Ids of the contact folder in which to look for matching
   *                               contacts.
   * @param searchScope            The scope of the search.
   * @param returnContactDetails   Indicates whether full contact information should be returned
   *                               for each of the found contacts.
   * @param contactDataPropertySet The property set for the contact details
   * @return a collection of name resolutions whose names match the one passed as a parameter
   * @throws Exception on error
   */
  public NameResolutionCollection resolveName(String nameToResolve,
      Iterable parentFolderIds,
      ResolveNameSearchLocation searchScope,
      boolean returnContactDetails, PropertySet contactDataPropertySet)
      throws Exception {
    if (contactDataPropertySet != null) {
      EwsUtilities.validateMethodVersion(this,
          ExchangeVersion.Exchange2010_SP1, "ResolveName");
    }

    EwsUtilities.validateParam(nameToResolve, "nameToResolve");

    if (parentFolderIds != null) {
      EwsUtilities.validateParamCollection(parentFolderIds.iterator(),
          "parentFolderIds");
    }
    ResolveNamesRequest request = new ResolveNamesRequest(this);

    request.setNameToResolve(nameToResolve);
    request.setReturnFullContactData(returnContactDetails);
    request.getParentFolderIds().addRangeFolderId(parentFolderIds);
    request.setSearchLocation(searchScope);
    request.setContactDataPropertySet(contactDataPropertySet);

    return request.execute().getResponseAtIndex(0).getResolutions();
  }

  /**
   * Finds contacts in the Global Address List that have names that match the
   * one passed as a parameter. Calling this method results in a call to EWS.
   *
   * @param nameToResolve          The name to resolve.
   * @param searchScope            The scope of the search.
   * @param returnContactDetails   Indicates whether full contact information should be returned
   *                               for each of the found contacts.
   * @param contactDataPropertySet The property set for the contact details
   * @return A collection of name resolutions whose names match the one
   * passed as a parameter.
   * @throws Exception on error
   */
  public NameResolutionCollection resolveName(String nameToResolve,
      ResolveNameSearchLocation searchScope,
      boolean returnContactDetails, PropertySet contactDataPropertySet)
      throws Exception {
    return this.resolveName(nameToResolve, null, searchScope,
        returnContactDetails, contactDataPropertySet);
  }

  /**
   * Finds contacts in the user's Contacts folder and the Global Address
   * List (in that order) that have names that match the one passed as a
   * parameter. Calling this method results in a call to EWS.
   *
   * @param nameToResolve        the name to resolve
   * @param searchScope          the search scope
   * @param returnContactDetails the return contact details
   * @return A collection of name resolutions whose names match the one passed
   * as a parameter.
   * @throws Exception the exception
   */
  public NameResolutionCollection resolveName(String nameToResolve,
      ResolveNameSearchLocation searchScope, boolean returnContactDetails)
      throws Exception {
    return this.resolveName(nameToResolve, null, searchScope, returnContactDetails);
  }

  /**
   * Expands a group by retrieving a list of its members. Calling this
   * method results in a call to EWS.
   *
   * @param emailAddress the email address
   * @return URL of the Exchange Web Services.
   * @throws Exception the exception
   */
  public ExpandGroupResults expandGroup(EmailAddress emailAddress)
      throws Exception {
    EwsUtilities.validateParam(emailAddress, "emailAddress");
    ExpandGroupRequest request = new ExpandGroupRequest(this);
    request.setEmailAddress(emailAddress);
    return request.execute().getResponseAtIndex(0).getMembers();
  }

  /**
   * Expands a group by retrieving a list of its members. Calling this
   * method results in a call to EWS.
   *
   * @param groupId the group id
   * @return An ExpandGroupResults containing the members of the group.
   * @throws Exception the exception
   */
  public ExpandGroupResults expandGroup(ItemId groupId) throws Exception {
    EwsUtilities.validateParam(groupId, "groupId");
    EmailAddress emailAddress = new EmailAddress();
    emailAddress.setId(groupId);
    return this.expandGroup(emailAddress);
  }

  /**
   * Expands a group by retrieving a list of its members. Calling this
   * method results in a call to EWS.
   *
   * @param smtpAddress the smtp address
   * @return An ExpandGroupResults containing the members of the group.
   * @throws Exception the exception
   */
  public ExpandGroupResults expandGroup(String smtpAddress) throws Exception {
    EwsUtilities.validateParam(smtpAddress, "smtpAddress");
    return this.expandGroup(new EmailAddress(smtpAddress));
  }

  /**
   * Expands a group by retrieving a list of its members. Calling this
   * method results in a call to EWS.
   *
   * @param address     the address
   * @param routingType the routing type
   * @return An ExpandGroupResults containing the members of the group.
   * @throws Exception the exception
   */
  public ExpandGroupResults expandGroup(String address, String routingType)
      throws Exception {
    EwsUtilities.validateParam(address, "address");
    EwsUtilities.validateParam(routingType, "routingType");

    EmailAddress emailAddress = new EmailAddress(address);
    emailAddress.setRoutingType(routingType);
    return this.expandGroup(emailAddress);
  }

  /**
   * Get the password expiration date
   *
   * @param mailboxSmtpAddress The e-mail address of the user.
   * @return The password expiration date
   * @throws Exception on error
   */
  public Date getPasswordExpirationDate(String mailboxSmtpAddress) throws Exception {
    GetPasswordExpirationDateRequest request = new GetPasswordExpirationDateRequest(this);
    request.setMailboxSmtpAddress(mailboxSmtpAddress);

    return request.execute().getPasswordExpirationDate();
  }

  /**
   * Subscribes to pull notification. Calling this method results in a call
   * to EWS.
   *
   * @param folderIds  The Ids of the folder to subscribe to
   * @param timeout    The timeout, in minutes, after which the subscription expires.
   *                   Timeout must be between 1 and 1440.
   * @param watermark  An optional watermark representing a previously opened
   *                   subscription.
   * @param eventTypes The event types to subscribe to.
   * @return A PullSubscription representing the new subscription.
   * @throws Exception on error
   */
  public PullSubscription subscribeToPullNotifications(
      Iterable folderIds, int timeout, String watermark,
      EventType... eventTypes) throws Exception {
    EwsUtilities.validateParamCollection(folderIds.iterator(), "folderIds");

    return this.buildSubscribeToPullNotificationsRequest(folderIds,
        timeout, watermark, eventTypes).execute().getResponseAtIndex(0)
        .getSubscription();
  }

  /**
   * Begins an asynchronous request to subscribes to pull notification.
   * Calling this method results in a call to EWS.
   *
   * @param callback   The AsyncCallback delegate.
   * @param state      An object that contains state information for this request.
   * @param folderIds  The Ids of the folder to subscribe to.
   * @param timeout    The timeout, in minutes, after which the subscription expires.
   *                   Timeout must be between 1 and 1440.
   * @param watermark  An optional watermark representing a previously opened
   *                   subscription.
   * @param eventTypes The event types to subscribe to.
   * @return An IAsyncResult that references the asynchronous request.
   * @throws Exception
   */
  public AsyncRequestResult beginSubscribeToPullNotifications(
      AsyncCallback callback, Object state, Iterable folderIds,
      int timeout, String watermark, EventType... eventTypes)
      throws Exception {
    EwsUtilities.validateParamCollection(folderIds.iterator(), "folderIds");

    return this.buildSubscribeToPullNotificationsRequest(folderIds, timeout, watermark,
                                                         eventTypes).beginExecute(callback);
  }

  /**
   * Subscribes to pull notification on all folder in the authenticated
   * user's mailbox. Calling this method results in a call to EWS.
   *
   * @param timeout    the timeout
   * @param watermark  the watermark
   * @param eventTypes the event types
   * @return A PullSubscription representing the new subscription.
   * @throws Exception the exception
   */
  public PullSubscription subscribeToPullNotificationsOnAllFolders(
      int timeout, String watermark, EventType... eventTypes)
      throws Exception {
    EwsUtilities.validateMethodVersion(this, ExchangeVersion.Exchange2010,
        "SubscribeToPullNotificationsOnAllFolders");

    return this.buildSubscribeToPullNotificationsRequest(null, timeout,
        watermark, eventTypes).execute().getResponseAtIndex(0)
        .getSubscription();
  }

  /**
   * Begins an asynchronous request to subscribe to pull notification on all
   * folder in the authenticated user's mailbox. Calling this method results
   * in a call to EWS.
   *
   * @param callback   The AsyncCallback delegate.
   * @param state      An object that contains state information for this request.
   * @param timeout    The timeout, in minutes, after which the subscription expires.
   *                   Timeout must be between 1 and 1440.
   * @param watermark  An optional watermark representing a previously opened
   *                   subscription.
   * @param eventTypes The event types to subscribe to.
   * @return An IAsyncResult that references the asynchronous request.
   * @throws Exception
   */
  public IAsyncResult beginSubscribeToPullNotificationsOnAllFolders(AsyncCallback callback, Object state,
      int timeout,
      String watermark, EventType... eventTypes) throws Exception {
    EwsUtilities.validateMethodVersion(this, ExchangeVersion.Exchange2010,
        "BeginSubscribeToPullNotificationsOnAllFolders");

    return this.buildSubscribeToPullNotificationsRequest(null, timeout, watermark, eventTypes).beginExecute(
        null);
  }

  /**
   * Ends an asynchronous request to subscribe to pull notification in the
   * authenticated user's mailbox.
   *
   * @param asyncResult An IAsyncResult that references the asynchronous request.
   * @return A PullSubscription representing the new subscription.
   * @throws Exception
   */
  public PullSubscription endSubscribeToPullNotifications(
      IAsyncResult asyncResult) throws Exception {
    SubscribeToPullNotificationsRequest request = AsyncRequestResult
        .extractServiceRequest(this, asyncResult);

    return request.endExecute(asyncResult).getResponseAtIndex(0)
        .getSubscription();
  }

  /**
   * Builds a request to subscribe to pull notification in the
   * authenticated user's mailbox.
   *
   * @param folderIds  The Ids of the folder to subscribe to.
   * @param timeout    The timeout, in minutes, after which the subscription expires.
   *                   Timeout must be between 1 and 1440
   * @param watermark  An optional watermark representing a previously opened
   *                   subscription
   * @param eventTypes The event types to subscribe to
   * @return A request to subscribe to pull notification in the authenticated
   * user's mailbox
   * @throws Exception the exception
   */
  private SubscribeToPullNotificationsRequest buildSubscribeToPullNotificationsRequest(
      Iterable folderIds, int timeout, String watermark,
      EventType... eventTypes) throws Exception {
    if (timeout < 1 || timeout > 1440) {
      throw new IllegalArgumentException("timeout", new Throwable(
          "Timeout must be a value between 1 and 1440."));
    }

    EwsUtilities.validateParamCollection(eventTypes, "eventTypes");

    SubscribeToPullNotificationsRequest request = new SubscribeToPullNotificationsRequest(
        this);

    if (folderIds != null) {
      request.getFolderIds().addRangeFolderId(folderIds);
    }

    request.setTimeOut(timeout);

    for (EventType event : eventTypes) {
      request.getEventTypes().add(event);
    }

    request.setWatermark(watermark);

    return request;
  }

  /**
   * Unsubscribes from a pull subscription. Calling this method results in a
   * call to EWS.
   *
   * @param subscriptionId the subscription id
   * @throws Exception the exception
   */
  public void unsubscribe(String subscriptionId) throws Exception {

    this.buildUnsubscribeRequest(subscriptionId).execute();
  }

  /**
   * Begins an asynchronous request to unsubscribe from a subscription.
   * Calling this method results in a call to EWS.
   *
   * @param callback       The AsyncCallback delegate.
   * @param state          An object that contains state information for this request.
   * @param subscriptionId The Id of the pull subscription to unsubscribe from.
   * @return An IAsyncResult that references the asynchronous request.
   * @throws Exception
   */
  public IAsyncResult beginUnsubscribe(AsyncCallback callback, Object state, String subscriptionId)
      throws Exception {
    return this.buildUnsubscribeRequest(subscriptionId).beginExecute(callback);
  }

  /**
   * Ends an asynchronous request to unsubscribe from a subscription.
   *
   * @param asyncResult An IAsyncResult that references the asynchronous request.
   * @throws Exception
   */
  public void endUnsubscribe(IAsyncResult asyncResult) throws Exception {
    UnsubscribeRequest request = AsyncRequestResult.extractServiceRequest(this, asyncResult);

    request.endExecute(asyncResult);
  }

  /**
   * Buids a request to unsubscribe from a subscription.
   *
   * @param subscriptionId The id of the subscription for which to get the events
   * @return A request to unsubscripbe from a subscription
   * @throws Exception
   */
  private UnsubscribeRequest buildUnsubscribeRequest(String subscriptionId)
      throws Exception {
    EwsUtilities.validateParam(subscriptionId, "subscriptionId");

    UnsubscribeRequest request = new UnsubscribeRequest(this);

    request.setSubscriptionId(subscriptionId);

    return request;
  }

  /**
   * Retrieves the latests events associated with a pull subscription.
   * Calling this method results in a call to EWS.
   *
   * @param subscriptionId the subscription id
   * @param waterMark      the water mark
   * @return A GetEventsResults containing a list of events associated with
   * the subscription.
   * @throws Exception the exception
   */
  public GetEventsResults getEvents(String subscriptionId, String waterMark)
      throws Exception {

    return this.buildGetEventsRequest(subscriptionId, waterMark).execute()
        .getResponseAtIndex(0).getResults();
  }

  /**
   * Begins an asynchronous request to retrieve the latest events associated
   * with a pull subscription. Calling this method results in a call to EWS.
   *
   * @param callback       The AsyncCallback delegate.
   * @param state          An object that contains state information for this request.
   * @param subscriptionId The id of the pull subscription for which to get the events
   * @param watermark      The watermark representing the point in time where to start
   *                       receiving events
   * @return An IAsynResult that references the asynchronous request
   * @throws Exception
   */
  public IAsyncResult beginGetEvents(AsyncCallback callback, Object state, String subscriptionId,
      String watermark) throws Exception {
    return this.buildGetEventsRequest(subscriptionId, watermark)
        .beginExecute(callback);
  }

  /**
   * Ends an asynchronous request to retrieve the latest events associated
   * with a pull subscription.
   *
   * @param asyncResult An IAsyncResult that references the asynchronous request.
   * @return A GetEventsResults containing a list of events associated with
   * the subscription.
   * @throws Exception
   */
  public GetEventsResults endGetEvents(IAsyncResult asyncResult) throws Exception {
    GetEventsRequest request = AsyncRequestResult.extractServiceRequest(this, asyncResult);

    return request.endExecute(asyncResult).getResponseAtIndex(0).getResults();
  }

  /**
   * Builds a request to retrieve the letest events associated with a pull
   * subscription
   *
   * @param subscriptionId The Id of the pull subscription for which to get the events
   * @param watermark      The watermark representing the point in time where to start
   *                       receiving events
   * @return An request to retrieve the latest events associated with a pull
   * subscription
   * @throws Exception
   */
  private GetEventsRequest buildGetEventsRequest(String subscriptionId,
      String watermark) throws Exception {
    EwsUtilities.validateParam(subscriptionId, "subscriptionId");
    EwsUtilities.validateParam(watermark, "watermark");

    GetEventsRequest request = new GetEventsRequest(this);

    request.setSubscriptionId(subscriptionId);
    request.setWatermark(watermark);

    return request;
  }

  /**
   * Subscribes to push notification. Calling this method results in a call
   * to EWS.
   *
   * @param folderIds  the folder ids
   * @param url        the url
   * @param frequency  the frequency
   * @param watermark  the watermark
   * @param eventTypes the event types
   * @return A PushSubscription representing the new subscription.
   * @throws Exception the exception
   */
  public PushSubscription subscribeToPushNotifications(
      Iterable folderIds, URI url, int frequency,
      String watermark, EventType... eventTypes) throws Exception {
    EwsUtilities.validateParamCollection(folderIds.iterator(), "folderIds");

    return this.buildSubscribeToPushNotificationsRequest(folderIds, url,
        frequency, watermark, eventTypes).execute().getResponseAtIndex(0).getSubscription();
  }

  /**
   * Begins an asynchronous request to subscribe to push notification.
   * Calling this method results in a call to EWS.
   *
   * @param callback   The asynccallback delegate
   * @param state      An object that contains state information for this request
   * @param folderIds  The ids of the folder to subscribe
   * @param url        the url of web service endpoint the exchange server should
   * @param frequency  the frequency,in minutes at which the exchange server should
   *                   contact the web Service endpoint. Frequency must be between 1
   *                   and 1440.
   * @param watermark  An optional watermark representing a previously opened
   *                   subscription
   * @param eventTypes The event types to subscribe to.
   * @return An IAsyncResult that references the asynchronous request.
   * @throws Exception
   */
  public IAsyncResult beginSubscribeToPushNotifications(
      AsyncCallback callback, Object state, Iterable folderIds,
      URI url, int frequency, String watermark, EventType... eventTypes)
      throws Exception {
    EwsUtilities.validateParamCollection(folderIds.iterator(), "folderIds");

    return this.buildSubscribeToPushNotificationsRequest(folderIds, url, frequency, watermark,
                                                         eventTypes).beginExecute(callback);
  }

  /**
   * Subscribes to push notification on all folder in the authenticated
   * user's mailbox. Calling this method results in a call to EWS.
   *
   * @param url        the url
   * @param frequency  the frequency
   * @param watermark  the watermark
   * @param eventTypes the event types
   * @return A PushSubscription representing the new subscription.
   * @throws Exception the exception
   */
  public PushSubscription subscribeToPushNotificationsOnAllFolders(URI url,
      int frequency, String watermark, EventType... eventTypes)
      throws Exception {
    EwsUtilities.validateMethodVersion(this, ExchangeVersion.Exchange2010,
        "SubscribeToPushNotificationsOnAllFolders");

    return this.buildSubscribeToPushNotificationsRequest(null, url,
        frequency, watermark, eventTypes).execute().getResponseAtIndex(0).getSubscription();
  }

  /**
   * Begins an asynchronous request to subscribe to push notification on all
   * folder in the authenticated user's mailbox. Calling this method results
   * in a call to EWS.
   *
   * @param callback   The asynccallback delegate
   * @param state      An object that contains state inforamtion for this request
   * @param url        the url
   * @param frequency  the frequency,in minutes at which the exchange server should
   *                   contact the web Service endpoint. Frequency must be between 1
   *                   and 1440.
   * @param watermark  An optional watermark representing a previously opened
   *                   subscription
   * @param eventTypes The event types to subscribe to.
   * @return An IAsyncResult that references the asynchronous request.
   * @throws Exception
   */
  public IAsyncResult beginSubscribeToPushNotificationsOnAllFolders(
      AsyncCallback callback, Object state, URI url, int frequency,
      String watermark, EventType... eventTypes) throws Exception {
    EwsUtilities.validateMethodVersion(this, ExchangeVersion.Exchange2010,
        "BeginSubscribeToPushNotificationsOnAllFolders");

    return this.buildSubscribeToPushNotificationsRequest(null, url, frequency, watermark,
                                                         eventTypes).beginExecute(callback);
  }


  /**
   * Ends an asynchronous request to subscribe to push notification in the
   * authenticated user's mailbox.
   *
   * @param asyncResult An IAsyncResult that references the asynchronous request.
   * @return A PushSubscription representing the new subscription
   * @throws Exception
   */
  public PushSubscription endSubscribeToPushNotifications(
      IAsyncResult asyncResult) throws Exception {
    SubscribeToPushNotificationsRequest request = AsyncRequestResult
        .extractServiceRequest(this, asyncResult);

    return request.endExecute(asyncResult).getResponseAtIndex(0)
        .getSubscription();
  }

  /**
   * Builds an request to request to subscribe to push notification in the
   * authenticated user's mailbox.
   *
   * @param folderIds  the folder ids
   * @param url        the url
   * @param frequency  the frequency
   * @param watermark  the watermark
   * @param eventTypes the event types
   * @return A request to request to subscribe to push notification in the
   * authenticated user's mailbox.
   * @throws Exception the exception
   */
  private SubscribeToPushNotificationsRequest buildSubscribeToPushNotificationsRequest(
      Iterable folderIds, URI url, int frequency,
      String watermark, EventType[] eventTypes) throws Exception {
    EwsUtilities.validateParam(url, "url");
    if (frequency < 1 || frequency > 1440) {
      throw new ArgumentOutOfRangeException("frequency", "The frequency must be a value between 1 and 1440.");
    }

    EwsUtilities.validateParamCollection(eventTypes, "eventTypes");
    SubscribeToPushNotificationsRequest request = new SubscribeToPushNotificationsRequest(this);

    if (folderIds != null) {
      request.getFolderIds().addRangeFolderId(folderIds);
    }

    request.setUrl(url);
    request.setFrequency(frequency);

    for (EventType event : eventTypes) {
      request.getEventTypes().add(event);
    }

    request.setWatermark(watermark);

    return request;
  }

  /**
   * Subscribes to streaming notification. Calling this method results in a
   * call to EWS.
   *
   * @param folderIds  The Ids of the folder to subscribe to.
   * @param eventTypes The event types to subscribe to.
   * @return A StreamingSubscription representing the new subscription
   * @throws Exception
   */
  public StreamingSubscription subscribeToStreamingNotifications(
      Iterable folderIds, EventType... eventTypes)
      throws Exception {
    EwsUtilities.validateMethodVersion(this,
        ExchangeVersion.Exchange2010_SP1,
        "SubscribeToStreamingNotifications");

    EwsUtilities.validateParamCollection(folderIds.iterator(), "folderIds");

    return this.buildSubscribeToStreamingNotificationsRequest(folderIds,
        eventTypes).execute().getResponseAtIndex(0).getSubscription();
  }

  /**
   * Subscribes to streaming notification on all folder in the authenticated
   * user's mailbox. Calling this method results in a call to EWS.
   *
   * @param eventTypes The event types to subscribe to.
   * @return A StreamingSubscription representing the new subscription.
   * @throws Exception
   */
  public StreamingSubscription subscribeToStreamingNotificationsOnAllFolders(
      EventType... eventTypes) throws Exception {
    EwsUtilities.validateMethodVersion(this, ExchangeVersion.Exchange2010_SP1,
                                       "SubscribeToStreamingNotificationsOnAllFolders");

    return this.buildSubscribeToStreamingNotificationsRequest(null,
        eventTypes).execute().getResponseAtIndex(0).getSubscription();
  }

  /**
   * Begins an asynchronous request to subscribe to streaming notification.
   * Calling this method results in a call to EWS.
   *
   * @param callback   The AsyncCallback delegate
   * @param state      An object that contains state information for this request.
   * @param folderIds  The Ids of the folder to subscribe to.
   * @param eventTypes The event types to subscribe to.
   * @return An IAsyncResult that references the asynchronous request
   * @throws Exception
   */
  public IAsyncResult beginSubscribeToStreamingNotifications(AsyncCallback callback, Object state,
      Iterable folderIds,
      EventType... eventTypes) throws Exception {
    EwsUtilities.validateMethodVersion(this,
        ExchangeVersion.Exchange2010_SP1,
        "BeginSubscribeToStreamingNotifications");

    EwsUtilities.validateParamCollection(folderIds.iterator(), "folderIds");

    return this.buildSubscribeToStreamingNotificationsRequest(folderIds,
        eventTypes).beginExecute(callback);
  }

  /**
   * Begins an asynchronous request to subscribe to streaming notification on
   * all folder in the authenticated user's mailbox. Calling this method
   * results in a call to EWS.
   *
   * @param callback The AsyncCallback delegate
   * @param state    An object that contains state information for this request.
   * @return An IAsyncResult that references the asynchronous request.
   * @throws Exception
   */
  public IAsyncResult beginSubscribeToStreamingNotificationsOnAllFolders(AsyncCallback callback, Object state,
      EventType... eventTypes) throws Exception {
    EwsUtilities.validateMethodVersion(this,
        ExchangeVersion.Exchange2010_SP1,
        "BeginSubscribeToStreamingNotificationsOnAllFolders");

    return this.buildSubscribeToStreamingNotificationsRequest(null,
        eventTypes).beginExecute(callback);
  }

  /**
   * Ends an asynchronous request to subscribe to push notification in the
   * authenticated user's mailbox.
   *
   * @param asyncResult An IAsyncResult that references the asynchronous request.
   * @return A streamingSubscription representing the new subscription
   * @throws Exception
   * @throws IndexOutOfBoundsException
   */
  public StreamingSubscription endSubscribeToStreamingNotifications(IAsyncResult asyncResult)
      throws IndexOutOfBoundsException, Exception {
    EwsUtilities.validateMethodVersion(
        this,
        ExchangeVersion.Exchange2010_SP1,
        "EndSubscribeToStreamingNotifications");

    SubscribeToStreamingNotificationsRequest request =
        AsyncRequestResult.extractServiceRequest(this, asyncResult);
    //   SubscribeToStreamingNotificationsRequest request = AsyncRequestResult.extractServiceRequest(this, asyncResult);
    return request.endExecute(asyncResult).getResponseAtIndex(0).getSubscription();
  }

  /**
   * Builds request to subscribe to streaming notification in the
   * authenticated user's mailbox.
   *
   * @param folderIds  The Ids of the folder to subscribe to.
   * @param eventTypes The event types to subscribe to.
   * @return A request to subscribe to streaming notification in the
   * authenticated user's mailbox
   * @throws Exception
   */
  private SubscribeToStreamingNotificationsRequest buildSubscribeToStreamingNotificationsRequest(
      Iterable folderIds, EventType[] eventTypes) throws Exception {
    EwsUtilities.validateParamCollection(eventTypes, "eventTypes");

    SubscribeToStreamingNotificationsRequest request = new SubscribeToStreamingNotificationsRequest(
        this);

    if (folderIds != null) {
      request.getFolderIds().addRangeFolderId(folderIds);
    }

    for (EventType event : eventTypes) {
      request.getEventTypes().add(event);
    }

    return request;
  }



  /**
   * Synchronizes the item of a specific folder. Calling this method
   * results in a call to EWS.
   *
   * @param syncFolderId       The Id of the folder containing the item to synchronize with.
   * @param propertySet        The set of property to retrieve for synchronized item.
   * @param ignoredItemIds     The optional list of item Ids that should be ignored.
   * @param maxChangesReturned The maximum number of changes that should be returned.
   * @param syncScope          The sync scope identifying item to include in the
   *                           ChangeCollection.
   * @param syncState          The optional sync state representing the point in time when to
   *                           start the synchronization.
   * @return A ChangeCollection containing a list of changes that occurred in
   * the specified folder.
   * @throws Exception the exception
   */
  public ChangeCollection syncFolderItems(FolderId syncFolderId,
      PropertySet propertySet, Iterable ignoredItemIds,
      int maxChangesReturned, SyncFolderItemsScope syncScope,
      String syncState) throws Exception {
    return this.buildSyncFolderItemsRequest(syncFolderId, propertySet,
        ignoredItemIds, maxChangesReturned, syncScope, syncState)
        .execute().getResponseAtIndex(0).getChanges();
  }

  /**
   * Begins an asynchronous request to synchronize the item of a specific
   * folder. Calling this method results in a call to EWS.
   *
   * @param callback           The AsyncCallback delegate
   * @param state              An object that contains state information for this request
   * @param syncFolderId       The Id of the folder containing the item to synchronize with
   * @param propertySet        The set of property to retrieve for synchronized item.
   * @param ignoredItemIds     The optional list of item Ids that should be ignored.
   * @param maxChangesReturned The maximum number of changes that should be returned.
   * @param syncScope          The sync scope identifying item to include in the
   *                           ChangeCollection
   * @param syncState          The optional sync state representing the point in time when to
   *                           start the synchronization
   * @return An IAsyncResult that references the asynchronous request.
   * @throws Exception
   */
  public IAsyncResult beginSyncFolderItems(AsyncCallback callback, Object state, FolderId syncFolderId,
      PropertySet propertySet,
      Iterable ignoredItemIds, int maxChangesReturned,
      SyncFolderItemsScope syncScope, String syncState) throws Exception {
    return this.buildSyncFolderItemsRequest(syncFolderId, propertySet,
        ignoredItemIds, maxChangesReturned, syncScope, syncState)
        .beginExecute(callback);
  }

  /**
   * Ends an asynchronous request to synchronize the item of a specific
   * folder.
   *
   * @param asyncResult An IAsyncResult that references the asynchronous request.
   * @return A ChangeCollection containing a list of changes that occurred in
   * the specified folder.
   * @throws Exception
   */
  public ChangeCollection endSyncFolderItems(IAsyncResult asyncResult) throws Exception {
    SyncFolderItemsRequest request = AsyncRequestResult.extractServiceRequest(this, asyncResult);

    return request.endExecute(asyncResult).getResponseAtIndex(0).getChanges();
  }

  /**
   * Builds a request to synchronize the item of a specific folder.
   *
   * @param syncFolderId       The Id of the folder containing the item to synchronize with
   * @param propertySet        The set of property to retrieve for synchronized item.
   * @param ignoredItemIds     The optional list of item Ids that should be ignored
   * @param maxChangesReturned The maximum number of changes that should be returned.
   * @param syncScope          The sync scope identifying item to include in the
   *                           ChangeCollection.
   * @param syncState          The optional sync state representing the point in time when to
   *                           start the synchronization.
   * @return A request to synchronize the item of a specific folder.
   * @throws Exception
   */
  private SyncFolderItemsRequest buildSyncFolderItemsRequest(
      FolderId syncFolderId, PropertySet propertySet,
      Iterable ignoredItemIds, int maxChangesReturned,
      SyncFolderItemsScope syncScope, String syncState) throws Exception {
    EwsUtilities.validateParam(syncFolderId, "syncFolderId");
    EwsUtilities.validateParam(propertySet, "propertySet");

    SyncFolderItemsRequest request = new SyncFolderItemsRequest(this);

    request.setSyncFolderId(syncFolderId);
    request.setPropertySet(propertySet);
    if (ignoredItemIds != null) {
      request.getIgnoredItemIds().addRange(ignoredItemIds);
    }
    request.setMaxChangesReturned(maxChangesReturned);
    request.setSyncScope(syncScope);
    request.setSyncState(syncState);

    return request;
  }

  /**
   * Synchronizes the sub-folder of a specific folder. Calling this method
   * results in a call to EWS.
   *
   * @param syncFolderId the sync folder id
   * @param propertySet  the property set
   * @param syncState    the sync state
   * @return A ChangeCollection containing a list of changes that occurred in
   * the specified folder.
   * @throws Exception the exception
   */
  public ChangeCollection syncFolderHierarchy(
      FolderId syncFolderId, PropertySet propertySet, String syncState)
      throws Exception {
    return this.buildSyncFolderHierarchyRequest(syncFolderId, propertySet,
        syncState).execute().getResponseAtIndex(0).getChanges();
  }

  /**
   * Begins an asynchronous request to synchronize the sub-folder of a
   * specific folder. Calling this method results in a call to EWS.
   *
   * @param callback     The AsyncCallback delegate
   * @param state        An object that contains state information for this request.
   * @param syncFolderId The Id of the folder containing the item to synchronize with.
   *                     A null value indicates the root folder of the mailbox.
   * @param propertySet  The set of property to retrieve for synchronized item.
   * @param syncState    The optional sync state representing the point in time when to
   *                     start the synchronization.
   * @return An IAsyncResult that references the asynchronous request
   * @throws Exception
   */
  public IAsyncResult beginSyncFolderHierarchy(AsyncCallback callback, Object state, FolderId syncFolderId,
      PropertySet propertySet,
      String syncState) throws Exception {
    return this.buildSyncFolderHierarchyRequest(syncFolderId, propertySet,
        syncState).beginExecute(callback);
  }

  /**
   * Synchronizes the entire folder hierarchy of the mailbox this Service is
   * connected to. Calling this method results in a call to EWS.
   *
   * @param propertySet The set of property to retrieve for synchronized item.
   * @param syncState   The optional sync state representing the point in time when to
   *                    start the synchronization.
   * @return A ChangeCollection containing a list of changes that occurred in
   * the specified folder.
   * @throws Exception
   */
  public ChangeCollection syncFolderHierarchy(
      PropertySet propertySet, String syncState)
      throws Exception {
    return this.syncFolderHierarchy(null, propertySet, syncState);
  }

	/*
	 * Begins an asynchronous request to synchronize the entire folder hierarchy
	 * of the mailbox this Service is connected to. Calling this method results
	 * in a call to EWS
	 * 
	 * @param callback
	 *            The AsyncCallback delegate
	 * @param state
	 *            An object that contains state information for this request.
	 * @param propertySet
	 *            The set of property to retrieve for synchronized item.
	 * @param syncState
	 *            The optional sync state representing the point in time when to
	 *            start the synchronization.
	 * @return An IAsyncResult that references the asynchronous request
	 * @throws Exception 
	public IAsyncResult beginSyncFolderHierarchy(FolderId syncFolderId, PropertySet propertySet, String syncState) throws Exception {
		return this.beginSyncFolderHierarchy(null,null, null,
				propertySet, syncState);
	}*/

  /**
   * Ends an asynchronous request to synchronize the specified folder
   * hierarchy of the mailbox this Service is connected to.
   *
   * @param asyncResult An IAsyncResult that references the asynchronous request.
   * @return A ChangeCollection containing a list of changes that occurred in
   * the specified folder.
   * @throws Exception
   */
  public ChangeCollection endSyncFolderHierarchy(IAsyncResult asyncResult) throws Exception {
    SyncFolderHierarchyRequest request = AsyncRequestResult.extractServiceRequest(this, asyncResult);

    return request.endExecute(asyncResult).getResponseAtIndex(0).getChanges();
  }

  /**
   * Builds a request to synchronize the specified folder hierarchy of the
   * mailbox this Service is connected to.
   *
   * @param syncFolderId The Id of the folder containing the item to synchronize with.
   *                     A null value indicates the root folder of the mailbox.
   * @param propertySet  The set of property to retrieve for synchronized item.
   * @param syncState    The optional sync state representing the point in time when to
   *                     start the synchronization.
   * @return A request to synchronize the specified folder hierarchy of the
   * mailbox this Service is connected to
   * @throws Exception
   */
  private SyncFolderHierarchyRequest buildSyncFolderHierarchyRequest(
      FolderId syncFolderId, PropertySet propertySet, String syncState)
      throws Exception {
    EwsUtilities.validateParamAllowNull(syncFolderId, "syncFolderId"); // Null
    // syncFolderId
    // is
    // allowed
    EwsUtilities.validateParam(propertySet, "propertySet");

    SyncFolderHierarchyRequest request = new SyncFolderHierarchyRequest(this);

    request.setPropertySet(propertySet);
    request.setSyncFolderId(syncFolderId);
    request.setSyncState(syncState);

    return request;
  }

  // Availability operations

  /**
   * Gets Out of Office (OOF) settings for a specific user. Calling this
   * method results in a call to EWS.
   *
   * @param smtpAddress the smtp address
   * @return An OofSettings instance containing OOF information for the
   * specified user.
   * @throws Exception the exception
   */
  public OofSettings getUserOofSettings(String smtpAddress) throws Exception {
    EwsUtilities.validateParam(smtpAddress, "smtpAddress");
    GetUserOofSettingsRequest request = new GetUserOofSettingsRequest(this);
    request.setSmtpAddress(smtpAddress);

    return request.execute().getOofSettings();
  }

  /**
   * Sets Out of Office (OOF) settings for a specific user. Calling this
   * method results in a call to EWS.
   *
   * @param smtpAddress the smtp address
   * @param oofSettings the oof settings
   * @throws Exception the exception
   */
  public void setUserOofSettings(String smtpAddress, OofSettings oofSettings)
      throws Exception {
    EwsUtilities.validateParam(smtpAddress, "smtpAddress");
    EwsUtilities.validateParam(oofSettings, "oofSettings");

    SetUserOofSettingsRequest request = new SetUserOofSettingsRequest(this);

    request.setSmtpAddress(smtpAddress);
    request.setOofSettings(oofSettings);

    request.execute();
  }

  /**
   * Gets detailed information about the availability of a set of users,
   * rooms, and resources within a specified time window.
   *
   * @param attendees     the attendees
   * @param timeWindow    the time window
   * @param requestedData the requested data
   * @param options       the options
   * @return The availability information for each user appears in a unique
   * FreeBusyResponse object. The order of users in the request
   * determines the order of availability data for each user in the
   * response.
   * @throws Exception the exception
   */
  public GetUserAvailabilityResults getUserAvailability(
      Iterable attendees, TimeWindow timeWindow,
      AvailabilityData requestedData, AvailabilityOptions options)
      throws Exception {
    EwsUtilities.validateParamCollection(attendees.iterator(), "attendees");
    EwsUtilities.validateParam(timeWindow, "timeWindow");
    EwsUtilities.validateParam(options, "options");

    GetUserAvailabilityRequest request = new GetUserAvailabilityRequest(this);

    request.setAttendees(attendees);
    request.setTimeWindow(timeWindow);
    request.setRequestedData(requestedData);
    request.setOptions(options);

    return request.execute();
  }

  /**
   * Gets detailed information about the availability of a set of users,
   * rooms, and resources within a specified time window.
   *
   * @param attendees     the attendees
   * @param timeWindow    the time window
   * @param requestedData the requested data
   * @return The availability information for each user appears in a unique
   * FreeBusyResponse object. The order of users in the request
   * determines the order of availability data for each user in the
   * response.
   * @throws Exception the exception
   */
  public GetUserAvailabilityResults getUserAvailability(
      Iterable attendees, TimeWindow timeWindow,
      AvailabilityData requestedData) throws Exception {
    return this.getUserAvailability(attendees, timeWindow, requestedData,
        new AvailabilityOptions());
  }

  /**
   * Retrieves a collection of all room lists in the organization.
   *
   * @return An EmailAddressCollection containing all the room lists in the
   * organization
   * @throws Exception the exception
   */
  public EmailAddressCollection getRoomLists() throws Exception {
    GetRoomListsRequest request = new GetRoomListsRequest(this);
    return request.execute().getRoomLists();
  }

  /**
   * Retrieves a collection of all room lists in the specified room list in
   * the organization.
   *
   * @param emailAddress the email address
   * @return A collection of EmailAddress objects representing all the rooms
   * within the specifed room list.
   * @throws Exception the exception
   */
  public Collection getRooms(EmailAddress emailAddress)
      throws Exception {
    EwsUtilities.validateParam(emailAddress, "emailAddress");
    GetRoomsRequest request = new GetRoomsRequest(this);
    request.setRoomList(emailAddress);

    return request.execute().getRooms();
  }

  // region Conversation

  /**
   * Retrieves a collection of all Conversations in the specified Folder.
   *
   * @param view     The view controlling the number of conversations returned.
   * @param filter   The search filter. Only search filter class supported
   *                 SearchFilter.IsEqualTo
   * @param folderId The Id of the folder in which to search for conversations.
   * @throws Exception
   */
  private Collection findConversation(
      ConversationIndexedItemView view, SearchFilter.IsEqualTo filter,
      FolderId folderId) throws Exception {
    EwsUtilities.validateParam(view, "view");
    EwsUtilities.validateParamAllowNull(filter, "filter");
    EwsUtilities.validateParam(folderId, "folderId");
    EwsUtilities.validateMethodVersion(this,
        ExchangeVersion.Exchange2010_SP1, "FindConversation");

    FindConversationRequest request = new FindConversationRequest(this);
    request.setIndexedItemView(view);
    request.setConversationViewFilter(filter);
    request.setFolderId(new FolderIdWrapper(folderId));

    return request.execute().getConversations();
  }

  /**
   * Retrieves a collection of all Conversations in the specified Folder.
   *
   * @param view     The view controlling the number of conversations returned.
   * @param folderId The Id of the folder in which to search for conversations.
   * @throws Exception
   */
  public Collection findConversation(
      ConversationIndexedItemView view, FolderId folderId)
      throws Exception {
    return this.findConversation(view, null, folderId);
  }

  /**
   * Applies ConversationAction on the specified conversation.
   *
   * @param actionType          ConversationAction
   * @param conversationIds     The conversation ids.
   * @param processRightAway    True to process at once . This is blocking and false to let
   *                            the Assitant process it in the back ground
   * @param categories          Catgories that need to be stamped can be null or empty
   * @param enableAlwaysDelete  True moves every current and future messages in the
   *                            conversation to deleted item folder. False stops the alwasy
   *                            delete action. This is applicable only if the action is
   *                            AlwaysDelete
   * @param destinationFolderId Applicable if the action is AlwaysMove. This moves every
   *                            current message and future message in the conversation to the
   *                            specified folder. Can be null if tis is then it stops the
   *                            always move action
   * @param errorHandlingMode   The error handling mode.
   * @throws Exception
   */
  private ServiceResponseCollection applyConversationAction(
      ConversationActionType actionType,
      Iterable conversationIds, boolean processRightAway,
      StringList categories, boolean enableAlwaysDelete,
      FolderId destinationFolderId, ServiceErrorHandling errorHandlingMode)
      throws Exception {
    EwsUtilities.ewsAssert(actionType == ConversationActionType.AlwaysCategorize
                           || actionType == ConversationActionType.AlwaysMove
                           || actionType == ConversationActionType.AlwaysDelete, "ApplyConversationAction",
                           "Invalic actionType");

    EwsUtilities.validateParam(conversationIds, "conversationId");
    EwsUtilities.validateMethodVersion(this,
        ExchangeVersion.Exchange2010_SP1, "ApplyConversationAction");

    ApplyConversationActionRequest request = new ApplyConversationActionRequest(
        this, errorHandlingMode);
    ConversationAction action = new ConversationAction();

    for (ConversationId conversationId : conversationIds) {
      action.setAction(actionType);
      action.setConversationId(conversationId);
      action.setProcessRightAway(processRightAway);
      action.setCategories(categories);
      action.setEnableAlwaysDelete(enableAlwaysDelete);
      action
          .setDestinationFolderId(destinationFolderId != null ? new FolderIdWrapper(
              destinationFolderId)
              : null);
      request.getConversationActions().add(action);
    }

    return request.execute();
  }

  /**
   * Applies one time conversation action on item in specified folder inside
   * the conversation.
   *
   * @param actionType          The action
   * @param idTimePairs         The id time pairs.
   * @param contextFolderId     The context folder id.
   * @param destinationFolderId The destination folder id.
   * @param deleteType          Type of the delete.
   * @param isRead              The is read.
   * @param errorHandlingMode   The error handling mode.
   * @throws Exception
   */
  private ServiceResponseCollection applyConversationOneTimeAction(
      ConversationActionType actionType,
      Iterable> idTimePairs,
      FolderId contextFolderId, FolderId destinationFolderId,
      DeleteMode deleteType, Boolean isRead,
      ServiceErrorHandling errorHandlingMode) throws Exception {
    EwsUtilities.ewsAssert(
        actionType == ConversationActionType.Move || actionType == ConversationActionType.Delete
        || actionType == ConversationActionType.SetReadState || actionType == ConversationActionType.Copy,
        "ApplyConversationOneTimeAction", "Invalid actionType");

    EwsUtilities.validateParamCollection(idTimePairs.iterator(),
        "idTimePairs");
    EwsUtilities.validateMethodVersion(this,
        ExchangeVersion.Exchange2010_SP1, "ApplyConversationAction");

    ApplyConversationActionRequest request = new ApplyConversationActionRequest(
        this, errorHandlingMode);

    for (HashMap idTimePair : idTimePairs) {
      ConversationAction action = new ConversationAction();

      action.setAction(actionType);
      action.setConversationId(idTimePair.keySet().iterator().next());
      action
          .setContextFolderId(contextFolderId != null ? new FolderIdWrapper(
              contextFolderId)
              : null);
      action
          .setDestinationFolderId(destinationFolderId != null ? new FolderIdWrapper(
              destinationFolderId)
              : null);
      action.setConversationLastSyncTime(idTimePair.values().iterator()
          .next());
      action.setIsRead(isRead);
      action.setDeleteType(deleteType);

      request.getConversationActions().add(action);
    }

    return request.execute();
  }

  /**
   * Sets up a conversation so that any item received within that conversation
   * is always categorized. Calling this method results in a call to EWS.
   *
   * @param conversationId       The id of the conversation.
   * @param categories           The categories that should be stamped on item in the
   *                             conversation.
   * @param processSynchronously Indicates whether the method should return only once enabling
   *                             this rule and stamping existing item in the conversation is
   *                             completely done. If processSynchronously is false, the method
   *                             returns immediately.
   * @throws Exception
   */
  public ServiceResponseCollection enableAlwaysCategorizeItemsInConversations(
      Iterable conversationId,
      Iterable categories, boolean processSynchronously)
      throws Exception {
    EwsUtilities.validateParamCollection(categories.iterator(),
        "categories");
    return this.applyConversationAction(
        ConversationActionType.AlwaysCategorize, conversationId,
        processSynchronously, new StringList(categories), false, null,
        ServiceErrorHandling.ReturnErrors);
  }

  /**
   * Sets up a conversation so that any item received within that conversation
   * is no longer categorized. Calling this method results in a call to EWS.
   *
   * @param conversationId       The id of the conversation.
   * @param processSynchronously Indicates whether the method should return only once enabling
   *                             this rule and stamping existing item in the conversation is
   *                             completely done. If processSynchronously is false, the method
   *                             returns immediately.
   * @throws Exception
   */
  public ServiceResponseCollection disableAlwaysCategorizeItemsInConversations(
      Iterable conversationId,
      boolean processSynchronously) throws Exception {
    return this.applyConversationAction(
        ConversationActionType.AlwaysCategorize, conversationId,
        processSynchronously, null, false, null,
        ServiceErrorHandling.ReturnErrors);
  }

  /**
   * Sets up a conversation so that any item received within that conversation
   * is always moved to Deleted Items folder. Calling this method results in a
   * call to EWS.
   *
   * @param conversationId       The id of the conversation.
   * @param processSynchronously Indicates whether the method should return only once enabling
   *                             this rule and stamping existing item in the conversation is
   *                             completely done. If processSynchronously is false, the method
   *                             returns immediately.
   * @throws Exception
   */
  public ServiceResponseCollection enableAlwaysDeleteItemsInConversations(
      Iterable conversationId,
      boolean processSynchronously) throws Exception {
    return this.applyConversationAction(
        ConversationActionType.AlwaysDelete, conversationId,
        processSynchronously, null, true, null,
        ServiceErrorHandling.ReturnErrors);
  }

  /**
   * Sets up a conversation so that any item received within that conversation
   * is no longer moved to Deleted Items folder. Calling this method results
   * in a call to EWS.
   *
   * @param conversationId       The id of the conversation.
   * @param processSynchronously Indicates whether the method should return only once enabling
   *                             this rule and stamping existing item in the conversation is
   *                             completely done. If processSynchronously is false, the method
   *                             returns immediately.
   * @throws Exception
   */
  public ServiceResponseCollection disableAlwaysDeleteItemsInConversations(
      Iterable conversationId,
      boolean processSynchronously) throws Exception {
    return this.applyConversationAction(
        ConversationActionType.AlwaysDelete, conversationId,
        processSynchronously, null, false, null,
        ServiceErrorHandling.ReturnErrors);
  }

  /**
   * Sets up a conversation so that any item received within that conversation
   * is always moved to a specific folder. Calling this method results in a
   * call to EWS.
   *
   * @param conversationId       The Id of the folder to which conversation item should be
   *                             moved.
   * @param destinationFolderId  The Id of the destination folder.
   * @param processSynchronously Indicates whether the method should return only once enabling
   *                             this rule and stamping existing item in the conversation is
   *                             completely done. If processSynchronously is false, the method
   *                             returns immediately.
   * @throws Exception
   */
  public ServiceResponseCollection enableAlwaysMoveItemsInConversations(
      Iterable conversationId,
      FolderId destinationFolderId, boolean processSynchronously)
      throws Exception {
    EwsUtilities.validateParam(destinationFolderId, "destinationFolderId");
    return this.applyConversationAction(ConversationActionType.AlwaysMove,
        conversationId, processSynchronously, null, false,
        destinationFolderId, ServiceErrorHandling.ReturnErrors);
  }

  /**
   * Sets up a conversation so that any item received within that conversation
   * is no longer moved to a specific folder. Calling this method results in a
   * call to EWS.
   *
   * @param conversationIds      The conversation ids.
   * @param processSynchronously Indicates whether the method should return only once disabling
   *                             this rule is completely done. If processSynchronously is
   *                             false, the method returns immediately.
   * @throws Exception
   */
  public ServiceResponseCollection disableAlwaysMoveItemsInConversations(
      Iterable conversationIds,
      boolean processSynchronously) throws Exception {
    return this.applyConversationAction(ConversationActionType.AlwaysMove,
        conversationIds, processSynchronously, null, false, null,
        ServiceErrorHandling.ReturnErrors);
  }

  /**
   * Moves the item in the specified conversation to the specified
   * destination folder. Calling this method results in a call to EWS.
   *
   * @param idLastSyncTimePairs The pairs of Id of conversation whose item should be moved
   *                            and the dateTime conversation was last synced (Items received
   *                            after that dateTime will not be moved).
   * @param contextFolderId     The Id of the folder that contains the conversation.
   * @param destinationFolderId The Id of the destination folder.
   * @throws Exception
   */
  public ServiceResponseCollection moveItemsInConversations(
      Iterable> idLastSyncTimePairs,
      FolderId contextFolderId, FolderId destinationFolderId)
      throws Exception {
    EwsUtilities.validateParam(destinationFolderId, "destinationFolderId");
    return this.applyConversationOneTimeAction(ConversationActionType.Move,
        idLastSyncTimePairs, contextFolderId, destinationFolderId,
        null, null, ServiceErrorHandling.ReturnErrors);
  }

  /**
   * Copies the item in the specified conversation to the specified
   * destination folder. Calling this method results in a call to EWS.
   *
   * @param idLastSyncTimePairs The pairs of Id of conversation whose item should be copied
   *                            and the dateTime conversation was last synced (Items received
   *                            after that dateTime will not be copied).
   * @param contextFolderId     The context folder id.
   * @param destinationFolderId The destination folder id.
   * @throws Exception
   */
  public ServiceResponseCollection copyItemsInConversations(
      Iterable> idLastSyncTimePairs,
      FolderId contextFolderId, FolderId destinationFolderId)
      throws Exception {
    EwsUtilities.validateParam(destinationFolderId, "destinationFolderId");
    return this.applyConversationOneTimeAction(ConversationActionType.Copy,
        idLastSyncTimePairs, contextFolderId, destinationFolderId,
        null, null, ServiceErrorHandling.ReturnErrors);
  }

  /**
   * Deletes the item in the specified conversation. Calling this method
   * results in a call to EWS.
   *
   * @param idLastSyncTimePairs The pairs of Id of conversation whose item should be deleted
   *                            and the date and time conversation was last synced (Items
   *                            received after that date will not be deleted). conversation
   *                            was last synced (Items received after that dateTime will not
   *                            be copied).
   * @param contextFolderId     The Id of the folder that contains the conversation.
   * @param deleteMode          The deletion mode
   * @throws Exception
   */
  public ServiceResponseCollection deleteItemsInConversations(
      Iterable> idLastSyncTimePairs,
      FolderId contextFolderId, DeleteMode deleteMode) throws Exception {
    return this.applyConversationOneTimeAction(
        ConversationActionType.Delete, idLastSyncTimePairs,
        contextFolderId, null, deleteMode, null,
        ServiceErrorHandling.ReturnErrors);
  }

  /**
   * Sets the read state for item in conversation. Calling this mehtod would
   * result in call to EWS.
   *
   * @param idLastSyncTimePairs The pairs of Id of conversation whose item should read state
   *                            set and the date and time conversation was last synced (Items
   *                            received after that date will not have their read state set).
   *                            was last synced (Items received after that date will not be
   *                            deleted). conversation was last synced (Items received after
   *                            that dateTime will not be copied).
   * @param contextFolderId     The Id of the folder that contains the conversation.
   * @param isRead              if set to true, conversation item are marked as read;
   *                            otherwise they are marked as unread.
   * @throws Exception
   */
  public ServiceResponseCollection setReadStateForItemsInConversations(
      Iterable> idLastSyncTimePairs,
      FolderId contextFolderId, boolean isRead) throws Exception {
    return this.applyConversationOneTimeAction(
        ConversationActionType.SetReadState, idLastSyncTimePairs,
        contextFolderId, null, null, isRead,
        ServiceErrorHandling.ReturnErrors);
  }

  private ServiceResponseCollection internalExportItems(
          Iterable itemIds, ServiceErrorHandling errorHandling) throws Exception {
    ExportItemsRequest request = new ExportItemsRequest(this, errorHandling);
    request.getItemIds().addRange(itemIds);
    return request.execute();
  }

  public ExportItemsResponse exportItem(ItemId itemId) throws Exception {
    EwsUtilities.validateParam(itemId, "itemId");
    ServiceResponseCollection responses = internalExportItems(Arrays.asList(itemId),
            ServiceErrorHandling.ThrowOnError);
    ExportItemsResponse response = responses.getResponseAtIndex(0);
    response.throwIfNecessary();
    return response;
  }

  public ServiceResponseCollection exportItems(
          Iterable itemIds) throws Exception {
    EwsUtilities.validateParamCollection(itemIds.iterator(), "itemIds");
    return this.internalExportItems(itemIds,
            ServiceErrorHandling.ReturnErrors);
  }

  private ServiceResponseCollection internalUploadItems(
          List items, ServiceErrorHandling errorHandling) throws Exception {
    UploadItemsRequest request = new UploadItemsRequest(this, errorHandling);
    request.setItems(items);
    return request.execute();
  }

  public UploadItemsResponse uploadItem(UploadItem item) throws Exception {
    EwsUtilities.validateParam(item, "item");
    ServiceResponseCollection responses = internalUploadItems(Arrays.asList(item),
            ServiceErrorHandling.ThrowOnError);
    UploadItemsResponse response = responses.getResponseAtIndex(0);
    response.throwIfNecessary();
    return response;
  }

  public ServiceResponseCollection uploadItems(
        List items) throws Exception {
    return this.internalUploadItems(items,
            ServiceErrorHandling.ReturnErrors);
  }
  // Id conversion operations

  /**
   * Converts multiple Ids from one format to another in a single call to
   * EWS.
   *
   * @param ids               the ids
   * @param destinationFormat the destination format
   * @param errorHandling     the error handling
   * @return A ServiceResponseCollection providing conversion results for each
   * specified Ids.
   * @throws Exception the exception
   */
  private ServiceResponseCollection internalConvertIds(
      Iterable ids, IdFormat destinationFormat,
      ServiceErrorHandling errorHandling) throws Exception {
    EwsUtilities.validateParamCollection(ids.iterator(), "ids");

    ConvertIdRequest request = new ConvertIdRequest(this, errorHandling);

    request.getIds().addAll((Collection) ids);
    request.setDestinationFormat(destinationFormat);

    return request.execute();
  }

  /**
   * Converts multiple Ids from one format to another in a single call to
   * EWS.
   *
   * @param ids               the ids
   * @param destinationFormat the destination format
   * @return A ServiceResponseCollection providing conversion results for each
   * specified Ids.
   * @throws Exception the exception
   */
  public ServiceResponseCollection convertIds(
      Iterable ids, IdFormat destinationFormat)
      throws Exception {
    EwsUtilities.validateParamCollection(ids.iterator(), "ids");

    return this.internalConvertIds(ids, destinationFormat,
        ServiceErrorHandling.ReturnErrors);
  }

  /**
   * Converts Id from one format to another in a single call to EWS.
   *
   * @param id                the id
   * @param destinationFormat the destination format
   * @return The converted Id.
   * @throws Exception the exception
   */
  public AlternateIdBase convertId(AlternateIdBase id,
      IdFormat destinationFormat) throws Exception {
    EwsUtilities.validateParam(id, "id");

    List alternateIdBaseArray = new ArrayList();
    alternateIdBaseArray.add(id);

    ServiceResponseCollection responses = this
        .internalConvertIds(alternateIdBaseArray, destinationFormat,
            ServiceErrorHandling.ThrowOnError);

    return responses.getResponseAtIndex(0).getConvertedId();
  }

  /**
   * Adds delegates to a specific mailbox. Calling this method results in a
   * call to EWS.
   *
   * @param mailbox                      the mailbox
   * @param meetingRequestsDeliveryScope the meeting request delivery scope
   * @param delegateUsers                the delegate users
   * @return A collection of DelegateUserResponse objects providing the
   * results of the operation.
   * @throws Exception the exception
   */
  public Collection addDelegates(Mailbox mailbox,
      MeetingRequestsDeliveryScope meetingRequestsDeliveryScope,
      DelegateUser... delegateUsers) throws Exception {
    return addDelegates(mailbox, meetingRequestsDeliveryScope,
                        Arrays.asList(delegateUsers));
  }

  /**
   * Adds delegates to a specific mailbox. Calling this method results in a
   * call to EWS.
   *
   * @param mailbox                      the mailbox
   * @param meetingRequestsDeliveryScope the meeting request delivery scope
   * @param delegateUsers                the delegate users
   * @return A collection of DelegateUserResponse objects providing the
   * results of the operation.
   * @throws Exception the exception
   */
  public Collection addDelegates(Mailbox mailbox,
      MeetingRequestsDeliveryScope meetingRequestsDeliveryScope,
      Iterable delegateUsers) throws Exception {
    EwsUtilities.validateParam(mailbox, "mailbox");
    EwsUtilities.validateParamCollection(delegateUsers.iterator(),
        "delegateUsers");

    AddDelegateRequest request = new AddDelegateRequest(this);
    request.setMailbox(mailbox);

    for (DelegateUser user : delegateUsers) {
      request.getDelegateUsers().add(user);
    }

    request.setMeetingRequestsDeliveryScope(meetingRequestsDeliveryScope);

    DelegateManagementResponse response = request.execute();
    return response.getDelegateUserResponses();
  }

  /**
   * Updates delegates on a specific mailbox. Calling this method results in
   * a call to EWS.
   *
   * @param mailbox                      the mailbox
   * @param meetingRequestsDeliveryScope the meeting request delivery scope
   * @param delegateUsers                the delegate users
   * @return A collection of DelegateUserResponse objects providing the
   * results of the operation.
   * @throws Exception the exception
   */
  public Collection updateDelegates(Mailbox mailbox,
      MeetingRequestsDeliveryScope meetingRequestsDeliveryScope,
      DelegateUser... delegateUsers) throws Exception {
    return this.updateDelegates(mailbox, meetingRequestsDeliveryScope,
        Arrays.asList(delegateUsers));
  }

  /**
   * Updates delegates on a specific mailbox. Calling this method results in
   * a call to EWS.
   *
   * @param mailbox                      the mailbox
   * @param meetingRequestsDeliveryScope the meeting request delivery scope
   * @param delegateUsers                the delegate users
   * @return A collection of DelegateUserResponse objects providing the
   * results of the operation.
   * @throws Exception the exception
   */
  public Collection updateDelegates(Mailbox mailbox,
      MeetingRequestsDeliveryScope meetingRequestsDeliveryScope,
      Iterable delegateUsers) throws Exception {
    EwsUtilities.validateParam(mailbox, "mailbox");
    EwsUtilities.validateParamCollection(delegateUsers.iterator(),
        "delegateUsers");

    UpdateDelegateRequest request = new UpdateDelegateRequest(this);

    request.setMailbox(mailbox);

    ArrayList delUser = new ArrayList();
    for (DelegateUser user : delegateUsers) {
      delUser.add(user);
    }
    request.getDelegateUsers().addAll(delUser);
    request.setMeetingRequestsDeliveryScope(meetingRequestsDeliveryScope);

    DelegateManagementResponse response = request.execute();
    return response.getDelegateUserResponses();
  }

  /**
   * Removes delegates on a specific mailbox. Calling this method results in
   * a call to EWS.
   *
   * @param mailbox the mailbox
   * @param userIds the user ids
   * @return A collection of DelegateUserResponse objects providing the
   * results of the operation.
   * @throws Exception the exception
   */
  public Collection removeDelegates(Mailbox mailbox,
      UserId... userIds) throws Exception {
    return removeDelegates(mailbox, Arrays.asList(userIds));
  }

  /**
   * Removes delegates on a specific mailbox. Calling this method results in
   * a call to EWS.
   *
   * @param mailbox the mailbox
   * @param userIds the user ids
   * @return A collection of DelegateUserResponse objects providing the
   * results of the operation.
   * @throws Exception the exception
   */
  public Collection removeDelegates(Mailbox mailbox,
      Iterable userIds) throws Exception {
    EwsUtilities.validateParam(mailbox, "mailbox");
    EwsUtilities.validateParamCollection(userIds.iterator(), "userIds");

    RemoveDelegateRequest request = new RemoveDelegateRequest(this);
    request.setMailbox(mailbox);

    ArrayList delUser = new ArrayList();
    for (UserId user : userIds) {
      delUser.add(user);
    }
    request.getUserIds().addAll(delUser);

    DelegateManagementResponse response = request.execute();
    return response.getDelegateUserResponses();
  }

  /**
   * Retrieves the delegates of a specific mailbox. Calling this method
   * results in a call to EWS.
   *
   * @param mailbox            the mailbox
   * @param includePermissions the include permissions
   * @param userIds            the user ids
   * @return A GetDelegateResponse providing the results of the operation.
   * @throws Exception the exception
   */
  public DelegateInformation getDelegates(Mailbox mailbox,
      boolean includePermissions, UserId... userIds) throws Exception {
    return this.getDelegates(mailbox, includePermissions, Arrays.asList(userIds));
  }

  /**
   * Retrieves the delegates of a specific mailbox. Calling this method
   * results in a call to EWS.
   *
   * @param mailbox            the mailbox
   * @param includePermissions the include permissions
   * @param userIds            the user ids
   * @return A GetDelegateResponse providing the results of the operation.
   * @throws Exception the exception
   */
  public DelegateInformation getDelegates(Mailbox mailbox,
      boolean includePermissions, Iterable userIds)
      throws Exception {
    EwsUtilities.validateParam(mailbox, "mailbox");

    GetDelegateRequest request = new GetDelegateRequest(this);

    request.setMailbox(mailbox);

    ArrayList delUser = new ArrayList();
    for (UserId user : userIds) {
      delUser.add(user);
    }
    request.getUserIds().addAll(delUser);
    request.setIncludePermissions(includePermissions);

    GetDelegateResponse response = request.execute();
    DelegateInformation delegateInformation = new DelegateInformation(
        (List) response
            .getDelegateUserResponses(), response
        .getMeetingRequestsDeliveryScope());

    return delegateInformation;
  }

  /**
   * Creates the user configuration.
   *
   * @param userConfiguration the user configuration
   * @throws Exception the exception
   */
  public void createUserConfiguration(UserConfiguration userConfiguration)
      throws Exception {
    EwsUtilities.validateParam(userConfiguration, "userConfiguration");

    CreateUserConfigurationRequest request = new CreateUserConfigurationRequest(
        this);

    request.setUserConfiguration(userConfiguration);

    request.execute();
  }

  /**
   * Creates a UserConfiguration.
   *
   * @param name           the name
   * @param parentFolderId the parent folder id
   * @throws Exception the exception
   */
  public void deleteUserConfiguration(String name, FolderId parentFolderId)
      throws Exception {
    EwsUtilities.validateParam(name, "name");
    EwsUtilities.validateParam(parentFolderId, "parentFolderId");

    DeleteUserConfigurationRequest request = new DeleteUserConfigurationRequest(
        this);

    request.setName(name);
    request.setParentFolderId(parentFolderId);
    request.execute();
  }

  /**
   * Creates a UserConfiguration.
   *
   * @param name           the name
   * @param parentFolderId the parent folder id
   * @param properties     the property
   * @return the user configuration
   * @throws Exception the exception
   */
  public UserConfiguration getUserConfiguration(String name, FolderId parentFolderId,
      UserConfigurationProperties properties)
      throws Exception {
    EwsUtilities.validateParam(name, "name");
    EwsUtilities.validateParam(parentFolderId, "parentFolderId");

    GetUserConfigurationRequest request = new GetUserConfigurationRequest(this);

    request.setName(name);
    request.setParentFolderId(parentFolderId);
    request.setProperties(EnumSet.of(properties));

    return request.execute().getResponseAtIndex(0).getUserConfiguration();
  }

  /**
   * Loads the property of the specified userConfiguration.
   *
   * @param userConfiguration the user configuration
   * @param properties        the property
   * @throws Exception the exception
   */
  public void loadPropertiesForUserConfiguration(UserConfiguration userConfiguration,
      UserConfigurationProperties properties) throws Exception {
    EwsUtilities.ewsAssert(userConfiguration != null, "ExchangeService.LoadPropertiesForUserConfiguration",
                           "userConfiguration is null");

    GetUserConfigurationRequest request = new GetUserConfigurationRequest(
        this);

    request.setUserConfiguration(userConfiguration);
    request.setProperties(EnumSet.of(properties));

    request.execute();
  }

  /**
   * Updates a UserConfiguration.
   *
   * @param userConfiguration the user configuration
   * @throws Exception the exception
   */
  public void updateUserConfiguration(UserConfiguration userConfiguration)
      throws Exception {
    EwsUtilities.validateParam(userConfiguration, "userConfiguration");
    UpdateUserConfigurationRequest request = new UpdateUserConfigurationRequest(this);

    request.setUserConfiguration(userConfiguration);

    request.execute();
  }

  // region InboxRule operations

  /**
   * Retrieves inbox rules of the authenticated user.
   *
   * @return A RuleCollection object containing the authenticated users inbox
   * rules.
   * @throws Exception
   */
  public RuleCollection getInboxRules() throws Exception {
    GetInboxRulesRequest request = new GetInboxRulesRequest(this);
    return request.execute().getRules();
  }

  /**
   * Retrieves the inbox rules of the specified user.
   *
   * @param mailboxSmtpAddress The SMTP address of the user whose inbox rules should be
   *                           retrieved
   * @return A RuleCollection object containing the inbox rules of the
   * specified user.
   * @throws Exception
   */
  public RuleCollection getInboxRules(String mailboxSmtpAddress)
      throws Exception {
    EwsUtilities.validateParam(mailboxSmtpAddress, "MailboxSmtpAddress");

    GetInboxRulesRequest request = new GetInboxRulesRequest(this);
    request.setmailboxSmtpAddress(mailboxSmtpAddress);
    return request.execute().getRules();
  }

  /**
   * Updates the authenticated user's inbox rules by applying the specified
   * operations.
   *
   * @param operations            The operations that should be applied to the user's inbox
   *                              rules.
   * @param removeOutlookRuleBlob Indicate whether or not to remove Outlook Rule Blob.
   * @throws Exception
   */
  public void updateInboxRules(Iterable operations,
      boolean removeOutlookRuleBlob) throws Exception {
    UpdateInboxRulesRequest request = new UpdateInboxRulesRequest(this);
    request.setInboxRuleOperations(operations);
    request.setRemoveOutlookRuleBlob(removeOutlookRuleBlob);
    request.execute();
  }

  /**
   * Updates the authenticated user's inbox rules by applying the specified
   * operations.
   *
   * @param operations            The operations that should be applied to the user's inbox
   *                              rules.
   * @param removeOutlookRuleBlob Indicate whether or not to remove Outlook Rule Blob.
   * @param mailboxSmtpAddress    The SMTP address of the user whose inbox rules should be
   *                              retrieved
   * @throws Exception
   */
  public void updateInboxRules(Iterable operations,
      boolean removeOutlookRuleBlob, String mailboxSmtpAddress)
      throws Exception {
    UpdateInboxRulesRequest request = new UpdateInboxRulesRequest(this);
    request.setInboxRuleOperations(operations);
    request.setRemoveOutlookRuleBlob(removeOutlookRuleBlob);
    request.setMailboxSmtpAddress(mailboxSmtpAddress);
    request.execute();
  }

  /**
   * Default implementation of AutodiscoverRedirectionUrlValidationCallback.
   * Always returns true indicating that the URL can be used.
   *
   * @param redirectionUrl the redirection url
   * @return Returns true.
   * @throws AutodiscoverLocalException the autodiscover local exception
   */
  private boolean defaultAutodiscoverRedirectionUrlValidationCallback(
      String redirectionUrl) throws AutodiscoverLocalException {
    throw new AutodiscoverLocalException(String.format(
        "Autodiscover blocked a potentially insecure redirection to %s. To allow Autodiscover to follow the redirection, use the AutodiscoverUrl(string, AutodiscoverRedirectionUrlValidationCallback) overload.", redirectionUrl));
  }

  /**
   * Initializes the Url property to the Exchange Web Services URL for the
   * specified e-mail address by calling the Autodiscover service.
   *
   * @param emailAddress the email address
   * @throws Exception the exception
   */
  public void autodiscoverUrl(String emailAddress) throws Exception {
    this.autodiscoverUrl(emailAddress, this);
  }

  /**
   * Initializes the Url property to the Exchange Web Services URL for the
   * specified e-mail address by calling the Autodiscover service.
   *
   * @param emailAddress                   the email address to use.
   * @param validateRedirectionUrlCallback The callback used to validate redirection URL
   * @throws Exception the exception
   */
  public void autodiscoverUrl(String emailAddress,
      IAutodiscoverRedirectionUrl validateRedirectionUrlCallback)
      throws Exception {
    URI exchangeServiceUrl = null;

    if (this.getRequestedServerVersion().ordinal() > ExchangeVersion.Exchange2007_SP1
        .ordinal()) {
      try {
        exchangeServiceUrl = this.getAutodiscoverUrl(emailAddress, this
                .getRequestedServerVersion(),
            validateRedirectionUrlCallback);
        this.setUrl(this
            .adjustServiceUriFromCredentials(exchangeServiceUrl));
        return;
      } catch (AutodiscoverLocalException ex) {

        this.traceMessage(TraceFlags.AutodiscoverResponse, String
            .format("Autodiscover service call "
                + "failed with error '%s'. "
                + "Will try legacy service", ex.getMessage()));

      } catch (ServiceRemoteException ex) {
        // E14:321785 -- Special case: if
        // the caller's account is locked
        // we want to return this exception, not continue.
        if (ex instanceof AccountIsLockedException) {
          throw new AccountIsLockedException(ex.getMessage(),
              exchangeServiceUrl, ex);
        }

        this.traceMessage(TraceFlags.AutodiscoverResponse, String
            .format("Autodiscover service call "
                + "failed with error '%s'. "
                + "Will try legacy service", ex.getMessage()));
      }
    }

    // Try legacy Autodiscover provider

    exchangeServiceUrl = this.getAutodiscoverUrl(emailAddress,
        ExchangeVersion.Exchange2007_SP1,
        validateRedirectionUrlCallback);

    this.setUrl(this.adjustServiceUriFromCredentials(exchangeServiceUrl));
  }

  /**
   * Autodiscover will always return the "plain" EWS endpoint URL but if the
   * client is using WindowsLive credential, ExchangeService needs to use the
   * WS-Security endpoint.
   *
   * @param uri the uri
   * @return Adjusted URL.
   * @throws Exception
   */
  private URI adjustServiceUriFromCredentials(URI uri)
      throws Exception {
    return (this.getCredentials() != null) ? this.getCredentials()
        .adjustUrl(uri) : uri;
  }

  /**
   * Gets the autodiscover url.
   *
   * @param emailAddress                   the email address
   * @param requestedServerVersion         the Exchange version
   * @param validateRedirectionUrlCallback the validate redirection url callback
   * @return the autodiscover url
   * @throws Exception the exception
   */
  private URI getAutodiscoverUrl(String emailAddress,
      ExchangeVersion requestedServerVersion,
      IAutodiscoverRedirectionUrl validateRedirectionUrlCallback)
      throws Exception {

    AutodiscoverService autodiscoverService = new AutodiscoverService(this, requestedServerVersion);
    autodiscoverService.setWebProxy(getWebProxy());
    autodiscoverService.setTimeout(getTimeout());
    
    autodiscoverService
        .setRedirectionUrlValidationCallback(validateRedirectionUrlCallback);
    autodiscoverService.setEnableScpLookup(this.getEnableScpLookup());

    GetUserSettingsResponse response = autodiscoverService.getUserSettings(
        emailAddress, UserSettingName.InternalEwsUrl,
        UserSettingName.ExternalEwsUrl);

    switch (response.getErrorCode()) {
      case NoError:
        return this.getEwsUrlFromResponse(response, autodiscoverService
            .isExternal().TRUE);

      case InvalidUser:
        throw new ServiceRemoteException(String.format("Invalid user: '%s'",
            emailAddress));

      case InvalidRequest:
        throw new ServiceRemoteException(String.format("Invalid Autodiscover request: '%s'", response
                .getErrorMessage()));

      default:
        this.traceMessage(TraceFlags.AutodiscoverConfiguration, String
            .format("No EWS Url returned for user %s, "
                + "error code is %s", emailAddress, response
                .getErrorCode()));

        throw new ServiceRemoteException(response.getErrorMessage());
    }
  }

  private URI getEwsUrlFromResponse(GetUserSettingsResponse response,
      boolean isExternal) throws URISyntaxException, AutodiscoverLocalException {
    String uriString;

    // Bug E14:59063 -- Figure out which URL to use: Internal or External.
    // Bug E14:67646 -- AutoDiscover may not return an external protocol.
    // First try external, then internal.
    // Bug E14:82650 -- Either protocol
    // may be returned without a configured URL.
    OutParam outParam = new OutParam();
    if ((isExternal && response.tryGetSettingValue(String.class,
        UserSettingName.ExternalEwsUrl, outParam))) {
      uriString = outParam.getParam();
      if (!(uriString == null || uriString.isEmpty())) {
        return new URI(uriString);
      }
    }
    if ((response.tryGetSettingValue(String.class,
        UserSettingName.InternalEwsUrl, outParam) || response
        .tryGetSettingValue(String.class,
            UserSettingName.ExternalEwsUrl, outParam))) {
      uriString = outParam.getParam();
      if (!(uriString == null || uriString.isEmpty())) {
        return new URI(uriString);
      }
    }

    // If Autodiscover doesn't return an
    // internal or external EWS URL, throw an exception.
    throw new AutodiscoverLocalException(
        "The Autodiscover service didn't return an appropriate URL that can be used for the ExchangeService Autodiscover URL.");
  }

  // region Diagnostic Method -- Only used by test

  /**
   * Executes the diagnostic method.
   *
   * @param verb      The verb.
   * @param parameter The parameter.
   * @throws Exception
   */
  protected Document executeDiagnosticMethod(String verb, Node parameter)
      throws Exception {
    ExecuteDiagnosticMethodRequest request = new ExecuteDiagnosticMethodRequest(this);
    request.setVerb(verb);
    request.setParameter(parameter);

    return request.execute().getResponseAtIndex(0).getReturnValue();

  }

  // endregion

  // region Validation

  /**
   * Validates this instance.
   *
   * @throws ServiceLocalException the service local exception
   */
  @Override public void validate() throws ServiceLocalException {
    super.validate();
    if (this.getUrl() == null) {
      throw new ServiceLocalException("The Url property on the ExchangeService object must be set.");
    }
  }

  // region Constructors

  /**
   * Initializes a new instance of the {@link ExchangeService} class,
   * targeting the specified version of EWS and scoped to the to the system's
   * current time zone.
   */
  public ExchangeService() {
    super();
  }

  /**
   * Initializes a new instance of the {@link ExchangeService} class,
   * targeting the specified version of EWS and scoped to the system's current
   * time zone.
   *
   * @param requestedServerVersion the requested server version
   */
  public ExchangeService(ExchangeVersion requestedServerVersion) {
    super(requestedServerVersion);
  }

  /**
   * Initializes a new instance of the {@link ExchangeService} class,
   * targeting the specified version of EWS and scoped to the system's current
   * time zone.
   * 
   * Additionally specifies an external HttpClient to use, e.g. one wired up
   * with a thread-safe pooling http connection factory. It is perfectly
   * acceptable for both arguments to use the same HttpClient.
   *
   * @param requestedServerVersion the requested server version
   * @param httpClient external HttpClient instance to use
   * @param httpPoolingClient external HttpClient instance to use for pooling
   */
  public ExchangeService(ExchangeVersion requestedServerVersion, CloseableHttpClient httpClient,
      CloseableHttpClient httpPoolingClient) {
    super(requestedServerVersion, httpClient, httpPoolingClient);
  }

  // Utilities

  /**
   * Prepare http web request.
   *
   * @return the http web request
   * @throws ServiceLocalException       the service local exception
   * @throws java.net.URISyntaxException the uRI syntax exception
   */
  public HttpWebRequest prepareHttpWebRequest()
      throws ServiceLocalException, URISyntaxException {
    try {
      this.url = this.adjustServiceUriFromCredentials(this.getUrl());
    } catch (Exception e) {
      LOG.error(e);
    }
    return this.prepareHttpWebRequestForUrl(url, this
        .getAcceptGzipEncoding(), true);
  }

  /**
   * Prepares a http web request from a pooling connection manager, used for subscriptions.
   * 
   * @return A http web request
   * @throws ServiceLocalException The service local exception
   * @throws java.net.URISyntaxException the uRI syntax exception
   */
  public HttpWebRequest prepareHttpPoolingWebRequest()
	      throws ServiceLocalException, URISyntaxException {
	    try {
	      this.url = this.adjustServiceUriFromCredentials(this.getUrl());
	    } catch (Exception e) {
	      LOG.error(e);
	    }
	    return this.prepareHttpPoolingWebRequestForUrl(url, this
	        .getAcceptGzipEncoding(), true);
	  }

  /**
   * Processes an HTTP error response.
   *
   * @param httpWebResponse The HTTP web response.
   * @param webException    The web exception
   * @throws Exception
   */
  @Override public void processHttpErrorResponse(HttpWebRequest httpWebResponse, Exception webException) throws Exception {
    this.internalProcessHttpErrorResponse(httpWebResponse, webException,
        TraceFlags.EwsResponseHttpHeaders, TraceFlags.EwsResponse);
  }

  // Properties

  /**
   * Gets the URL of the Exchange Web Services.
   *
   * @return URL of the Exchange Web Services.
   */
  public URI getUrl() {
    return url;
  }

  /**
   * Sets the URL of the Exchange Web Services.
   *
   * @param url URL of the Exchange Web Services.
   */
  public void setUrl(URI url) {
    this.url = url;
  }

  /**
   * Gets the impersonated user id.
   *
   * @return the impersonated user id
   */
  public ImpersonatedUserId getImpersonatedUserId() {
    return impersonatedUserId;
  }

  /**
   * Sets the impersonated user id.
   *
   * @param impersonatedUserId the new impersonated user id
   */
  public void setImpersonatedUserId(ImpersonatedUserId impersonatedUserId) {
    this.impersonatedUserId = impersonatedUserId;
  }

  /**
   * Gets the preferred culture.
   *
   * @return the preferred culture
   */
  public Locale getPreferredCulture() {
    return preferredCulture;
  }

  /**
   * Sets the preferred culture.
   *
   * @param preferredCulture the new preferred culture
   */
  public void setPreferredCulture(Locale preferredCulture) {
    this.preferredCulture = preferredCulture;
  }

  /**
   * Gets the DateTime precision for DateTime values returned from Exchange
   * Web Services.
   *
   * @return the DateTimePrecision
   */
  public DateTimePrecision getDateTimePrecision() {
    return this.dateTimePrecision;
  }

  /**
   * Sets the DateTime precision for DateTime values Web Services.
   * @param d date time precision
   */
  public void setDateTimePrecision(DateTimePrecision d) {
    this.dateTimePrecision = d;
  }

  /**
   * Sets the DateTime precision for DateTime values returned from Exchange
   * Web Services.
   *
   * @param dateTimePrecision the new DateTimePrecision
   */
  public void setPreferredCulture(DateTimePrecision dateTimePrecision) {
    this.dateTimePrecision = dateTimePrecision;
  }

  /**
   * Gets the file attachment content handler.
   *
   * @return the file attachment content handler
   */
  public IFileAttachmentContentHandler getFileAttachmentContentHandler() {
    return this.fileAttachmentContentHandler;
  }

  /**
   * Sets the file attachment content handler.
   *
   * @param fileAttachmentContentHandler the new file attachment content handler
   */
  public void setFileAttachmentContentHandler(
      IFileAttachmentContentHandler fileAttachmentContentHandler) {
    this.fileAttachmentContentHandler = fileAttachmentContentHandler;
  }

  /**
   * Provides access to the Unified Messaging functionalities.
   *
   * @return the unified messaging
   */
  public UnifiedMessaging getUnifiedMessaging() {
    if (this.unifiedMessaging == null) {
      this.unifiedMessaging = new UnifiedMessaging(this);
    }

    return this.unifiedMessaging;
  }

  /**
   * Gets or sets a value indicating whether the AutodiscoverUrl method should
   * perform SCP (Service Connection Point) record lookup when determining the
   * Autodiscover service URL.
   *
   * @return enable scp lookup flag.
   */
  public boolean getEnableScpLookup() {
    return this.enableScpLookup;
  }


  public void setEnableScpLookup(boolean value) {
    this.enableScpLookup = value;
  }

  /**
   * Returns true whether Exchange2007 compatibility mode is enabled, false otherwise.
   */
  public boolean getExchange2007CompatibilityMode() {
    return this.exchange2007CompatibilityMode;
  }

  /**
   * Set the flag indicating if the Exchange2007 compatibility mode is enabled.
   *
   * 

* In order to support E12 servers, the exchange2007CompatibilityMode property, * set to true, can be used to indicate that we should use "Exchange2007" as the server version String * rather than Exchange2007_SP1. *

* * @param value true if the Exchange2007 compatibility mode is enabled. */ public void setExchange2007CompatibilityMode(boolean value) { this.exchange2007CompatibilityMode = value; } /** * Retrieves the definitions of the specified server-side time zones. * * @param timeZoneIds the time zone ids * @return A Collection containing the definitions of the specified time * zones. * @throws Exception */ public Collection getServerTimeZones( Iterable timeZoneIds) throws Exception { Map timeZoneMap = new HashMap(); GetServerTimeZonesRequest request = new GetServerTimeZonesRequest(this); ServiceResponseCollection responses = request.execute(); for (GetServerTimeZonesResponse response : responses) { for (TimeZoneDefinition tzd : response.getTimeZones()) { timeZoneMap.put(tzd.getId(), tzd); } } Collection timeZoneList = new ArrayList(); for (String timeZoneId : timeZoneIds) { timeZoneList.add(timeZoneMap.get(timeZoneId)); } return timeZoneList; } /** * Retrieves the definitions of all server-side time zones. * * @return A Collection containing the definitions of the specified time * zones. * @throws Exception */ public Collection getServerTimeZones() throws Exception { GetServerTimeZonesRequest request = new GetServerTimeZonesRequest(this); Collection timeZoneList = new ArrayList(); ServiceResponseCollection responses = request.execute(); for (GetServerTimeZonesResponse response : responses) { timeZoneList.addAll(response.getTimeZones()); } return timeZoneList; } /* * (non-Javadoc) * * @seemicrosoft.exchange.webservices.AutodiscoverRedirectionUrlInterface# * autodiscoverRedirectionUrlValidationCallback(java.lang.String) */ public boolean autodiscoverRedirectionUrlValidationCallback( String redirectionUrl) throws AutodiscoverLocalException { return defaultAutodiscoverRedirectionUrlValidationCallback(redirectionUrl); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy