gnodet-bot commented on code in PR #29618: URL: https://github.com/apache/camel/pull/29618#discussion_r4536183574
########## components/camel-kafka/src/test/java/org/apache/camel/component/kafka/consumer/errorhandler/KafkaErrorStrategiesTest.java: ########## @@ -0,0 +1,113 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.component.kafka.consumer.errorhandler; + +import org.apache.camel.component.kafka.KafkaComponent; +import org.apache.camel.component.kafka.KafkaConfiguration; +import org.apache.camel.component.kafka.KafkaEndpoint; +import org.apache.camel.component.kafka.KafkaFetchRecords; +import org.apache.camel.component.kafka.PollExceptionStrategy; +import org.apache.camel.component.kafka.PollExceptionStrategyFactory; +import org.apache.camel.component.kafka.PollOnError; +import org.apache.kafka.clients.consumer.Consumer; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class KafkaErrorStrategiesTest { + + private final Consumer<byte[], byte[]> consumer = mock(Consumer.class); + + private KafkaEndpoint endpointWith(KafkaComponent component, PollOnError pollOnError) { + KafkaConfiguration configuration = mock(KafkaConfiguration.class); + when(configuration.getPollOnError()).thenReturn(pollOnError); + + KafkaEndpoint endpoint = mock(KafkaEndpoint.class); + when(endpoint.getComponent()).thenReturn(component); + when(endpoint.getConfiguration()).thenReturn(configuration); + return endpoint; + } + + @Test + void defaultsToTheBuiltInStrategyForThePollOnErrorConfiguration() { + KafkaComponent component = mock(KafkaComponent.class); + when(component.getPollExceptionStrategyFactory()).thenReturn(null); + when(component.getPollExceptionStrategy()).thenReturn(null); + KafkaEndpoint endpoint = endpointWith(component, PollOnError.RECONNECT); + + PollExceptionStrategy strategy = KafkaErrorStrategies.strategies( + mock(KafkaFetchRecords.class), endpoint, consumer); + + assertInstanceOf(ReconnectErrorStrategy.class, strategy); + } + + @Test + void honoursTheLegacyComponentStrategyWhenNoFactoryIsSet() { + PollExceptionStrategy legacy = mock(PollExceptionStrategy.class); + KafkaComponent component = mock(KafkaComponent.class); + when(component.getPollExceptionStrategyFactory()).thenReturn(null); + when(component.getPollExceptionStrategy()).thenReturn(legacy); + KafkaEndpoint endpoint = endpointWith(component, PollOnError.RECONNECT); + + PollExceptionStrategy strategy = KafkaErrorStrategies.strategies( + mock(KafkaFetchRecords.class), endpoint, consumer); + + assertSame(legacy, strategy); + } + + @Test + void factoryTakesPrecedenceOverTheLegacyComponentStrategy() { + PollExceptionStrategy legacy = mock(PollExceptionStrategy.class); + PollExceptionStrategy fromFactory = mock(PollExceptionStrategy.class); + PollExceptionStrategyFactory factory = mock(PollExceptionStrategyFactory.class); + when(factory.create(org.mockito.ArgumentMatchers.any(), org.mockito.ArgumentMatchers.any(), + org.mockito.ArgumentMatchers.any())).thenReturn(fromFactory); + + KafkaComponent component = mock(KafkaComponent.class); Review Comment: π‘ **Style nit:** `org.mockito.ArgumentMatchers.any()` is used as a fully-qualified name here while the other Mockito utilities (`mock`, `when`) are statically imported. Consider adding a static import for `any` to keep the style consistent: ```suggestion when(factory.create(any(), any(), any())).thenReturn(fromFactory); ``` ########## components/camel-kafka/src/main/java/org/apache/camel/component/kafka/consumer/errorhandler/KafkaErrorStrategies.java: ########## @@ -35,26 +34,19 @@ public static PollExceptionStrategy strategies( KafkaFetchRecords recordFetcher, KafkaEndpoint endpoint, Consumer<?, ?> consumer) { ObjectHelper.notNull(consumer, "consumer"); + // a factory is created per fetch thread and receives the owning fetch records task, so it supports the + // fetcher-bound built-in strategies and works correctly with multiple consumer threads + PollExceptionStrategyFactory factory = endpoint.getComponent().getPollExceptionStrategyFactory(); + if (factory != null) { + return factory.create(recordFetcher, endpoint, consumer); + } + + // a single strategy instance shared across all fetch threads; kept for backwards compatibility PollExceptionStrategy strategy = endpoint.getComponent().getPollExceptionStrategy(); if (strategy != null) { return strategy; } - PollOnError onError = endpoint.getConfiguration().getPollOnError(); - - switch (onError) { - case RETRY: - return new RetryErrorStrategy(); - case RECONNECT: - return new ReconnectErrorStrategy(recordFetcher); - case ERROR_HANDLER: - return new BridgeErrorStrategy(recordFetcher, consumer); - case DISCARD: - return new DiscardErrorStrategy(consumer); - case STOP: - return new StopErrorStrategy(recordFetcher); - default: - throw new RuntimeCamelException("The provided pollOnError strategy is invalid"); - } + return new DefaultPollExceptionStrategyFactory().create(recordFetcher, endpoint, consumer); } Review Comment: π‘ **Minor:** The fallback path allocates a new `DefaultPollExceptionStrategyFactory` on every call to `strategies()`. Since `DefaultPollExceptionStrategyFactory` is stateless, consider promoting it to a constant to avoid a small but unnecessary per-call allocation: ```suggestion return DEFAULT_FACTORY.create(recordFetcher, endpoint, consumer); ``` β¦with `private static final DefaultPollExceptionStrategyFactory DEFAULT_FACTORY = new DefaultPollExceptionStrategyFactory();` declared at the top of the class. (Alternatively, the method could also move into the factory itself.) ########## components/camel-kafka/src/main/java/org/apache/camel/component/kafka/PollExceptionStrategyFactory.java: ########## @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.component.kafka; + +import org.apache.kafka.clients.consumer.Consumer; + +/** + * Factory to create a {@link PollExceptionStrategy} for each Kafka consumer fetch thread. + * <p/> Review Comment: π‘ **Style nit:** `<p/>` is not valid HTML4/5 β use `<p>` (self-closing void elements are not valid in HTML) or simply drop it, as Javadoc tools handle bare paragraph breaks fine. ```suggestion * <p> ``` ########## components/camel-kafka/src/main/java/org/apache/camel/component/kafka/consumer/errorhandler/DefaultPollExceptionStrategyFactory.java: ########## @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.component.kafka.consumer.errorhandler; + +import org.apache.camel.RuntimeCamelException; +import org.apache.camel.component.kafka.KafkaEndpoint; +import org.apache.camel.component.kafka.KafkaFetchRecords; +import org.apache.camel.component.kafka.PollExceptionStrategy; +import org.apache.camel.component.kafka.PollExceptionStrategyFactory; +import org.apache.camel.component.kafka.PollOnError; +import org.apache.kafka.clients.consumer.Consumer; + +/** + * Default {@link PollExceptionStrategyFactory} that creates the built-in strategy selected by the endpoint's + * {@link PollOnError} configuration. + * <p/> Review Comment: π‘ **Style nit:** `<p/>` is not valid HTML4/5 β use `<p>` or drop it. ```suggestion * <p> ``` ########## components/camel-kafka/src/main/java/org/apache/camel/component/kafka/PollExceptionStrategyFactory.java: ########## @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.component.kafka; + +import org.apache.kafka.clients.consumer.Consumer; + +/** + * Factory to create a {@link PollExceptionStrategy} for each Kafka consumer fetch thread. + * <p/> + * Unlike setting a single {@link PollExceptionStrategy} on the component, which shares one instance across all consumer + * threads, this factory is invoked once per fetch thread and receives the owning fetch records task. This allows a + * custom strategy to be bound to its fetch records task, which is required by the built-in reconnect, stop, and bridge + * strategies to drive the consumer (for example to request a reconnect). It therefore also works correctly when a + * single endpoint uses multiple consumer threads (the <tt>consumersCount</tt> option). + */ +public interface PollExceptionStrategyFactory { + Review Comment: π‘ **Style nit:** `PollExceptionStrategyFactory` is a single-abstract-method interface β adding `@FunctionalInterface` documents the intent, enables lambda usage, and causes a compile error if a second abstract method is accidentally added: ```suggestion @FunctionalInterface public interface PollExceptionStrategyFactory { ``` ########## components/camel-kafka/src/main/java/org/apache/camel/component/kafka/PollExceptionStrategyFactory.java: ########## @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.component.kafka; + +import org.apache.kafka.clients.consumer.Consumer; + +/** + * Factory to create a {@link PollExceptionStrategy} for each Kafka consumer fetch thread. + * <p/> + * Unlike setting a single {@link PollExceptionStrategy} on the component, which shares one instance across all consumer + * threads, this factory is invoked once per fetch thread and receives the owning fetch records task. This allows a + * custom strategy to be bound to its fetch records task, which is required by the built-in reconnect, stop, and bridge + * strategies to drive the consumer (for example to request a reconnect). It therefore also works correctly when a + * single endpoint uses multiple consumer threads (the <tt>consumersCount</tt> option). + */ Review Comment: π‘ **Style nit:** `<tt>` is deprecated in HTML5 β use `{@code consumersCount}` instead: ```suggestion * single endpoint uses multiple consumer threads (the {@code consumersCount} option). ``` -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
