3737import org .apache .kafka .streams .state .AggregationWithHeaders ;
3838import org .apache .kafka .streams .state .KeyValueIterator ;
3939import org .apache .kafka .streams .state .KeyValueStore ;
40+ import org .apache .kafka .streams .state .QueryableStoreType ;
4041import org .apache .kafka .streams .state .QueryableStoreTypes ;
4142import org .apache .kafka .streams .state .ReadOnlyKeyValueStore ;
4243import org .apache .kafka .streams .state .ReadOnlySessionStore ;
5152import org .apache .kafka .streams .state .ValueAndTimestamp ;
5253import org .apache .kafka .streams .state .ValueTimestampHeaders ;
5354import org .apache .kafka .streams .state .WindowStore ;
55+ import org .apache .kafka .streams .state .internals .CompositeReadOnlyKeyValueStore ;
56+ import org .apache .kafka .streams .state .internals .CompositeReadOnlyWindowStore ;
57+ import org .apache .kafka .streams .state .internals .StateStoreProvider ;
5458import org .apache .kafka .test .TestUtils ;
5559
5660import org .junit .jupiter .api .AfterAll ;
@@ -479,7 +483,7 @@ private <K, V> void processKeyValueWithTimestampAndHeadersAndVerify(final K key,
479483 () -> {
480484 try {
481485 final ReadOnlyKeyValueStore <K , ValueTimestampHeaders <V >> store = IntegrationTestUtils
482- .getStore (STORE_NAME , kafkaStreams , QueryableStoreTypes . keyValueStore ());
486+ .getStore (STORE_NAME , kafkaStreams , new TimestampedKeyValueStoreWithHeadersType <> ());
483487
484488 if (store == null )
485489 return false ;
@@ -520,7 +524,7 @@ private <K, V> void processKeyValueWithTimestampAndHeadersAndVerify(final K key,
520524 () -> {
521525 try {
522526 final ReadOnlyKeyValueStore <K , ValueTimestampHeaders <V >> store = IntegrationTestUtils
523- .getStore (STORE_NAME , kafkaStreams , QueryableStoreTypes . keyValueStore ());
527+ .getStore (STORE_NAME , kafkaStreams , new TimestampedKeyValueStoreWithHeadersType <> ());
524528
525529 if (store == null )
526530 return false ;
@@ -546,7 +550,7 @@ private <K, V> void verifyLegacyValuesWithEmptyHeaders(final K key,
546550 () -> {
547551 try {
548552 final ReadOnlyKeyValueStore <K , ValueTimestampHeaders <V >> store = IntegrationTestUtils
549- .getStore (STORE_NAME , kafkaStreams , QueryableStoreTypes . keyValueStore ());
553+ .getStore (STORE_NAME , kafkaStreams , new TimestampedKeyValueStoreWithHeadersType <> ());
550554
551555 if (store == null )
552556 return false ;
@@ -565,30 +569,6 @@ private <K, V> void verifyLegacyValuesWithEmptyHeaders(final K key,
565569 "Could not get expected result in time." );
566570 }
567571
568- private <K , V > void verifyLegacyValuesWithEmptyHeaders (final K key ,
569- final V value ) throws Exception {
570- TestUtils .waitForCondition (
571- () -> {
572- try {
573- final ReadOnlyKeyValueStore <K , ValueTimestampHeaders <V >> store = IntegrationTestUtils
574- .getStore (STORE_NAME , kafkaStreams , QueryableStoreTypes .keyValueStore ());
575-
576- if (store == null )
577- return false ;
578-
579- final ValueTimestampHeaders <V > result = store .get (key );
580- return result != null
581- && result .value ().equals (value )
582- && result .headers ().toArray ().length == 0 ;
583- } catch (final Exception swallow ) {
584- LOG .error ("Error while verifying legacy value with empty headers" , swallow );
585- return false ;
586- }
587- },
588- 60_000L ,
589- "Could not get expected result in time." );
590- }
591-
592572 private static class KeyValueProcessor implements Processor <String , String , Void , Void > {
593573 private KeyValueStore <String , String > store ;
594574
@@ -906,7 +886,7 @@ private void verifyPlainWindowValueWithEmptyHeadersAndTimestamp(final String key
906886 TestUtils .waitForCondition (() -> {
907887 try {
908888 final ReadOnlyWindowStore <String , ValueTimestampHeaders <String >> store =
909- IntegrationTestUtils .getStore (WINDOW_STORE_NAME , kafkaStreams , QueryableStoreTypes . windowStore ());
889+ IntegrationTestUtils .getStore (WINDOW_STORE_NAME , kafkaStreams , new TimestampedWindowStoreWithHeadersType <> ());
910890
911891 if (store == null ) {
912892 return false ;
@@ -963,7 +943,7 @@ private void processPlainWindowedKeyValueWithHeadersAndVerify(final String key,
963943 TestUtils .waitForCondition (() -> {
964944 try {
965945 final ReadOnlyWindowStore <String , ValueTimestampHeaders <String >> store =
966- IntegrationTestUtils .getStore (WINDOW_STORE_NAME , kafkaStreams , QueryableStoreTypes . windowStore ());
946+ IntegrationTestUtils .getStore (WINDOW_STORE_NAME , kafkaStreams , new TimestampedWindowStoreWithHeadersType <> ());
967947
968948 if (store == null ) {
969949 return false ;
@@ -1049,7 +1029,7 @@ private void processWindowedKeyValueWithHeadersAndVerify(final String key,
10491029 TestUtils .waitForCondition (() -> {
10501030 try {
10511031 final ReadOnlyWindowStore <String , ValueTimestampHeaders <String >> store =
1052- IntegrationTestUtils .getStore (WINDOW_STORE_NAME , kafkaStreams , QueryableStoreTypes . windowStore ());
1032+ IntegrationTestUtils .getStore (WINDOW_STORE_NAME , kafkaStreams , new TimestampedWindowStoreWithHeadersType <> ());
10531033
10541034 if (store == null ) {
10551035 return false ;
@@ -1089,7 +1069,7 @@ private void verifyWindowValueWithEmptyHeaders(final String key,
10891069 TestUtils .waitForCondition (() -> {
10901070 try {
10911071 final ReadOnlyWindowStore <String , ValueTimestampHeaders <String >> store =
1092- IntegrationTestUtils .getStore (WINDOW_STORE_NAME , kafkaStreams , QueryableStoreTypes . windowStore ());
1072+ IntegrationTestUtils .getStore (WINDOW_STORE_NAME , kafkaStreams , new TimestampedWindowStoreWithHeadersType <> ());
10931073
10941074 if (store == null ) {
10951075 return false ;
@@ -1493,7 +1473,7 @@ public void shouldSuccessfullyDowngradeFromTimestampedWindowStoreWithHeadersAfte
14931473 private boolean windowStoreContainsKey (final String key , final long timestamp ) {
14941474 try {
14951475 final ReadOnlyWindowStore <String , ValueTimestampHeaders <String >> store =
1496- IntegrationTestUtils .getStore (WINDOW_STORE_NAME , kafkaStreams , QueryableStoreTypes . windowStore ());
1476+ IntegrationTestUtils .getStore (WINDOW_STORE_NAME , kafkaStreams , new TimestampedWindowStoreWithHeadersType <> ());
14971477
14981478 if (store == null ) {
14991479 return false ;
@@ -2037,4 +2017,52 @@ public void process(final Record<String, String> record) {
20372017 store .put (sessionKey , AggregationWithHeaders .make (record .value (), record .headers ()));
20382018 }
20392019 }
2020+
2021+ // ==================== Custom QueryableStoreTypes ====================
2022+
2023+ /**
2024+ * Custom QueryableStoreType for querying TimestampedKeyValueStoreWithHeaders directly
2025+ * without facade wrapping. This returns the full ValueTimestampHeaders wrapper.
2026+ */
2027+ private static class TimestampedKeyValueStoreWithHeadersType <K , V >
2028+ implements QueryableStoreType <ReadOnlyKeyValueStore <K , ValueTimestampHeaders <V >>> {
2029+
2030+ @ Override
2031+ public boolean accepts (final org .apache .kafka .streams .processor .StateStore stateStore ) {
2032+ // Accept stores that implement both TimestampedKeyValueStoreWithHeaders and ReadOnlyKeyValueStore
2033+ return stateStore instanceof TimestampedKeyValueStoreWithHeaders
2034+ && stateStore instanceof ReadOnlyKeyValueStore ;
2035+ }
2036+
2037+ @ Override
2038+ public ReadOnlyKeyValueStore <K , ValueTimestampHeaders <V >> create (
2039+ final StateStoreProvider storeProvider ,
2040+ final String storeName ) {
2041+ return new CompositeReadOnlyKeyValueStore <>(storeProvider , this , storeName );
2042+ }
2043+ }
2044+
2045+ /**
2046+ * Custom queryable store type for accessing TimestampedWindowStoreWithHeaders directly
2047+ * without facade wrapping. This returns the full ValueTimestampHeaders wrapper.
2048+ */
2049+ private static class TimestampedWindowStoreWithHeadersType <K , V >
2050+ implements QueryableStoreType <ReadOnlyWindowStore <K , ValueTimestampHeaders <V >>> {
2051+
2052+ @ Override
2053+ public boolean accepts (final org .apache .kafka .streams .processor .StateStore stateStore ) {
2054+ // Accept stores that implement both TimestampedWindowStoreWithHeaders and ReadOnlyWindowStore
2055+ return stateStore instanceof TimestampedWindowStoreWithHeaders
2056+ && stateStore instanceof ReadOnlyWindowStore ;
2057+ }
2058+
2059+ @ Override
2060+ public ReadOnlyWindowStore <K , ValueTimestampHeaders <V >> create (
2061+ final StateStoreProvider storeProvider ,
2062+ final String storeName ) {
2063+ return new CompositeReadOnlyWindowStore <>(storeProvider , this , storeName );
2064+ }
2065+ }
2066+
2067+
20402068}
0 commit comments