/// <summary>
        /// Builds a new instance of the <see cref="IChangeFeedProcessor"/> with the specified configuration.
        /// </summary>
        /// <returns>An instance of <see cref="IChangeFeedProcessor"/>.</returns>
        public async Task <IChangeFeedProcessor> BuildAsync()
        {
            if (this.HostName == null)
            {
                throw new InvalidOperationException("Host name was not specified");
            }

            if (this.feedCollectionLocation == null)
            {
                throw new InvalidOperationException(nameof(this.feedCollectionLocation) + " was not specified");
            }

            if (this.leaseCollectionLocation == null && this.LeaseStoreManager == null)
            {
                throw new InvalidOperationException($"Either {nameof(this.leaseCollectionLocation)} or {nameof(this.LeaseStoreManager)} must be specified");
            }

            if (this.observerFactory == null)
            {
                throw new InvalidOperationException("Observer was not specified");
            }

            await this.InitializeCollectionPropertiesForBuildAsync().ConfigureAwait(false);

            ILeaseStoreManager leaseStoreManager = await this.GetLeaseStoreManagerAsync(this.leaseCollectionLocation, true).ConfigureAwait(false);

            IPartitionManager partitionManager = this.BuildPartitionManager(leaseStoreManager);

            return(new ChangeFeedProcessor(partitionManager));
        }
 /// <summary>
 /// Sets the <see cref="ILeaseStoreManager"/> to be used to manage leases.
 /// </summary>
 /// <param name="leaseStoreManager">The instance of <see cref="ILeaseStoreManager"/> to use.</param>
 /// <returns>The instance of <see cref="ChangeFeedProcessorBuilder"/> to use.</returns>
 public ChangeFeedProcessorBuilder WithLeaseStoreManager(ILeaseStoreManager leaseStoreManager)
 {
     if (leaseStoreManager == null)
     {
         throw new ArgumentNullException(nameof(leaseStoreManager));
     }
     this.LeaseStoreManager = leaseStoreManager;
     return(this);
 }
        private IPartitionManager BuildPartitionManager(ILeaseStoreManager leaseStoreManager)
        {
            string feedCollectionSelfLink = this.feedCollectionLocation.GetCollectionSelfLink();
            var    factory      = new CheckpointerObserverFactory(this.observerFactory, this.changeFeedProcessorOptions.CheckpointFrequency);
            var    synchronizer = new PartitionSynchronizer(
                this.feedDocumentClient,
                feedCollectionSelfLink,
                leaseStoreManager,
                leaseStoreManager,
                this.changeFeedProcessorOptions.DegreeOfParallelism,
                this.changeFeedProcessorOptions.QueryPartitionsMaxBatchSize);
            var bootstrapper = new Bootstrapper(synchronizer, leaseStoreManager, this.lockTime, this.sleepTime);
            var partitionSuperviserFactory = new PartitionSupervisorFactory(
                factory,
                leaseStoreManager,
                this.partitionProcessorFactory ?? new PartitionProcessorFactory(this.feedDocumentClient, this.changeFeedProcessorOptions, leaseStoreManager, feedCollectionSelfLink),
                this.changeFeedProcessorOptions);

            if (this.loadBalancingStrategy == null)
            {
                this.loadBalancingStrategy = new EqualPartitionsBalancingStrategy(
                    this.HostName,
                    this.changeFeedProcessorOptions.MinPartitionCount,
                    this.changeFeedProcessorOptions.MaxPartitionCount,
                    this.changeFeedProcessorOptions.LeaseExpirationInterval);
            }

            IPartitionController partitionController = new PartitionController(leaseStoreManager, leaseStoreManager, partitionSuperviserFactory, synchronizer);

            if (this.healthMonitor == null)
            {
                this.healthMonitor = new TraceHealthMonitor();
            }

            partitionController = new HealthMonitoringPartitionControllerDecorator(partitionController, this.healthMonitor);
            var partitionLoadBalancer = new PartitionLoadBalancer(
                partitionController,
                leaseStoreManager,
                this.loadBalancingStrategy,
                this.changeFeedProcessorOptions.LeaseAcquireInterval);

            return(new PartitionManager(bootstrapper, partitionController, partitionLoadBalancer));
        }
        private async Task <ILeaseStoreManager> GetLeaseStoreManagerAsync(
            DocumentCollectionInfo collectionInfo,
            bool isPartitionKeyByIdRequiredIfPartitioned)
        {
            if (this.LeaseStoreManager == null)
            {
                var leaseDocumentClient = this.leaseDocumentClient ?? this.leaseCollectionLocation.CreateDocumentClient();
                var collection          = await leaseDocumentClient.GetDocumentCollectionAsync(collectionInfo).ConfigureAwait(false);

                bool isPartitioned =
                    collection.PartitionKey != null &&
                    collection.PartitionKey.Paths != null &&
                    collection.PartitionKey.Paths.Count > 0;
                if (isPartitioned && isPartitionKeyByIdRequiredIfPartitioned &&
                    (collection.PartitionKey.Paths.Count != 1 || collection.PartitionKey.Paths[0] != "/id"))
                {
                    throw new ArgumentException("The lease collection, if partitioned, must have partition key equal to id.");
                }

                var requestOptionsFactory = isPartitioned ?
                                            (IRequestOptionsFactory) new PartitionedByIdCollectionRequestOptionsFactory() :
                                            (IRequestOptionsFactory) new SinglePartitionRequestOptionsFactory();

                string leasePrefix = this.GetLeasePrefix();
                var    leaseStoreManagerBuilder = new DocumentServiceLeaseStoreManagerBuilder()
                                                  .WithLeasePrefix(leasePrefix)
                                                  .WithLeaseCollection(this.leaseCollectionLocation)
                                                  .WithLeaseCollectionLink(collection.SelfLink)
                                                  .WithRequestOptionsFactory(requestOptionsFactory)
                                                  .WithHostName(this.HostName);

                leaseStoreManagerBuilder = leaseStoreManagerBuilder.WithLeaseDocumentClient(leaseDocumentClient);

                this.LeaseStoreManager = await leaseStoreManagerBuilder.BuildAsync().ConfigureAwait(false);
            }

            return(this.LeaseStoreManager);
        }
        public ChangeFeedProcessorTests()
        {
            var leaseQueryMock      = new Mock <IDocumentQuery <Document> >();
            var leaseDocumentClient = Mock.Of <IChangeFeedDocumentClient>();

            Mock.Get(leaseDocumentClient)
            .Setup(c => c.CreateDocumentQuery <Document>(collectionLink,
                                                         It.Is <SqlQuerySpec>(spec => spec.QueryText == "SELECT * FROM c WHERE STARTSWITH(c.id, @PartitionLeasePrefix)" &&
                                                                              spec.Parameters.Count == 1 &&
                                                                              spec.Parameters[0].Name == "@PartitionLeasePrefix" &&
                                                                              (string)spec.Parameters[0].Value == storeNamePrefix + ".."
                                                                              ), null))
            .Returns(leaseQueryMock.As <IQueryable <Document> >().Object);
            leaseQueryMock
            .Setup(q => q.HasMoreResults)
            .Returns(false);
            Mock.Get(leaseDocumentClient)
            .Setup(ex => ex.ReadDatabaseAsync(It.IsAny <Uri>(), It.IsAny <RequestOptions>()))
            .ReturnsAsync(new ResourceResponse <Database>(database));
            Mock.Get(leaseDocumentClient)
            .Setup(ex => ex.ReadDocumentCollectionAsync(It.IsAny <Uri>(), It.IsAny <RequestOptions>()))
            .ReturnsAsync(new ResourceResponse <DocumentCollection>(collection));

            var documents    = new List <Document> {
            };
            var feedResponse = Mock.Of <IFeedResponse <Document> >();

            Mock.Get(feedResponse)
            .Setup(response => response.Count)
            .Returns(documents.Count);
            Mock.Get(feedResponse)
            .Setup(response => response.GetEnumerator())
            .Returns(documents.GetEnumerator());

            var documentQuery = Mock.Of <IChangeFeedDocumentQuery <Document> >();

            Mock.Get(documentQuery)
            .Setup(query => query.HasMoreResults)
            .Returns(false);
            Mock.Get(documentQuery)
            .Setup(query => query.ExecuteNextAsync <Document>(It.IsAny <CancellationToken>()))
            .ReturnsAsync(() => feedResponse)
            .Callback(cancellationTokenSource.Cancel);

            var documentClient = Mock.Of <IChangeFeedDocumentClient>();

            Mock.Get(documentClient)
            .Setup(ex => ex.CreateDocumentChangeFeedQuery(It.IsAny <string>(), It.IsAny <ChangeFeedOptions>()))
            .Callback((string s, ChangeFeedOptions o) => this.createDocumentChangeFeedQueryCallback(s, o))
            .Returns(documentQuery);
            Mock.Get(documentClient)
            .Setup(ex => ex.ReadDatabaseAsync(It.IsAny <Uri>(), It.IsAny <RequestOptions>()))
            .ReturnsAsync(new ResourceResponse <Database>(database));
            Mock.Get(documentClient)
            .Setup(ex => ex.ReadDocumentCollectionAsync(It.IsAny <Uri>(), It.IsAny <RequestOptions>()))
            .ReturnsAsync(new ResourceResponse <DocumentCollection>(collection));

            this.lease = Mock.Of <ILease>();
            Mock.Get(this.lease)
            .Setup(l => l.PartitionId)
            .Returns("partitionId");

            var leaseStore = Mock.Of <ILeaseStore>();

            this.leaseStoreManager = Mock.Of <ILeaseStoreManager>();
            Mock.Get(this.leaseStoreManager)
            .Setup(store => store.IsInitializedAsync())
            .ReturnsAsync(true);
            Mock.Get(this.leaseStoreManager)
            .Setup(manager => manager.AcquireAsync(lease))
            .ReturnsAsync(lease);
            Mock.Get(this.leaseStoreManager)
            .Setup(manager => manager.ReleaseAsync(lease))
            .Returns(Task.CompletedTask);

            this.builder
            .WithHostName("someHost")
            .WithFeedDocumentClient(documentClient)
            .WithFeedCollection(collectionInfo)
            .WithProcessorOptions(new ChangeFeedProcessorOptions())
            .WithLeaseStoreManager(leaseStoreManager)
            .WithLeaseCollection(collectionInfo)
            .WithLeaseDocumentClient(leaseDocumentClient);

            this.observer = Mock.Of <IChangeFeedObserver>();
            Mock.Get(observer)
            .Setup(feedObserver => feedObserver
                   .ProcessChangesAsync(It.IsAny <ChangeFeedObserverContext>(), It.IsAny <IReadOnlyList <Document> >(), It.IsAny <CancellationToken>()))
            .Returns(Task.CompletedTask)
            .Callback(cancellationTokenSource.Cancel);
            Mock.Get(observer)
            .Setup(observer => observer.OpenAsync(It.IsAny <ChangeFeedObserverContext>()))
            .Returns(Task.CompletedTask);

            this.observerFactory = Mock.Of <IChangeFeedObserverFactory>();
            Mock.Get(observerFactory)
            .Setup(observer => observer.CreateObserver())
            .Returns(observer);
        }