|
108 | 108 | * @author Nakul Mishra
|
109 | 109 | * @author Artem Bilan
|
110 | 110 | * @author Chris Gilbert
|
| 111 | + * @author Thomas Strauß |
111 | 112 | */
|
112 | 113 | public class DefaultKafkaProducerFactory<K, V> extends KafkaResourceFactory
|
113 | 114 | implements ProducerFactory<K, V>, ApplicationContextAware,
|
114 |
| - BeanNameAware, ApplicationListener<ContextStoppedEvent>, DisposableBean { |
| 115 | + BeanNameAware, ApplicationListener<ContextStoppedEvent>, DisposableBean { |
115 | 116 |
|
116 | 117 | private static final LogAccessor LOGGER = new LogAccessor(LogFactory.getLog(DefaultKafkaProducerFactory.class));
|
117 | 118 |
|
@@ -360,6 +361,63 @@ public void setMaxAge(Duration maxAge) {
|
360 | 361 | this.maxAge = maxAge.toMillis();
|
361 | 362 | }
|
362 | 363 |
|
| 364 | + /** |
| 365 | + * Copy properties of the instance and the given properties to create a new producer factory. |
| 366 | + * <p>If the {@link org.springframework.kafka.core.DefaultKafkaProducerFactory} makes a |
| 367 | + * copy of itself, the transaction id prefix is recovered from the properties. If |
| 368 | + * you want to change the ID config, add a new |
| 369 | + * {@link org.apache.kafka.clients.producer.ProducerConfig#TRANSACTIONAL_ID_CONFIG} |
| 370 | + * key to the override config.</p> |
| 371 | + * @param overrideProperties the properties to be applied to the new factory |
| 372 | + * @return {@link org.springframework.kafka.core.DefaultKafkaProducerFactory} with |
| 373 | + * properties applied |
| 374 | + */ |
| 375 | + @Override |
| 376 | + public ProducerFactory<K, V> copyWithConfigurationOverride(Map<String, Object> overrideProperties) { |
| 377 | + Map<String, Object> producerProperties = new HashMap<>(getConfigurationProperties()); |
| 378 | + producerProperties.putAll(overrideProperties); |
| 379 | + producerProperties = ensureExistingTransactionIdPrefixInProperties(producerProperties); |
| 380 | + DefaultKafkaProducerFactory<K, V> newFactory = |
| 381 | + new DefaultKafkaProducerFactory<>(producerProperties, |
| 382 | + getKeySerializerSupplier(), |
| 383 | + getValueSerializerSupplier()); |
| 384 | + newFactory.setPhysicalCloseTimeout((int) getPhysicalCloseTimeout().getSeconds()); |
| 385 | + newFactory.setProducerPerConsumerPartition(isProducerPerConsumerPartition()); |
| 386 | + newFactory.setProducerPerThread(isProducerPerThread()); |
| 387 | + for (ProducerPostProcessor<K, V> templatePostProcessor : getPostProcessors()) { |
| 388 | + newFactory.addPostProcessor(templatePostProcessor); |
| 389 | + } |
| 390 | + for (ProducerFactory.Listener<K, V> templateListener : getListeners()) { |
| 391 | + newFactory.addListener(templateListener); |
| 392 | + } |
| 393 | + return newFactory; |
| 394 | + } |
| 395 | + |
| 396 | + |
| 397 | + /** |
| 398 | + * Ensures that the returned properties map contains a transaction id prefix. |
| 399 | + * The {@link org.springframework.kafka.core.DefaultKafkaProducerFactory} |
| 400 | + * modifies the local properties copy, the txn key is removed and |
| 401 | + * stored locally in a property. To make a proper copy of the properties in a |
| 402 | + * new factory, the transactionId has to be reinserted prior use. |
| 403 | + * The incoming properties are checked for a transactionId key. If none is |
| 404 | + * there, the one existing in the factory is added. |
| 405 | + * @param producerProperties the properties to be used for the new factory |
| 406 | + * @return the producerProperties or a copy with the transaction ID set |
| 407 | + */ |
| 408 | + private Map<String, Object> ensureExistingTransactionIdPrefixInProperties(Map<String, Object> producerProperties) { |
| 409 | + String transactionIdPrefix = getTransactionIdPrefix(); |
| 410 | + if (StringUtils.hasText(transactionIdPrefix)) { |
| 411 | + if (!producerProperties.containsKey(ProducerConfig.TRANSACTIONAL_ID_CONFIG)) { |
| 412 | + Map<String, Object> producerPropertiesWithTxnId = new HashMap<>(producerProperties); |
| 413 | + producerPropertiesWithTxnId.put(ProducerConfig.TRANSACTIONAL_ID_CONFIG, transactionIdPrefix); |
| 414 | + return producerPropertiesWithTxnId; |
| 415 | + } |
| 416 | + } |
| 417 | + |
| 418 | + return producerProperties; |
| 419 | + } |
| 420 | + |
363 | 421 | /**
|
364 | 422 | * Add a listener.
|
365 | 423 | * @param listener the listener.
|
@@ -417,8 +475,8 @@ public void updateConfigs(Map<String, Object> updates) {
|
417 | 475 | Assert.isTrue(entry.getValue() instanceof String, () -> "'" + ProducerConfig.TRANSACTIONAL_ID_CONFIG
|
418 | 476 | + "' must be a String, not a " + entry.getClass().getName());
|
419 | 477 | Assert.isTrue(this.transactionIdPrefix != null
|
420 |
| - ? entry.getValue() != null |
421 |
| - : entry.getValue() == null, |
| 478 | + ? entry.getValue() != null |
| 479 | + : entry.getValue() == null, |
422 | 480 | "Cannot change transactional capability");
|
423 | 481 | this.transactionIdPrefix = (String) entry.getValue();
|
424 | 482 | }
|
@@ -694,7 +752,7 @@ boolean cacheReturner(CloseSafeProducer<K, V> producerToRemove, Duration timeout
|
694 | 752 | BlockingQueue<CloseSafeProducer<K, V>> txIdCache = getCache(producerToRemove.txIdPrefix);
|
695 | 753 | if (producerToRemove.epoch != this.epoch.get()
|
696 | 754 | || (txIdCache != null && !txIdCache.contains(producerToRemove)
|
697 |
| - && !txIdCache.offer(producerToRemove))) { |
| 755 | + && !txIdCache.offer(producerToRemove))) { |
698 | 756 | producerToRemove.closeDelegate(timeout, this.listeners);
|
699 | 757 | return true;
|
700 | 758 | }
|
@@ -942,7 +1000,7 @@ public void abortTransaction() throws ProducerFencedException {
|
942 | 1000 | LOGGER.debug(() -> toString() + " abortTransaction()");
|
943 | 1001 | if (this.producerFailed != null) {
|
944 | 1002 | LOGGER.debug(() -> "abortTransaction ignored - previous txFailed: " + this.producerFailed.getMessage()
|
945 |
| - + ": " + this); |
| 1003 | + + ": " + this); |
946 | 1004 | }
|
947 | 1005 | else {
|
948 | 1006 | try {
|
|
0 commit comments