1818
1919import static com .google .cloud .pubsublite .kafka .KafkaExceptionUtils .toKafka ;
2020
21+ import com .google .api .core .ApiFuture ;
2122import com .google .api .core .ApiFutureCallback ;
2223import com .google .api .core .ApiFutures ;
2324import com .google .api .gax .rpc .ApiException ;
2627import com .google .cloud .pubsublite .SubscriptionPath ;
2728import com .google .cloud .pubsublite .TopicPath ;
2829import com .google .cloud .pubsublite .internal .CursorClient ;
30+ import com .google .cloud .pubsublite .internal .TopicStatsClient ;
2931import com .google .cloud .pubsublite .internal .wire .Assigner ;
3032import com .google .cloud .pubsublite .internal .wire .AssignerFactory ;
3133import com .google .cloud .pubsublite .internal .wire .PartitionAssignmentReceiver ;
@@ -74,6 +76,7 @@ class PubsubLiteConsumer implements Consumer<byte[], byte[]> {
7476 private final ConsumerFactory consumerFactory ;
7577 private final AssignerFactory assignerFactory ;
7678 private final CursorClient cursorClient ;
79+ private final TopicStatsClient topicStatsClient ;
7780 private Optional <Assigner > assigner = Optional .empty ();
7881 private Optional <SingleSubscriptionConsumer > consumer = Optional .empty ();
7982
@@ -83,13 +86,15 @@ class PubsubLiteConsumer implements Consumer<byte[], byte[]> {
8386 SharedBehavior shared ,
8487 ConsumerFactory consumerFactory ,
8588 AssignerFactory assignerFactory ,
86- CursorClient cursorClient ) {
89+ CursorClient cursorClient ,
90+ TopicStatsClient topicStatsClient ) {
8791 this .subscriptionPath = subscriptionPath ;
8892 this .topicPath = topicPath ;
8993 this .shared = shared ;
9094 this .consumerFactory = consumerFactory ;
9195 this .assignerFactory = assignerFactory ;
9296 this .cursorClient = cursorClient ;
97+ this .topicStatsClient = topicStatsClient ;
9398 }
9499
95100 private TopicPartition toTopicPartition (Partition partition ) {
@@ -490,8 +495,25 @@ public Map<TopicPartition, Long> endOffsets(Collection<TopicPartition> collectio
490495 @ Override
491496 public Map <TopicPartition , Long > endOffsets (
492497 Collection <TopicPartition > collection , Duration duration ) {
493- throw new UnsupportedVersionException (
494- "Pub/Sub Lite does not support Consumer backlog introspection." );
498+ try {
499+ Map <TopicPartition , ApiFuture <Cursor >> cursors =
500+ collection .stream ()
501+ .collect (
502+ Collectors .toMap (
503+ topicPartition -> topicPartition ,
504+ topicPartition ->
505+ topicStatsClient .computeHeadCursor (
506+ topicPath , checkTopicGetPartition (topicPartition ))));
507+ ApiFutures .allAsList (cursors .values ()).get (duration .toMillis (), TimeUnit .MILLISECONDS );
508+
509+ ImmutableMap .Builder <TopicPartition , Long > output = ImmutableMap .builder ();
510+ for (Map .Entry <TopicPartition , ApiFuture <Cursor >> entry : cursors .entrySet ()) {
511+ output .put (entry .getKey (), entry .getValue ().get ().getOffset ());
512+ }
513+ return output .build ();
514+ } catch (Throwable t ) {
515+ throw toKafka (t );
516+ }
495517 }
496518
497519 @ Override
@@ -511,6 +533,12 @@ public void close(Duration timeout) {
511533 } catch (Exception e ) {
512534 logger .atSevere ().withCause (e ).log ("Error closing cursor client during Consumer shutdown." );
513535 }
536+ try {
537+ topicStatsClient .close ();
538+ } catch (Exception e ) {
539+ logger .atSevere ().withCause (e ).log (
540+ "Error closing topic stats client during Consumer shutdown." );
541+ }
514542 try {
515543 shared .close ();
516544 } catch (Exception e ) {
0 commit comments