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

com.microsoft.azure.reactiveeventhubs.scaladsl.EventHubPartition.scala Maven / Gradle / Ivy

The newest version!
// Copyright (c) Microsoft. All rights reserved.

package com.microsoft.azure.reactiveeventhubs.scaladsl

import java.time.Instant

import akka.NotUsed
import akka.pattern.ask
import akka.stream.scaladsl.Source
import akka.util.Timeout
import com.microsoft.azure.eventhubs.PartitionReceiver
import com.microsoft.azure.reactiveeventhubs._
import com.microsoft.azure.reactiveeventhubs.checkpointing.CheckpointService.GetOffset
import com.microsoft.azure.reactiveeventhubs.checkpointing.{CheckpointActorSystem, SaveOffsetOnPull}
import com.microsoft.azure.reactiveeventhubs.config.IConfiguration

import scala.concurrent.Await
import scala.concurrent.duration._
import scala.language.{implicitConversions, postfixOps}

object EventHubPartition extends Logger {

  // Public constant: offset position used to start reading from the beginning
  final val OffsetStartOfStream: String = PartitionReceiver.START_OF_STREAM

  // Public constant: used internally to signal when there is no position saved in the storage
  // To be used by custom backend implementations
  final val OffsetCheckpointNotFound: String = "{offset checkpoint not found}"
}

/** Provide a streaming source to retrieve messages from one Azure Event Hub partition
  *
  * @param partition Event Hub partition number (0-based). The number of
  *                  partitions is set during the deployment.
  */
private[reactiveeventhubs] case class EventHubPartition(config: IConfiguration, val partition: Int) extends Logger {

  /** Create a stream returning all the messages for the defined partition, from the given start
    * point, optionally with checkpointing
    *
    * @return A source of Event hub messages
    */
  def source(options: SourceOptions): Source[EventHubsMessage, NotUsed] = {

    // Load the partition offset saved in the checkpoint storage
    val savedOffset = if (!options.isFromSavedOffsets)
                        None
                      else {
                        val savedOffset = GetSavedOffset
                        if (savedOffset.isDefined) {
                          log.info("Starting partition {} from saved offset {}", partition, savedOffset.get)
                          savedOffset
                        } else if (options.getStartTimeOnNoCheckpoint.isEmpty) {
                          // The user didn't provide a start time for missing
                          // checkpoints, so let's start from the beginning
                          Some(EventHubPartition.OffsetStartOfStream)
                        } else {
                          // The user didn't provide a start time for missing
                          // checkpoints, but provided a start time for such case
                          // so we leave this empty
                          None
                        }
                      }

    // Define the start point offset
    val startOffsets = if (options.isFromStart) Some(EventHubPartition.OffsetStartOfStream)
                       else if (options.isFromOffsets) Some(options.getStartOffsets(config.connect)(partition))
                       else if (options.isFromSavedOffsets) savedOffset
                       else if (options.isFromTime) None
                       else None

    // Decide whether to start streaming from a time or an offset
    val withTimeOffset = if (options.isFromTime) true
                         else if (startOffsets.isDefined) false
                         else if (options.getStartTimeOnNoCheckpoint.isDefined) true
                         else false

    // Define the start point timestamp
    val startTime = if (options.isFromTime) options.getStartTime.get
                    else if (withTimeOffset) options.getStartTimeOnNoCheckpoint.get
                    else Instant.MIN

    // Build the source starting by time or by offset
    val source = if (withTimeOffset)
                    EventHubsMessageSource(config, partition, startTime)
                 else
                    EventHubsMessageSource(config, partition, startOffsets.get)

    // Inject a flow to store the stream position after each pull
    if (options.isSaveOffsets) {
      log.debug("Adding checkpointing flow to the partition {} stream", partition)
      source.via(new SaveOffsetOnPull(config.checkpointing, partition))
    } else {
      source
    }
  }

  /** Get the offset saved for the current partition
    *
    * @return Offset
    */
  private[this] def GetSavedOffset(): Option[String] = {
    val partitionCp = CheckpointActorSystem(config.checkpointing).getCheckpointService(partition)
    implicit val rwTimeout = Timeout(config.checkpointing.checkpointRWTimeout)
    try {
      Retry(3, 5 seconds) {
        log.debug("Loading the stream offset for partition {}", partition)
        val future = (partitionCp ? GetOffset).mapTo[String]
        val offset = Await.result(future, rwTimeout.duration)
        if (offset != EventHubPartition.OffsetCheckpointNotFound) Some(offset) else None
      }
    } catch {
      case e: java.util.concurrent.TimeoutException ⇒
        log.error(e, "Timeout while retrieving the offset from the storage")
        throw e

      case e: Exception ⇒
        log.error(e, e.getMessage)
        throw e
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy