From bf544b39c0af940c200e822eb6a1e2fb4163340c Mon Sep 17 00:00:00 2001 From: Anders Swanson Date: Thu, 20 Mar 2025 07:59:30 -0700 Subject: [PATCH 1/4] Stream Binder transactions Signed-off-by: Anders Swanson --- .../cstream/JMSMessageChannelBinder.java | 278 ++++---- .../cstream/TxEventQQueueProvisioner.java | 454 ++++++------- .../config/JmsBinderAutoConfiguration.java | 14 +- .../config/JmsBinderGlobalConfiguration.java | 171 ++--- .../cstream/config/JmsBindingProperties.java | 38 +- .../cstream/config/JmsConsumerProperties.java | 80 ++- .../config/JmsExtendedBindingProperties.java | 41 +- .../cstream/config/JmsProducerProperties.java | 28 +- .../config/TxEventQJmsConfiguration.java | 80 +-- .../oracle/cstream/plsql/OracleDBUtils.java | 197 +++--- .../provisioning/JmsConsumerDestination.java | 54 +- .../provisioning/JmsProducerDestination.java | 86 +-- .../CustomSerializationMessageConverter.java | 112 ++-- .../cstream/serialize/Deserializer.java | 12 +- .../oracle/cstream/serialize/Serializer.java | 12 +- .../utils/AnonymousNamingStrategy.java | 14 +- .../utils/Base64UrlNamingStrategy.java | 65 +- .../utils/DestinationNameResolver.java | 26 +- .../utils/JmsMessageDrivenChannelAdapter.java | 204 +++--- ...JmsMessageDrivenChannelAdapterFactory.java | 324 ++++----- .../JmsSendingMessageHandlerFactory.java | 82 +-- .../utils/ListenerContainerFactory.java | 83 +-- .../cstream/utils/MessageRecoverer.java | 24 +- ...artitionAwareJmsSendingMessageHandler.java | 417 ++++++------ .../utils/RepublishMessageRecoverer.java | 172 +++-- .../utils/SpecCompliantJmsHeaderMapper.java | 124 ++-- .../utils/TEQBatchMessageListener.java | 554 ++++++++-------- .../TEQBatchMessageListenerContainer.java | 627 +++++++++--------- .../utils/TEQMessageListenerContainer.java | 77 ++- .../utils/TxEventQBinderHeaderConstants.java | 9 + .../cstream/utils/TxEventQMessageBuilder.java | 257 +++++++ .../oracle/cstream/utils/TxEventQUtils.java | 34 + .../main/resources/META-INF/spring.binders | 2 +- 33 files changed, 2568 insertions(+), 2184 deletions(-) create mode 100644 database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/TxEventQBinderHeaderConstants.java create mode 100644 database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/TxEventQMessageBuilder.java create mode 100644 database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/TxEventQUtils.java diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/JMSMessageChannelBinder.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/JMSMessageChannelBinder.java index 74734b63..16d485d0 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/JMSMessageChannelBinder.java +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/JMSMessageChannelBinder.java @@ -1,10 +1,10 @@ /* - ** TxEventQ Support for Spring Cloud Stream - ** Copyright (c) 2023, 2024 Oracle and/or its affiliates. - ** - ** This file has been modified by Oracle Corporation. - ** - */ +** TxEventQ Support for Spring Cloud Stream +** Copyright (c) 2023, 2024 Oracle and/or its affiliates. +** +** This file has been modified by Oracle Corporation. +** +*/ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -53,138 +53,138 @@ public class JMSMessageChannelBinder - extends AbstractMessageChannelBinder, ExtendedProducerProperties, ProvisioningProvider, ExtendedProducerProperties>> - implements - ExtendedPropertiesBinder { - - private JmsExtendedBindingProperties extendedBindingProperties = new JmsExtendedBindingProperties(); - - private final JmsSendingMessageHandlerFactory jmsSendingMessageHandlerFactory; - private final JmsMessageDrivenChannelAdapterFactory jmsMessageDrivenChannelAdapterFactory; - private final ConnectionFactory connectionFactory; - - private final DestinationResolver destinationResolver; - - private DestinationNameResolver destinationNameResolver; - - public JMSMessageChannelBinder( - ProvisioningProvider, ExtendedProducerProperties> provisioningProvider, - JmsSendingMessageHandlerFactory jmsSendingMessageHandlerFactory, - JmsMessageDrivenChannelAdapterFactory jmsMessageDrivenChannelAdapterFactory, - JmsTemplate jmsTemplate, - ConnectionFactory connectionFactory - ) { - super(null, provisioningProvider); - this.jmsSendingMessageHandlerFactory = jmsSendingMessageHandlerFactory; - this.jmsMessageDrivenChannelAdapterFactory = - jmsMessageDrivenChannelAdapterFactory; - this.connectionFactory = connectionFactory; - this.destinationResolver = jmsTemplate.getDestinationResolver(); - } - - public void setExtendedBindingProperties( - JmsExtendedBindingProperties extendedBindingProperties - ) { - this.extendedBindingProperties = extendedBindingProperties; - } - - public void setDestinationNameResolver(DestinationNameResolver destinationNameResolver) { - this.destinationNameResolver = destinationNameResolver; - } - - @Override - protected MessageHandler createProducerMessageHandler( - ProducerDestination producerDestination, - ExtendedProducerProperties producerProperties, - MessageChannel errorChannel - ) throws Exception { - Topic topic = null; - try (Connection conn = connectionFactory.createConnection()) { - Session session = conn.createSession(true, 1); - - String destination = producerDestination.getName(); - topic = (Topic) destinationResolver.resolveDestinationName( - session, - destination, - true); - } - - if (producerProperties.isUseNativeEncoding()) { - return this.jmsSendingMessageHandlerFactory - .build(topic, errorChannel, - producerProperties.getHeaderMode() == null - || producerProperties.getHeaderMode().equals(HeaderMode.headers), - producerProperties.getExtension().getSerializer(), - ((JmsProducerDestination) producerDestination).getDBVersion()); - } - - return this.jmsSendingMessageHandlerFactory - .build(topic, errorChannel, - producerProperties.getHeaderMode() == null || - producerProperties.getHeaderMode().equals(HeaderMode.headers), - null, - ((JmsProducerDestination) producerDestination).getDBVersion()); - } - - @Override - protected org.springframework.integration.core.MessageProducer createConsumerEndpoint( - ConsumerDestination consumerDestination, - String group, - ExtendedConsumerProperties properties - ) throws Exception { - group = this.destinationNameResolver.resolveGroupName(group); - Topic topic = null; - try (Connection conn = connectionFactory.createConnection()) { - Session session = conn.createSession(true, 1); - - topic = (Topic) destinationResolver.resolveDestinationName( - session, - consumerDestination.getName(), - true); - } - - - RetryTemplate retryTemplate = buildRetryTemplate(properties); - ErrorInfrastructure errorInfrastructure = registerErrorInfrastructure(consumerDestination, group, properties); - RecoveryCallback recoveryCallback = errorInfrastructure - .getRecoverer(); - - return jmsMessageDrivenChannelAdapterFactory.build( - topic, - group, - retryTemplate, - recoveryCallback, - errorInfrastructure.getErrorChannel(), - properties, - ((JmsConsumerDestination) consumerDestination).getDBVersion() - ); - } - - @Override - public JmsConsumerProperties getExtendedConsumerProperties( - String channelName - ) { - return this.extendedBindingProperties.getExtendedConsumerProperties( - channelName - ); - } - - @Override - public JmsProducerProperties getExtendedProducerProperties( - String channelName - ) { - return this.extendedBindingProperties.getExtendedProducerProperties( - channelName - ); - } - - @Override - public String getDefaultsPrefix() { - return this.extendedBindingProperties.getDefaultsPrefix(); - } - - @Override - public Class getExtendedPropertiesEntryClass() { - return this.extendedBindingProperties.getExtendedPropertiesEntryClass(); + extends AbstractMessageChannelBinder, ExtendedProducerProperties, ProvisioningProvider, ExtendedProducerProperties>> + implements + ExtendedPropertiesBinder { + + private JmsExtendedBindingProperties extendedBindingProperties = new JmsExtendedBindingProperties(); + + private final JmsSendingMessageHandlerFactory jmsSendingMessageHandlerFactory; + private final JmsMessageDrivenChannelAdapterFactory jmsMessageDrivenChannelAdapterFactory; + private final ConnectionFactory connectionFactory; + + private final DestinationResolver destinationResolver; + + private DestinationNameResolver destinationNameResolver; + + public JMSMessageChannelBinder( + ProvisioningProvider, ExtendedProducerProperties> provisioningProvider, + JmsSendingMessageHandlerFactory jmsSendingMessageHandlerFactory, + JmsMessageDrivenChannelAdapterFactory jmsMessageDrivenChannelAdapterFactory, + JmsTemplate jmsTemplate, + ConnectionFactory connectionFactory + ) { + super(null, provisioningProvider); + this.jmsSendingMessageHandlerFactory = jmsSendingMessageHandlerFactory; + this.jmsMessageDrivenChannelAdapterFactory = + jmsMessageDrivenChannelAdapterFactory; + this.connectionFactory = connectionFactory; + this.destinationResolver = jmsTemplate.getDestinationResolver(); + } + + public void setExtendedBindingProperties( + JmsExtendedBindingProperties extendedBindingProperties + ) { + this.extendedBindingProperties = extendedBindingProperties; + } + + public void setDestinationNameResolver(DestinationNameResolver destinationNameResolver) { + this.destinationNameResolver = destinationNameResolver; + } + + @Override + protected MessageHandler createProducerMessageHandler( + ProducerDestination producerDestination, + ExtendedProducerProperties producerProperties, + MessageChannel errorChannel + ) throws Exception { + Topic topic = null; + try(Connection conn = connectionFactory.createConnection()) { + Session session = conn.createSession(true, 1); + + String destination = producerDestination.getName(); + topic = (Topic) destinationResolver.resolveDestinationName( + session, + destination, + true); + } + + if(producerProperties.isUseNativeEncoding()) { + return this.jmsSendingMessageHandlerFactory + .build(topic, errorChannel, + producerProperties.getHeaderMode() == null + || producerProperties.getHeaderMode().equals(HeaderMode.headers), + producerProperties.getExtension().getSerializer(), + ((JmsProducerDestination)producerDestination).getDBVersion()); } + + return this.jmsSendingMessageHandlerFactory + .build(topic, errorChannel, + producerProperties.getHeaderMode() == null || + producerProperties.getHeaderMode().equals(HeaderMode.headers), + null, + ((JmsProducerDestination)producerDestination).getDBVersion()); + } + + @Override + protected org.springframework.integration.core.MessageProducer createConsumerEndpoint( + ConsumerDestination consumerDestination, + String group, + ExtendedConsumerProperties properties + ) throws Exception { + group = this.destinationNameResolver.resolveGroupName(group); + Topic topic = null; + try(Connection conn = connectionFactory.createConnection()) { + Session session = conn.createSession(true, 1); + + topic = (Topic) destinationResolver.resolveDestinationName( + session, + consumerDestination.getName(), + true); + } + + + RetryTemplate retryTemplate = buildRetryTemplate(properties); + ErrorInfrastructure errorInfrastructure = registerErrorInfrastructure(consumerDestination, group, properties); + RecoveryCallback recoveryCallback = errorInfrastructure + .getRecoverer(); + + return jmsMessageDrivenChannelAdapterFactory.build( + topic, + group, + retryTemplate, + recoveryCallback, + errorInfrastructure.getErrorChannel(), + properties, + ((JmsConsumerDestination)consumerDestination).getDBVersion() + ); + } + + @Override + public JmsConsumerProperties getExtendedConsumerProperties( + String channelName + ) { + return this.extendedBindingProperties.getExtendedConsumerProperties( + channelName + ); + } + + @Override + public JmsProducerProperties getExtendedProducerProperties( + String channelName + ) { + return this.extendedBindingProperties.getExtendedProducerProperties( + channelName + ); + } + + @Override + public String getDefaultsPrefix() { + return this.extendedBindingProperties.getDefaultsPrefix(); + } + + @Override + public Class getExtendedPropertiesEntryClass() { + return this.extendedBindingProperties.getExtendedPropertiesEntryClass(); + } } diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/TxEventQQueueProvisioner.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/TxEventQQueueProvisioner.java index dd772d9a..4ddcb71d 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/TxEventQQueueProvisioner.java +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/TxEventQQueueProvisioner.java @@ -1,9 +1,9 @@ /* - ** TxEventQ Support for Spring Cloud Stream - ** Copyright (c) 2023, 2024 Oracle and/or its affiliates. - ** - ** This file has been modified by Oracle Corporation. - */ +** TxEventQ Support for Spring Cloud Stream +** Copyright (c) 2023, 2024 Oracle and/or its affiliates. +** +** This file has been modified by Oracle Corporation. +*/ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -36,7 +36,6 @@ import jakarta.jms.Topic; import java.sql.SQLException; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.cloud.stream.binder.ExtendedConsumerProperties; @@ -47,225 +46,228 @@ import org.springframework.jms.support.JmsUtils; public class TxEventQQueueProvisioner - implements - ProvisioningProvider, ExtendedProducerProperties> { - - private final ConnectionFactory connectionFactory; - - private final Logger logger = LoggerFactory.getLogger(TxEventQQueueProvisioner.class); - - private OracleDBUtils dbutils; - - public void setDBUtils(OracleDBUtils dbutils) { - this.dbutils = dbutils; - } - - public TxEventQQueueProvisioner( - ConnectionFactory connectionFactory, - OracleDBUtils dbutils - ) { - this.connectionFactory = connectionFactory; - this.dbutils = dbutils; - } - - @Override - public ProducerDestination provisionProducerDestination( - final String name, - ExtendedProducerProperties properties - ) { - // Step 1: Get correct topic name - String topicName = formatName(name); - logger.info("Binding topic {} for {}", topicName, properties.getBindingName()); - - // Step 2: Provision topic for producer binding - // along with required groups (if any) - Topic topic = provisionProducerTopic(topicName, properties); - - // Step 3: Return the required ProducerDestination - return new JmsProducerDestination(topic, properties.getPartitionCount(), this.dbutils.getDBVersion()); - } - - @Override - public ConsumerDestination provisionConsumerDestination( - String name, - String group, - ExtendedConsumerProperties properties - ) { - if (properties.isMultiplex()) { - throw new - IllegalArgumentException("The property 'multiplex:true' is not supported."); - } - if (properties.getInstanceIndexList() != null && - !properties.getInstanceIndexList().isEmpty()) { - throw new - IllegalArgumentException("The property 'instanceIndexList' is not supported."); - } - - return provideSingleConsumerDestination(name, group, properties); - } - - /* - * Helper function to bind one consumer destination name - */ - public ConsumerDestination provideSingleConsumerDestination(String name, - String group, - ExtendedConsumerProperties properties) { - String topicName = formatName(name); - logger.info("Binding topic {} for {} for group {}", topicName, properties.getBindingName(), group); - - // Step 2: Provision topic for consumer binding - provisionConsumerTopic(topicName, properties); - - // Step 3: Return the required ConsumerDestination - return new JmsConsumerDestination(topicName, this.dbutils.getDBVersion()); - } - - /* - * Utility function to allocate necessary resources - * for a producer destination with the configured properties - * If the topic does not exist, it creates one with the configured - * partition count. - * By default, if the producer is not partitioned, then - * topic with only one partition is created. - * Throws an error if topic exists and is partitioned - * but the configured partitionCount does not match - * actual partitions on the topic - */ - private Topic provisionProducerTopic(String topicName, - ExtendedProducerProperties properties) { - Connection aQConnection = null; - Session session = null; - Topic topic = null; - try { - aQConnection = connectionFactory.createConnection(); - session = aQConnection.createSession(true, Session.CLIENT_ACKNOWLEDGE); - - topic = getTopicInstance(topicName, session); - - if (topic == null) { - logger.info("Creating topic: {} as it does not exist.", topicName); - // create Key based TEQ with specified number of partitions - int partitionNum = properties.getPartitionCount(); - createTopicWithPartitions(topicName, partitionNum); - topic = getTopicInstance(topicName, session); - } else if (properties.isPartitioned()) { - // topic exists - // match its partition Count - int partitionNum = properties.getPartitionCount(); - checkPartitionCount(topicName, partitionNum); - } - - // create necessary required subscriptions - for (String requiredGroup : properties.getRequiredGroups()) { - session.createDurableSubscriber(topic, requiredGroup); - } - - JmsUtils.commitIfNecessary(session); - } catch (JMSException e) { - throw new IllegalStateException(e); - } finally { - JmsUtils.closeSession(session); - JmsUtils.closeConnection(aQConnection); - } - - return topic; - } - - private Topic provisionConsumerTopic(String topicName, - ExtendedConsumerProperties properties) { - Connection aQConnection = null; - Session session = null; - Topic topic = null; - try { - aQConnection = connectionFactory.createConnection(); - session = aQConnection.createSession(true, Session.CLIENT_ACKNOWLEDGE); - - topic = getTopicInstance(topicName, session); - - if (topic == null) { - logger.info("Creating topic: {} as it does not exist.", topicName); - createTopicWithInstances(topicName, properties); - topic = getTopicInstance(topicName, session); - } else if (properties.isPartitioned()) { - // topic exists - // match its instance Count - int instCnt = properties.getInstanceCount(); - checkPartitionCount(topicName, instCnt); - } - - /* Set instance index to -1 if not partitioned */ - if (!properties.isPartitioned()) properties.setInstanceIndex(-1); - - JmsUtils.commitIfNecessary(session); - } catch (JMSException e) { - throw new IllegalStateException(e); - } finally { - JmsUtils.closeSession(session); - JmsUtils.closeConnection(aQConnection); - } - - return topic; - } - - private void createTopicWithPartitions(String topicName, int pCount) { - try { - this.dbutils.createKBQ(topicName, pCount); - } catch (SQLException e) { - throw new IllegalArgumentException("Error when creating procedures & topic.", e); - } - } - - private void createTopicWithInstances(String topicName, - ExtendedConsumerProperties properties) { - try { - if (properties.isPartitioned()) { - // the consumer is partitioned - // create KBQ with configured instance count - int instCnt = properties.getInstanceCount(); - this.dbutils.createKBQ(topicName, instCnt); - } else { - // The consumer is not partitioned - // create default KBQ with 1 partition - this.dbutils.createKBQ(topicName, 1); - } - } catch (SQLException e) { - throw new IllegalArgumentException("Error when creating topic.", e); - } - } - - private void checkPartitionCount(String topicName, int pCount) { - try { - int topicParts = this.dbutils.getTopicPartitions(topicName); - if (pCount != topicParts) { - throw new IllegalArgumentException("Partition Count mismatch, Expected: " - + topicParts + ", Found: " + pCount); - } - } catch (SQLException e) { - throw new IllegalArgumentException("Error when checking partitionCount on topics.", e); - } - } - - - private String formatName(String name) { - // surround with double quotes - // to use exact name for topic - return "\"" + name + "\""; - } - - /* Utility function to check if the topic exists or not - * If the topic does not exist, returns null - * Otherwise returns the actual Topic object - * associated with the name topicName - */ - private Topic getTopicInstance(String topicName, Session session) { - Topic topic = null; - try { - topic = session.createTopic(topicName); - } catch (JMSException e) { - logger.info("Exception: {}", e.getMessage()); - return null; - } - return topic; - } + implements + ProvisioningProvider, ExtendedProducerProperties> { + + private final ConnectionFactory connectionFactory; + + private final Logger logger = LoggerFactory.getLogger(TxEventQQueueProvisioner.class); + + private OracleDBUtils dbutils; + + public void setDBUtils(OracleDBUtils dbutils) { + this.dbutils = dbutils; + } + + public TxEventQQueueProvisioner( + ConnectionFactory connectionFactory, + OracleDBUtils dbutils + ) { + this.connectionFactory = connectionFactory; + this.dbutils = dbutils; + } + + @Override + public ProducerDestination provisionProducerDestination( + final String name, + ExtendedProducerProperties properties + ) { + // Step 1: Get correct topic name + String topicName = formatName(name); + logger.info("Binding topic {} for {}", topicName, properties.getBindingName()); + + // Step 2: Provision topic for producer binding + // along with required groups (if any) + Topic topic = provisionProducerTopic(topicName, properties); + + // Step 3: Return the required ProducerDestination + return new JmsProducerDestination(topic, properties.getPartitionCount(), this.dbutils.getDBVersion()); + } + + @Override + public ConsumerDestination provisionConsumerDestination( + String name, + String group, + ExtendedConsumerProperties properties + ) { + if(properties.isMultiplex()) { + throw new + IllegalArgumentException("The property 'multiplex:true' is not supported."); + } + if(properties.getInstanceIndexList() != null && + !properties.getInstanceIndexList().isEmpty()) { + throw new + IllegalArgumentException("The property 'instanceIndexList' is not supported."); + } + + return provideSingleConsumerDestination(name, group, properties); + } + + /* + * Helper function to bind one consumer destination name + */ + public ConsumerDestination provideSingleConsumerDestination(String name, + String group, + ExtendedConsumerProperties properties) { + String topicName = formatName(name); + logger.info("Binding topic {} for {} for group {}", topicName, properties.getBindingName(), group); + + // Step 2: Provision topic for consumer binding + provisionConsumerTopic(topicName, properties); + + // Step 3: Return the required ConsumerDestination + return new JmsConsumerDestination(topicName, this.dbutils.getDBVersion()); + } + + /* + * Utility function to allocate necessary resources + * for a producer destination with the configured properties + * If the topic does not exist, it creates one with the configured + * partition count. + * By default, if the producer is not partitioned, then + * topic with only one partition is created. + * Throws an error if topic exists and is partitioned + * but the configured partitionCount does not match + * actual partitions on the topic + */ + private Topic provisionProducerTopic(String topicName, + ExtendedProducerProperties properties) { + Connection aQConnection = null; + Session session = null; + Topic topic = null; + try { + aQConnection = connectionFactory.createConnection(); + session = aQConnection.createSession(true, Session.CLIENT_ACKNOWLEDGE); + + topic = getTopicInstance(topicName, session); + + if(topic == null) { + logger.info("Creating topic: {} as it does not exist.", topicName); + // create Key based TEQ with specified number of partitions + int partitionNum = properties.getPartitionCount(); + createTopicWithPartitions(topicName, partitionNum); + topic = getTopicInstance(topicName, session); + } else if(properties.isPartitioned()){ + // topic exists + // match its partition Count + int partitionNum = properties.getPartitionCount(); + checkPartitionCount(topicName, partitionNum); + } + + // create necessary required subscriptions + for(String requiredGroup: properties.getRequiredGroups()) { + session.createDurableSubscriber(topic, requiredGroup); + } + + JmsUtils.commitIfNecessary(session); + } catch (JMSException e) { + throw new IllegalStateException(e); + } finally { + JmsUtils.closeSession(session); + JmsUtils.closeConnection(aQConnection); + } + + return topic; + } + + private Topic provisionConsumerTopic(String topicName, + ExtendedConsumerProperties properties) { + Connection aQConnection = null; + Session session = null; + Topic topic = null; + try { + aQConnection = connectionFactory.createConnection(); + session = aQConnection.createSession(true, Session.CLIENT_ACKNOWLEDGE); + + topic = getTopicInstance(topicName, session); + + if(topic == null) { + logger.info("Creating topic: {} as it does not exist.", topicName); + createTopicWithInstances(topicName, properties); + topic = getTopicInstance(topicName, session); + } else if(properties.isPartitioned()){ + // topic exists + // match its instance Count + int instCnt = properties.getInstanceCount(); + checkPartitionCount(topicName, instCnt); + } + + /* Set instance index to -1 if not partitioned */ + if(!properties.isPartitioned()) properties.setInstanceIndex(-1); + + JmsUtils.commitIfNecessary(session); + } catch (JMSException e) { + throw new IllegalStateException(e); + } finally { + JmsUtils.closeSession(session); + JmsUtils.closeConnection(aQConnection); + } + + return topic; + } + + private void createTopicWithPartitions(String topicName, int pCount) { + try { + this.dbutils.createKBQ(topicName, pCount); + } catch (SQLException e) { + throw new IllegalArgumentException("Error when creating procedures & topic.", e); + } + } + + private void createTopicWithInstances(String topicName, + ExtendedConsumerProperties properties) { + try { + if(properties.isPartitioned()) { + // the consumer is partitioned + // create KBQ with configured instance count + int instCnt = properties.getInstanceCount(); + this.dbutils.createKBQ(topicName, instCnt); + } + else { + // The consumer is not partitioned + // create default KBQ with 1 partition + this.dbutils.createKBQ(topicName, 1); + } + } + catch (SQLException e) { + throw new IllegalArgumentException("Error when creating topic.", e); + } + } + + private void checkPartitionCount(String topicName, int pCount) { + try { + int topicParts = this.dbutils.getTopicPartitions(topicName); + if(pCount != topicParts) { + throw new IllegalArgumentException("Partition Count mismatch, Expected: " + + topicParts + ", Found: " + pCount); + } + } catch(SQLException e) { + throw new IllegalArgumentException("Error when checking partitionCount on topics.", e); + } + } + + + + private String formatName(String name) { + // surround with double quotes + // to use exact name for topic + return "\"" + name + "\""; + } + + /* Utility function to check if the topic exists or not + * If the topic does not exist, returns null + * Otherwise returns the actual Topic object + * associated with the name topicName + */ + private Topic getTopicInstance(String topicName, Session session) { + Topic topic = null; + try { + topic = session.createTopic(topicName); + } catch (JMSException e) { + logger.info("Exception: {}", e.getMessage()); + return null; + } + return topic; + } } diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/config/JmsBinderAutoConfiguration.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/config/JmsBinderAutoConfiguration.java index eececf22..cc850a57 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/config/JmsBinderAutoConfiguration.java +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/config/JmsBinderAutoConfiguration.java @@ -1,10 +1,10 @@ /* - ** TxEventQ Support for Spring Cloud Stream - ** Copyright (c) 2023, 2024 Oracle and/or its affiliates. - ** - ** This file has been modified by Oracle Corporation. - ** - */ +** TxEventQ Support for Spring Cloud Stream +** Copyright (c) 2023, 2024 Oracle and/or its affiliates. +** +** This file has been modified by Oracle Corporation. +** +*/ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -34,5 +34,5 @@ @ConditionalOnMissingBean(Binder.class) @Import({JmsBinderGlobalConfiguration.class}) public class JmsBinderAutoConfiguration { - + } diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/config/JmsBinderGlobalConfiguration.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/config/JmsBinderGlobalConfiguration.java index bfbb0265..489998a2 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/config/JmsBinderGlobalConfiguration.java +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/config/JmsBinderGlobalConfiguration.java @@ -1,9 +1,9 @@ /* - ** TxEventQ Support for Spring Cloud Stream - ** Copyright (c) 2023, 2024 Oracle and/or its affiliates. - ** - ** This file has been modified by Oracle Corporation. - */ +** TxEventQ Support for Spring Cloud Stream +** Copyright (c) 2023, 2024 Oracle and/or its affiliates. +** +** This file has been modified by Oracle Corporation. +*/ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -39,88 +39,89 @@ @Configuration public class JmsBinderGlobalConfiguration { - private final ConnectionFactory connectionFactory; - - public JmsBinderGlobalConfiguration(ConnectionFactory connectionFactory) { - this.connectionFactory = connectionFactory; - } - - @Bean - public DestinationNameResolver queueNameResolver() { - return new DestinationNameResolver( - new Base64UrlNamingStrategy("anonymous_") - ); - } - - @Bean - @ConditionalOnMissingBean(MessageRecoverer.class) - MessageRecoverer defaultMessageRecoverer() { - return new RepublishMessageRecoverer( - jmsTemplate(), - new SpecCompliantJmsHeaderMapper() - ); - } - - @Bean - ListenerContainerFactory listenerContainerFactory() { - return new ListenerContainerFactory(connectionFactory); - } + private ConnectionFactory connectionFactory; + + public JmsBinderGlobalConfiguration(ConnectionFactory connectionFactory) { + this.connectionFactory = connectionFactory; + } + + @Bean + public DestinationNameResolver queueNameResolver() { + return new DestinationNameResolver( + new Base64UrlNamingStrategy("anonymous_") + ); + } + + @Bean + @ConditionalOnMissingBean(MessageRecoverer.class) + MessageRecoverer defaultMessageRecoverer() { + return new RepublishMessageRecoverer( + jmsTemplate(), + new SpecCompliantJmsHeaderMapper() + ); + } + + @Bean + ListenerContainerFactory listenerContainerFactory() { + return new ListenerContainerFactory(connectionFactory); + } + + @Bean + public JmsMessageDrivenChannelAdapterFactory jmsMessageDrivenChannelAdapterFactory( + MessageRecoverer messageRecoverer, + ListenerContainerFactory listenerContainerFactory + ) { + return new JmsMessageDrivenChannelAdapterFactory( + listenerContainerFactory, + messageRecoverer + ); + } + + @Bean + @ConditionalOnMissingBean(JmsSendingMessageHandlerFactory.class) + public JmsSendingMessageHandlerFactory jmsSendingMessageHandlerFactory() + { + return new JmsSendingMessageHandlerFactory( + jmsTemplate(), + new SpecCompliantJmsHeaderMapper() + ); + } + + @Bean + @ConditionalOnMissingBean(JmsTemplate.class) + public JmsTemplate jmsTemplate() { + return new JmsTemplate(connectionFactory); + } + + @Configuration + @EnableConfigurationProperties(JmsExtendedBindingProperties.class) + public static class JmsBinderConfiguration { @Bean - public JmsMessageDrivenChannelAdapterFactory jmsMessageDrivenChannelAdapterFactory( - MessageRecoverer messageRecoverer, - ListenerContainerFactory listenerContainerFactory + JMSMessageChannelBinder jmsMessageChannelBinder( + JmsMessageDrivenChannelAdapterFactory jmsMessageDrivenChannelAdapterFactory, + JmsSendingMessageHandlerFactory jmsSendingMessageHandlerFactory, + JmsTemplate jmsTemplate, + ProvisioningProvider, ExtendedProducerProperties> provisioningProvider, + ConnectionFactory connectionFactory, + JmsExtendedBindingProperties jmsExtendedBindingProperties, + DestinationNameResolver destinationNameResolver ) { - return new JmsMessageDrivenChannelAdapterFactory( - listenerContainerFactory, - messageRecoverer - ); - } - - @Bean - @ConditionalOnMissingBean(JmsSendingMessageHandlerFactory.class) - public JmsSendingMessageHandlerFactory jmsSendingMessageHandlerFactory() { - return new JmsSendingMessageHandlerFactory( - jmsTemplate(), - new SpecCompliantJmsHeaderMapper() - ); - } - - @Bean - @ConditionalOnMissingBean(JmsTemplate.class) - public JmsTemplate jmsTemplate() { - return new JmsTemplate(connectionFactory); - } - - @Configuration - @EnableConfigurationProperties(JmsExtendedBindingProperties.class) - public static class JmsBinderConfiguration { - - @Bean - JMSMessageChannelBinder jmsMessageChannelBinder( - JmsMessageDrivenChannelAdapterFactory jmsMessageDrivenChannelAdapterFactory, - JmsSendingMessageHandlerFactory jmsSendingMessageHandlerFactory, - JmsTemplate jmsTemplate, - ProvisioningProvider, ExtendedProducerProperties> provisioningProvider, - ConnectionFactory connectionFactory, - JmsExtendedBindingProperties jmsExtendedBindingProperties, - DestinationNameResolver destinationNameResolver - ) { - JMSMessageChannelBinder jmsMessageChannelBinder = new JMSMessageChannelBinder( - provisioningProvider, - jmsSendingMessageHandlerFactory, - jmsMessageDrivenChannelAdapterFactory, - jmsTemplate, - connectionFactory - ); - - jmsMessageChannelBinder.setExtendedBindingProperties( - jmsExtendedBindingProperties - ); - - jmsMessageChannelBinder.setDestinationNameResolver(destinationNameResolver); - - return jmsMessageChannelBinder; - } + JMSMessageChannelBinder jmsMessageChannelBinder = new JMSMessageChannelBinder( + provisioningProvider, + jmsSendingMessageHandlerFactory, + jmsMessageDrivenChannelAdapterFactory, + jmsTemplate, + connectionFactory + ); + + jmsMessageChannelBinder.setExtendedBindingProperties( + jmsExtendedBindingProperties + ); + + jmsMessageChannelBinder.setDestinationNameResolver(destinationNameResolver); + + return jmsMessageChannelBinder; } + } } diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/config/JmsBindingProperties.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/config/JmsBindingProperties.java index d521bdba..0e65fc14 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/config/JmsBindingProperties.java +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/config/JmsBindingProperties.java @@ -1,9 +1,9 @@ /* - ** TxEventQ Support for Spring Cloud Stream - ** Copyright (c) 2023, 2024 Oracle and/or its affiliates. - ** - ** This file has been modified by Oracle Corporation. - */ +** TxEventQ Support for Spring Cloud Stream +** Copyright (c) 2023, 2024 Oracle and/or its affiliates. +** +** This file has been modified by Oracle Corporation. +*/ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -28,23 +28,23 @@ public class JmsBindingProperties implements BinderSpecificPropertiesProvider { - private JmsConsumerProperties consumer = new JmsConsumerProperties(); + private JmsConsumerProperties consumer = new JmsConsumerProperties(); - private JmsProducerProperties producer = new JmsProducerProperties(); + private JmsProducerProperties producer = new JmsProducerProperties(); - public JmsConsumerProperties getConsumer() { - return consumer; - } + public JmsConsumerProperties getConsumer() { + return consumer; + } - public void setConsumer(JmsConsumerProperties consumer) { - this.consumer = consumer; - } + public void setConsumer(JmsConsumerProperties consumer) { + this.consumer = consumer; + } - public JmsProducerProperties getProducer() { - return producer; - } + public JmsProducerProperties getProducer() { + return producer; + } - public void setProducer(JmsProducerProperties producer) { - this.producer = producer; - } + public void setProducer(JmsProducerProperties producer) { + this.producer = producer; + } } diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/config/JmsConsumerProperties.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/config/JmsConsumerProperties.java index 0aed39fb..52dafa77 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/config/JmsConsumerProperties.java +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/config/JmsConsumerProperties.java @@ -1,9 +1,9 @@ /* - ** TxEventQ Support for Spring Cloud Stream - ** Copyright (c) 2023, 2024 Oracle and/or its affiliates. - ** - ** This file has been modified by Oracle Corporation. - */ +** TxEventQ Support for Spring Cloud Stream +** Copyright (c) 2023, 2024 Oracle and/or its affiliates. +** +** This file has been modified by Oracle Corporation. +*/ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -25,49 +25,47 @@ package com.oracle.cstream.config; public class JmsConsumerProperties { - private static final String DEFAULT_DLQ_NAME = "Spring_Cloud_Stream_dlq"; - - /* Properties relevant for batching of messages */ - private int batchSize = 10; - - private int timeout = 1000; // in milliseconds, default => 1 second - - /** - * the name of the dead letter queue - **/ - private String dlqName = DEFAULT_DLQ_NAME; + private static final String DEFAULT_DLQ_NAME = "Spring_Cloud_Stream_dlq"; - private String deSerializer = null; + /* Properties relevant for batching of messages */ + private int batchSize = 10; + + private int timeout = 1000; // in milliseconds, default => 1 second + + /** the name of the dead letter queue **/ + private String dlqName = DEFAULT_DLQ_NAME; + + private String deSerializer = null; - public String getDeSerializer() { - return deSerializer; - } + public String getDeSerializer() { + return deSerializer; + } - public void setDeSerializer(String deSerializer) { - this.deSerializer = deSerializer; - } + public void setDeSerializer(String deSerializer) { + this.deSerializer = deSerializer; + } - public String getDlqName() { - return dlqName; - } + public String getDlqName() { + return dlqName; + } - public void setDlqName(String dlqName) { - this.dlqName = dlqName; - } + public void setDlqName(String dlqName) { + this.dlqName = dlqName; + } - public int getTimeout() { - return this.timeout; - } + public int getTimeout() { + return this.timeout; + } - public int getBatchSize() { - return batchSize; - } + public int getBatchSize() { + return batchSize; + } - public void setBatchSize(int batchSize) { - this.batchSize = batchSize; - } + public void setBatchSize(int batchSize) { + this.batchSize = batchSize; + } - public void setTimeout(int timeout) { - this.timeout = timeout; - } + public void setTimeout(int timeout) { + this.timeout = timeout; + } } diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/config/JmsExtendedBindingProperties.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/config/JmsExtendedBindingProperties.java index 3f7ab306..6e1e9f11 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/config/JmsExtendedBindingProperties.java +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/config/JmsExtendedBindingProperties.java @@ -1,9 +1,9 @@ /* - ** TxEventQ Support for Spring Cloud Stream - ** Copyright (c) 2023, 2024 Oracle and/or its affiliates. - ** - ** This file has been modified by Oracle Corporation. - */ +** TxEventQ Support for Spring Cloud Stream +** Copyright (c) 2023, 2024 Oracle and/or its affiliates. +** +** This file has been modified by Oracle Corporation. +*/ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -25,30 +25,29 @@ package com.oracle.cstream.config; import java.util.Map; - import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.cloud.stream.binder.BinderSpecificPropertiesProvider; import org.springframework.cloud.stream.binder.AbstractExtendedBindingProperties; @ConfigurationProperties("spring.cloud.stream.txeventq") public class JmsExtendedBindingProperties - extends - AbstractExtendedBindingProperties { + extends + AbstractExtendedBindingProperties { - private static final String DEFAULT_PREFIX = "spring.cloud.stream.txeventq.default"; + private static final String DEFAULT_PREFIX = "spring.cloud.stream.txeventq.default"; - @Override - public Map getBindings() { - return this.doGetBindings(); - } + @Override + public Map getBindings() { + return this.doGetBindings(); + } - @Override - public String getDefaultsPrefix() { - return DEFAULT_PREFIX; - } + @Override + public String getDefaultsPrefix() { + return DEFAULT_PREFIX; + } - @Override - public Class getExtendedPropertiesEntryClass() { - return JmsBindingProperties.class; - } + @Override + public Class getExtendedPropertiesEntryClass() { + return JmsBindingProperties.class; + } } diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/config/JmsProducerProperties.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/config/JmsProducerProperties.java index 02726b2c..81eac968 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/config/JmsProducerProperties.java +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/config/JmsProducerProperties.java @@ -1,9 +1,9 @@ /* - ** TxEventQ Support for Spring Cloud Stream - ** Copyright (c) 2023, 2024 Oracle and/or its affiliates. - ** - ** This file has been modified by Oracle Corporation. - */ +** TxEventQ Support for Spring Cloud Stream +** Copyright (c) 2023, 2024 Oracle and/or its affiliates. +** +** This file has been modified by Oracle Corporation. +*/ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -25,13 +25,13 @@ package com.oracle.cstream.config; public class JmsProducerProperties { - private String serializer = null; - - public String getSerializer() { - return this.serializer; - } - - public void setSerializer(String serializer) { - this.serializer = serializer; - } + private String serializer = null; + + public String getSerializer() { + return this.serializer; + } + + public void setSerializer(String serializer) { + this.serializer = serializer; + } } diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/config/TxEventQJmsConfiguration.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/config/TxEventQJmsConfiguration.java index d5e30eed..ad04f0c9 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/config/TxEventQJmsConfiguration.java +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/config/TxEventQJmsConfiguration.java @@ -1,9 +1,9 @@ /* - ** TxEventQ Support for Spring Cloud Stream - ** Copyright (c) 2023, 2024 Oracle and/or its affiliates. - ** - ** This file has been modified by Oracle Corporation. - */ +** TxEventQ Support for Spring Cloud Stream +** Copyright (c) 2023, 2024 Oracle and/or its affiliates. +** +** This file has been modified by Oracle Corporation. +*/ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -51,43 +51,43 @@ @Configuration //It is important to include the root JMS configuration class. @Import(JmsBinderAutoConfiguration.class) -@AutoConfigureAfter({JndiConnectionFactoryAutoConfiguration.class}) -@ConditionalOnClass({ConnectionFactory.class, AQjmsConnectionFactory.class}) +@AutoConfigureAfter({ JndiConnectionFactoryAutoConfiguration.class }) +@ConditionalOnClass({ ConnectionFactory.class, AQjmsConnectionFactory.class }) public class TxEventQJmsConfiguration { + + private final Logger logger = LoggerFactory.getLogger(TxEventQJmsConfiguration.class); - private final Logger logger = LoggerFactory.getLogger(TxEventQJmsConfiguration.class); - - @Bean - @ConditionalOnMissingBean(ConnectionFactory.class) - public ConnectionFactory aqJmsConnectionFactory(PoolDataSource ds) { - ConnectionFactory connectionFactory = null; - try { - connectionFactory = AQjmsFactory.getConnectionFactory(ds); - } catch (JMSException ignore) { - logger.error("Error creating connection factory bean.", ignore); - throw new IllegalArgumentException("Error while trying to obtain connectionFactory."); - } - return connectionFactory; + @Bean + @ConditionalOnMissingBean(ConnectionFactory.class) + public ConnectionFactory aqJmsConnectionFactory(PoolDataSource ds) { + ConnectionFactory connectionFactory = null; + try { + connectionFactory = AQjmsFactory.getConnectionFactory(ds); + } catch (JMSException ignore) { + logger.error("Error creating connection factory bean.", ignore); + throw new IllegalArgumentException("Error while trying to obtain connectionFactory."); } + return connectionFactory; + } + + @Bean + public OracleDBUtils getOracleDBUtils(PoolDataSource pds) { + try(java.sql.Connection conn = pds.getConnection()) { + return new OracleDBUtils(pds, conn.getMetaData().getDatabaseMajorVersion()); + } catch (SQLException e) { + logger.error("Error creating OracleDBUtils Bean."); + throw new IllegalArgumentException("Cannot initialize OracleDBUtils", e); + } + } - @Bean - public OracleDBUtils getOracleDBUtils(PoolDataSource pds) { - try (java.sql.Connection conn = pds.getConnection()) { - return new OracleDBUtils(pds, conn.getMetaData().getDatabaseMajorVersion()); - } catch (SQLException e) { - logger.error("Error creating OracleDBUtils Bean."); - throw new IllegalArgumentException("Cannot initialize OracleDBUtils", e); - } - } - - @Bean - ProvisioningProvider, ExtendedProducerProperties> txeventQQueueProvisioner( - ConnectionFactory connectionFactory, - OracleDBUtils dbutils - ) { - return new TxEventQQueueProvisioner( - connectionFactory, - dbutils - ); - } + @Bean + ProvisioningProvider,ExtendedProducerProperties> txeventQQueueProvisioner( + ConnectionFactory connectionFactory, + OracleDBUtils dbutils + ) { + return new TxEventQQueueProvisioner( + connectionFactory, + dbutils + ); + } } diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/plsql/OracleDBUtils.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/plsql/OracleDBUtils.java index 50294f46..cd59d505 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/plsql/OracleDBUtils.java +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/plsql/OracleDBUtils.java @@ -1,9 +1,9 @@ /* - ** TxEventQ Support for Spring Cloud Stream - ** Copyright (c) 2023, 2024 Oracle and/or its affiliates. - ** - ** This file has been modified by Oracle Corporation. - */ +** TxEventQ Support for Spring Cloud Stream +** Copyright (c) 2023, 2024 Oracle and/or its affiliates. +** +** This file has been modified by Oracle Corporation. +*/ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -35,97 +35,98 @@ import oracle.ucp.jdbc.PoolDataSource; public class OracleDBUtils { - - private PoolDataSource pds = null; - private final int dbversion; - private final Logger logger = LoggerFactory.getLogger(getClass()); - - private static final String CREATE_KB2_TEQ = - "BEGIN " - + "dbms_aqadm.create_transactional_event_queue(?, multiple_consumers => true);" - + "dbms_aqadm.set_queue_parameter(?, 'KEY_BASED_ENQUEUE', 2); " - + "dbms_aqadm.set_queue_parameter(?, 'SHARD_NUM', ?); " - + "dbms_aqadm.start_queue(?); " - + "END;"; - - private static final String CREATE_KB1_TEQ = - "BEGIN " - + "dbms_aqadm.create_sharded_queue(?, multiple_consumers => true);" - + "dbms_aqadm.set_queue_parameter(?, 'KEY_BASED_ENQUEUE', 1); " - + "dbms_aqadm.set_queue_parameter(?, 'SHARD_NUM', ?); " - + "FOR i in 0..?-1 " - + "LOOP " - + "dbms_aqadm.set_queue_parameter(?, 'AQ$KEY_TO_SHARD_MAP='||i, i*2); " - + "END LOOP; " - + "dbms_aqadm.start_queue(?); " - + "END;"; - - private static final String GET_PARTITION_COUNT = - "BEGIN " - + "dbms_aqadm.get_queue_parameter(?, 'SHARD_NUM', ?);" - + "END;"; - - public OracleDBUtils(PoolDataSource pds, int dbversion) { - this.pds = pds; - if (dbversion < 19) { - logger.error("DB version: {} not supported.", dbversion); - throw new IllegalArgumentException("The TxEventQ Binder is compatible with database versions >= 19. The current database version is: " + dbversion); - } - this.dbversion = dbversion; - } - - public int getDBVersion() { - return this.dbversion; - } - - public void createKBQ(String qname, int pCount) throws SQLException { - CallableStatement cstmt = null; - try (Connection conn = this.pds.getConnection()) { - if (this.dbversion != 19) { - /* For DB Versions > 19 */ - cstmt = conn.prepareCall(CREATE_KB2_TEQ); - cstmt.setString(1, qname); - cstmt.setString(2, qname); - cstmt.setString(3, qname); - cstmt.setInt(4, pCount); - cstmt.setString(5, qname); - } else { - /* For DB Version 19*/ - cstmt = conn.prepareCall(CREATE_KB1_TEQ); - cstmt.setString(1, qname); - cstmt.setString(2, qname); - cstmt.setString(3, qname); - cstmt.setInt(4, pCount); - cstmt.setInt(5, pCount); - cstmt.setString(6, qname); - cstmt.setString(7, qname); - } - cstmt.execute(); - } finally { - try { - if (cstmt != null) - cstmt.close(); - } catch (SQLException e) { - logger.error("Error while closing callable statement."); - } - } - } - - public int getTopicPartitions(String qname) throws SQLException { - CallableStatement cstmt = null; - try (Connection conn = pds.getConnection()) { - cstmt = conn.prepareCall(GET_PARTITION_COUNT); - cstmt.setString(1, qname); - cstmt.registerOutParameter(2, Types.INTEGER); - cstmt.execute(); - return cstmt.getInt(2); - } finally { - try { - if (cstmt != null) - cstmt.close(); - } catch (SQLException e) { - logger.error("Error while closing callable statement."); - } - } - } + + private PoolDataSource pds = null; + private int dbversion; + private final Logger logger = LoggerFactory.getLogger(getClass()); + + private static final String CREATE_KB2_TEQ = + "BEGIN " + + "dbms_aqadm.create_transactional_event_queue(?, multiple_consumers => true);" + + "dbms_aqadm.set_queue_parameter(?, 'KEY_BASED_ENQUEUE', 2); " + + "dbms_aqadm.set_queue_parameter(?, 'SHARD_NUM', ?); " + + "dbms_aqadm.start_queue(?); " + + "END;"; + + private static final String CREATE_KB1_TEQ = + "BEGIN " + + "dbms_aqadm.create_sharded_queue(?, multiple_consumers => true);" + + "dbms_aqadm.set_queue_parameter(?, 'KEY_BASED_ENQUEUE', 1); " + + "dbms_aqadm.set_queue_parameter(?, 'SHARD_NUM', ?); " + + "FOR i in 0..?-1 " + + "LOOP " + + "dbms_aqadm.set_queue_parameter(?, 'AQ$KEY_TO_SHARD_MAP='||i, i*2); " + + "END LOOP; " + + "dbms_aqadm.start_queue(?); " + + "END;"; + + private static final String GET_PARTITION_COUNT = + "BEGIN " + + "dbms_aqadm.get_queue_parameter(?, 'SHARD_NUM', ?);" + + "END;"; + + public OracleDBUtils(PoolDataSource pds, int dbversion) { + this.pds = pds; + if(dbversion < 19) { + logger.error("DB version: {} not supported.", dbversion); + throw new IllegalArgumentException("The TxEventQ Binder is compatible with database versions >= 19. The current database version is: " + dbversion); + } + this.dbversion = dbversion; + } + + public int getDBVersion() { + return this.dbversion; + } + + public void createKBQ(String qname, int pCount) throws SQLException { + CallableStatement cstmt = null; + try(Connection conn = this.pds.getConnection()) { + if(this.dbversion != 19) { + /* For DB Versions > 19 */ + cstmt = conn.prepareCall(CREATE_KB2_TEQ); + cstmt.setString(1, qname); + cstmt.setString(2, qname); + cstmt.setString(3, qname); + cstmt.setInt(4, pCount); + cstmt.setString(5, qname); + } + else { + /* For DB Version 19*/ + cstmt = conn.prepareCall(CREATE_KB1_TEQ); + cstmt.setString(1, qname); + cstmt.setString(2, qname); + cstmt.setString(3, qname); + cstmt.setInt(4, pCount); + cstmt.setInt(5, pCount); + cstmt.setString(6, qname); + cstmt.setString(7, qname); + } + cstmt.execute(); + } finally { + try { + if(cstmt != null) + cstmt.close(); + } catch(SQLException e) { + logger.error("Error while closing callable statement."); + } + } + } + + public int getTopicPartitions(String qname) throws SQLException { + CallableStatement cstmt = null; + try(Connection conn = pds.getConnection()) { + cstmt = conn.prepareCall(GET_PARTITION_COUNT); + cstmt.setString(1, qname); + cstmt.registerOutParameter(2, Types.INTEGER); + cstmt.execute(); + return cstmt.getInt(2); + } finally { + try { + if(cstmt != null) + cstmt.close(); + } catch(SQLException e) { + logger.error("Error while closing callable statement."); + } + } + } } diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/provisioning/JmsConsumerDestination.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/provisioning/JmsConsumerDestination.java index a7319d39..0d68ab4f 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/provisioning/JmsConsumerDestination.java +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/provisioning/JmsConsumerDestination.java @@ -1,9 +1,9 @@ /* - ** TxEventQ Support for Spring Cloud Stream - ** Copyright (c) 2023, 2024 Oracle and/or its affiliates. - ** - ** This file has been modified by Oracle Corporation. - */ +** TxEventQ Support for Spring Cloud Stream +** Copyright (c) 2023, 2024 Oracle and/or its affiliates. +** +** This file has been modified by Oracle Corporation. +*/ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -28,26 +28,26 @@ public class JmsConsumerDestination implements ConsumerDestination { - private final String topicName; - - private final int dbversion; - - public JmsConsumerDestination(final String topicName, final int dbversion) { - this.topicName = topicName; - this.dbversion = dbversion; - } - - @Override - public String getName() { - return this.topicName; - } - - @Override - public String toString() { - return "JmsConsumerDestination{" + "topic=" + topicName + '}'; - } - - public int getDBVersion() { - return this.dbversion; - } + private final String topicName; + + private final int dbversion; + + public JmsConsumerDestination(final String topicName, final int dbversion) { + this.topicName = topicName; + this.dbversion = dbversion; + } + + @Override + public String getName() { + return this.topicName; + } + + @Override + public String toString() { + return "JmsConsumerDestination{" + "topic=" + topicName + '}'; + } + + public int getDBVersion() { + return this.dbversion; + } } diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/provisioning/JmsProducerDestination.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/provisioning/JmsProducerDestination.java index 3b649978..858c5e86 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/provisioning/JmsProducerDestination.java +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/provisioning/JmsProducerDestination.java @@ -1,9 +1,9 @@ /* - ** TxEventQ Support for Spring Cloud Stream - ** Copyright (c) 2023, 2024 Oracle and/or its affiliates. - ** - ** This file has been modified by Oracle Corporation. - */ +** TxEventQ Support for Spring Cloud Stream +** Copyright (c) 2023, 2024 Oracle and/or its affiliates. +** +** This file has been modified by Oracle Corporation. +*/ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -32,48 +32,48 @@ public class JmsProducerDestination implements ProducerDestination { - private final Topic topic; - private final int partitionCount; - private final int dbversion; + private final Topic topic; + private final int partitionCount; + private int dbversion; - public JmsProducerDestination(Topic topic, int pCount, int dbversion) { - this.topic = topic; - this.partitionCount = pCount; - this.dbversion = dbversion; - } + public JmsProducerDestination(Topic topic, int pCount, int dbversion) { + this.topic = topic; + this.partitionCount = pCount; + this.dbversion = dbversion; + } - @Override - public String getName() { - try { - return topic.getTopicName(); - } catch (JMSException e) { - throw new ProvisioningException( - "Error getting topic name", - JmsUtils.convertJmsAccessException(e) - ); - } + @Override + public String getName() { + try { + return topic.getTopicName(); + } catch (JMSException e) { + throw new ProvisioningException( + "Error getting topic name", + JmsUtils.convertJmsAccessException(e) + ); } + } - @Override - public String getNameForPartition(int partition) { - try { - return topic.getTopicName(); - } catch (JMSException e) { - throw new ProvisioningException( - "Error getting topic name", - JmsUtils.convertJmsAccessException(e) - ); - } + @Override + public String getNameForPartition(int partition) { + try { + return topic.getTopicName(); + } catch (JMSException e) { + throw new ProvisioningException( + "Error getting topic name", + JmsUtils.convertJmsAccessException(e) + ); } + } + + public int getDBVersion() { + return this.dbversion; + } - public int getDBVersion() { - return this.dbversion; - } - - @Override - public String toString() { - return ( - "JmsProducerDestination{" + "topic=" + topic + ", partitions=" + partitionCount + ", DB Version: " + this.dbversion + "}" - ); - } + @Override + public String toString() { + return ( + "JmsProducerDestination{" + "topic=" + topic + ", partitions=" + partitionCount + ", DB Version: " + this.dbversion + "}" + ); + } } diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/serialize/CustomSerializationMessageConverter.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/serialize/CustomSerializationMessageConverter.java index 0cb75440..8973d53f 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/serialize/CustomSerializationMessageConverter.java +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/serialize/CustomSerializationMessageConverter.java @@ -1,9 +1,9 @@ /* - ** TxEventQ Support for Spring Cloud Stream - ** Copyright (c) 2023, 2024 Oracle and/or its affiliates. - ** - ** This file has been modified by Oracle Corporation. - */ +** TxEventQ Support for Spring Cloud Stream +** Copyright (c) 2023, 2024 Oracle and/or its affiliates. +** +** This file has been modified by Oracle Corporation. +*/ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -32,55 +32,55 @@ import jakarta.jms.Message; public class CustomSerializationMessageConverter extends SimpleMessageConverter { - public String deserializer = null; - - private static final Logger logger = LoggerFactory.getLogger(CustomSerializationMessageConverter.class); - - public String getDeserializer() { - return deserializer; - } - - public void setDeserializer(String deserializer) { - this.deserializer = deserializer; - } - - @Override - public Object fromMessage(Message jmsMessage) throws JMSException { - Object result = super.fromMessage(jmsMessage); - - // get class object - Class deserializeClass = null; - try { - deserializeClass = Class.forName(deserializer); - } catch (ClassNotFoundException e) { - throw new IllegalArgumentException("Deserialization class not found: " + this.deserializer); - } - - //verify that it is correct instance - boolean isInstanceOfDeserializer = false; - for (Class inter_face : deserializeClass.getInterfaces()) { - if (inter_face.toString().equals(Deserializer.class.toString())) { - isInstanceOfDeserializer = true; - break; - } - } - - if (!isInstanceOfDeserializer) { - logger.debug("The configured deserializer class is not an instance of 'com.oracle.cstream.serialize.DeSerializer'"); - throw new IllegalArgumentException("The configured serializer class is not an instance of 'com.oracle.cstream.serialize.DeSerializer'"); - } - - Deserializer s = null; - - try { - s = (Deserializer) (deserializeClass.getDeclaredConstructor().newInstance()); - } catch (Exception e) { - logger.debug("Serializer object could not be initiated."); - throw new IllegalArgumentException("Serializer object could not be initiated."); - } - - result = s.deserialize((byte[]) result); - - return result; - } + public String deserializer = null; + + private static final Logger logger = LoggerFactory.getLogger(CustomSerializationMessageConverter.class); + + public String getDeserializer() { + return deserializer; + } + + public void setDeserializer(String deserializer) { + this.deserializer = deserializer; + } + + @Override + public Object fromMessage(Message jmsMessage) throws JMSException { + Object result = super.fromMessage(jmsMessage); + + // get class object + Class deserializeClass = null; + try { + deserializeClass = Class.forName(deserializer); + } catch (ClassNotFoundException e) { + throw new IllegalArgumentException("Deserialization class not found: " + this.deserializer); + } + + //verify that it is correct instance + boolean isInstanceOfDeserializer = false; + for(Class inter_face: deserializeClass.getInterfaces()) { + if(inter_face.toString().equals(Deserializer.class.toString())) { + isInstanceOfDeserializer = true; + break; + } + } + + if(!isInstanceOfDeserializer) { + logger.debug("The configured deserializer class is not an instance of 'com.oracle.cstream.serialize.DeSerializer'"); + throw new IllegalArgumentException("The configured serializer class is not an instance of 'com.oracle.cstream.serialize.DeSerializer'"); + } + + Deserializer s = null; + + try { + s = (Deserializer)(deserializeClass.getDeclaredConstructor().newInstance()); + } catch(Exception e) { + logger.debug("Serializer object could not be initiated."); + throw new IllegalArgumentException("Serializer object could not be initiated."); + } + + result = (Object)(s.deserialize((byte[])result)); + + return result; + } } diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/serialize/Deserializer.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/serialize/Deserializer.java index c6976ff5..203ed79e 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/serialize/Deserializer.java +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/serialize/Deserializer.java @@ -1,9 +1,9 @@ /* - ** TxEventQ Support for Spring Cloud Stream - ** Copyright (c) 2023, 2024 Oracle and/or its affiliates. - ** - ** This file has been modified by Oracle Corporation. - */ +** TxEventQ Support for Spring Cloud Stream +** Copyright (c) 2023, 2024 Oracle and/or its affiliates. +** +** This file has been modified by Oracle Corporation. +*/ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -25,5 +25,5 @@ package com.oracle.cstream.serialize; public interface Deserializer { - T deserialize(byte[] bytes); + public T deserialize(byte[] bytes); } diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/serialize/Serializer.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/serialize/Serializer.java index d43cbf6a..443571af 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/serialize/Serializer.java +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/serialize/Serializer.java @@ -1,9 +1,9 @@ /* - ** TxEventQ Support for Spring Cloud Stream - ** Copyright (c) 2023, 2024 Oracle and/or its affiliates. - ** - ** This file has been modified by Oracle Corporation. - */ +** TxEventQ Support for Spring Cloud Stream +** Copyright (c) 2023, 2024 Oracle and/or its affiliates. +** +** This file has been modified by Oracle Corporation. +*/ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -25,5 +25,5 @@ package com.oracle.cstream.serialize; public interface Serializer { - byte[] serialize(Object data); + public byte[] serialize(Object data); } diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/AnonymousNamingStrategy.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/AnonymousNamingStrategy.java index 6baf93de..afdf5e89 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/AnonymousNamingStrategy.java +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/AnonymousNamingStrategy.java @@ -1,9 +1,9 @@ /* - ** TxEventQ Support for Spring Cloud Stream - ** Copyright (c) 2023, 2024 Oracle and/or its affiliates. - ** - ** This file has been modified by Oracle Corporation. - */ +** TxEventQ Support for Spring Cloud Stream +** Copyright (c) 2023, 2024 Oracle and/or its affiliates. +** +** This file has been modified by Oracle Corporation. +*/ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -26,7 +26,7 @@ public interface AnonymousNamingStrategy { - String generateName(); + String generateName(); - String generateName(String prefix); + String generateName(String prefix); } diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/Base64UrlNamingStrategy.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/Base64UrlNamingStrategy.java index 5b65c2b8..476ab97b 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/Base64UrlNamingStrategy.java +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/Base64UrlNamingStrategy.java @@ -1,9 +1,9 @@ /* - ** TxEventQ Support for Spring Cloud Stream - ** Copyright (c) 2023, 2024 Oracle and/or its affiliates. - ** - ** This file has been modified by Oracle Corporation. - */ +** TxEventQ Support for Spring Cloud Stream +** Copyright (c) 2023, 2024 Oracle and/or its affiliates. +** +** This file has been modified by Oracle Corporation. +*/ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -30,32 +30,31 @@ public class Base64UrlNamingStrategy implements AnonymousNamingStrategy { - private String prefix = "spring.gen-"; - - public Base64UrlNamingStrategy() { - } - - public Base64UrlNamingStrategy(String prefix) { - this.prefix = prefix; - } - - @Override - public String generateName() { - return generateName(this.prefix); - } - - @Override - public String generateName(String prefix) { - UUID uuid = UUID.randomUUID(); - ByteBuffer bb = ByteBuffer.wrap(new byte[16]); - bb - .putLong(uuid.getMostSignificantBits()) - .putLong(uuid.getLeastSignificantBits()); - // Convert to base64 and remove trailing = - Base64.Encoder encoder = Base64.getUrlEncoder().withoutPadding(); - return ( - prefix + - encoder.encodeToString(bb.array()).replace("=", "").replace("-", "\\$") - ); - } + private String prefix = "spring.gen-"; + + public Base64UrlNamingStrategy() {} + + public Base64UrlNamingStrategy(String prefix) { + this.prefix = prefix; + } + + @Override + public String generateName() { + return generateName(this.prefix); + } + + @Override + public String generateName(String prefix) { + UUID uuid = UUID.randomUUID(); + ByteBuffer bb = ByteBuffer.wrap(new byte[16]); + bb + .putLong(uuid.getMostSignificantBits()) + .putLong(uuid.getLeastSignificantBits()); + // Convert to base64 and remove trailing = + Base64.Encoder encoder = Base64.getUrlEncoder().withoutPadding(); + return ( + prefix + + encoder.encodeToString(bb.array()).replace("=", "").replace("-", "\\$") + ); + } } diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/DestinationNameResolver.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/DestinationNameResolver.java index 060131cd..e5fdfa5d 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/DestinationNameResolver.java +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/DestinationNameResolver.java @@ -1,9 +1,9 @@ /* - ** TxEventQ Support for Spring Cloud Stream - ** Copyright (c) 2023, 2024 Oracle and/or its affiliates. - ** - ** This file has been modified by Oracle Corporation. - */ +** TxEventQ Support for Spring Cloud Stream +** Copyright (c) 2023, 2024 Oracle and/or its affiliates. +** +** This file has been modified by Oracle Corporation. +*/ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -27,14 +27,14 @@ import org.springframework.util.StringUtils; public class DestinationNameResolver { - private final AnonymousNamingStrategy namingStrategy; + private AnonymousNamingStrategy namingStrategy; - public DestinationNameResolver(AnonymousNamingStrategy namingStrategy) { - this.namingStrategy = namingStrategy; - } + public DestinationNameResolver(AnonymousNamingStrategy namingStrategy) { + this.namingStrategy = namingStrategy; + } - public String resolveGroupName(String group) { - boolean anonymous = !StringUtils.hasText(group); - return anonymous ? namingStrategy.generateName() : group; - } + public String resolveGroupName(String group) { + boolean anonymous = !StringUtils.hasText(group); + return anonymous ? namingStrategy.generateName() : group; + } } diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/JmsMessageDrivenChannelAdapter.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/JmsMessageDrivenChannelAdapter.java index 6d801e06..371c96cc 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/JmsMessageDrivenChannelAdapter.java +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/JmsMessageDrivenChannelAdapter.java @@ -1,9 +1,9 @@ /* - ** TxEventQ Support for Spring Cloud Stream - ** Copyright (c) 2023, 2024 Oracle and/or its affiliates. - ** - ** This file has been modified by Oracle Corporation. - */ +** TxEventQ Support for Spring Cloud Stream +** Copyright (c) 2023, 2024 Oracle and/or its affiliates. +** +** This file has been modified by Oracle Corporation. +*/ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -34,102 +34,102 @@ import org.springframework.messaging.MessageChannel; public class JmsMessageDrivenChannelAdapter extends MessageProducerSupport implements - OrderlyShutdownCapable { - - private final JmsMessageDrivenEndpoint endpoint; - - private final ChannelPublishingJmsMessageListener listener; - - public JmsMessageDrivenChannelAdapter(AbstractMessageListenerContainer listenerContainer, - ChannelPublishingJmsMessageListener listener) { - this.endpoint = new JmsMessageDrivenEndpoint(listenerContainer, listener); - this.listener = listener; - } - - @Override - public void setOutputChannel(MessageChannel requestChannel) { - super.setOutputChannel(requestChannel); - this.listener.setRequestChannel(requestChannel); - } - - @Override - public void setOutputChannelName(String requestChannelName) { - super.setOutputChannelName(requestChannelName); - this.listener.setRequestChannelName(requestChannelName); - } - - @Override - public void setErrorChannel(MessageChannel errorChannel) { - super.setErrorChannel(errorChannel); - this.listener.setErrorChannel(errorChannel); - } - - @Override - public void setErrorChannelName(String errorChannelName) { - this.listener.setErrorChannelName(errorChannelName); - } - - @Override - public void setSendTimeout(long requestTimeout) { - this.listener.setRequestTimeout(requestTimeout); - } - - @Override - public void setShouldTrack(boolean shouldTrack) { - this.listener.setShouldTrack(shouldTrack); - } - - @Override - public String getComponentType() { - return "jms:message-driven-channel-adapter"; - } - - @Override - public void setComponentName(String componentName) { - super.setComponentName(componentName); - this.endpoint.setComponentName(getComponentName()); - } - - @Override - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { - super.setApplicationContext(applicationContext); - this.endpoint.setApplicationContext(applicationContext); - this.endpoint.setBeanFactory(applicationContext); - this.listener.setBeanFactory(applicationContext); - } - - @Override - protected void onInit() { - this.endpoint.afterPropertiesSet(); - } - - ChannelPublishingJmsMessageListener getListener() { - return this.listener; - } - - @Override - protected void doStart() { - this.endpoint.start(); - } - - @Override - protected void doStop() { - this.endpoint.stop(); - } - - @Override - public void destroy() { - this.endpoint.destroy(); - } - - @Override - public int beforeShutdown() { - return this.endpoint.beforeShutdown(); - } - - @Override - public int afterShutdown() { - return this.endpoint.afterShutdown(); - } +OrderlyShutdownCapable { + +private final JmsMessageDrivenEndpoint endpoint; + +private final ChannelPublishingJmsMessageListener listener; + +public JmsMessageDrivenChannelAdapter(AbstractMessageListenerContainer listenerContainer, + ChannelPublishingJmsMessageListener listener) { +this.endpoint = new JmsMessageDrivenEndpoint(listenerContainer, listener); +this.listener = listener; +} + +@Override +public void setOutputChannel(MessageChannel requestChannel) { +super.setOutputChannel(requestChannel); +this.listener.setRequestChannel(requestChannel); +} + +@Override +public void setOutputChannelName(String requestChannelName) { +super.setOutputChannelName(requestChannelName); +this.listener.setRequestChannelName(requestChannelName); +} + +@Override +public void setErrorChannel(MessageChannel errorChannel) { +super.setErrorChannel(errorChannel); +this.listener.setErrorChannel(errorChannel); +} + +@Override +public void setErrorChannelName(String errorChannelName) { +this.listener.setErrorChannelName(errorChannelName); +} + +@Override +public void setSendTimeout(long requestTimeout) { +this.listener.setRequestTimeout(requestTimeout); +} + +@Override +public void setShouldTrack(boolean shouldTrack) { +this.listener.setShouldTrack(shouldTrack); +} + +@Override +public String getComponentType() { +return "jms:message-driven-channel-adapter"; +} + +@Override +public void setComponentName(String componentName) { +super.setComponentName(componentName); +this.endpoint.setComponentName(getComponentName()); +} + +@Override +public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { +super.setApplicationContext(applicationContext); +this.endpoint.setApplicationContext(applicationContext); +this.endpoint.setBeanFactory(applicationContext); +this.listener.setBeanFactory(applicationContext); +} + +@Override +protected void onInit() { +this.endpoint.afterPropertiesSet(); +} + +ChannelPublishingJmsMessageListener getListener() { +return this.listener; +} + +@Override +protected void doStart() { +this.endpoint.start(); +} + +@Override +protected void doStop() { +this.endpoint.stop(); +} + +@Override +public void destroy() { +this.endpoint.destroy(); +} + +@Override +public int beforeShutdown() { +return this.endpoint.beforeShutdown(); +} + +@Override +public int afterShutdown() { +return this.endpoint.afterShutdown(); +} } diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/JmsMessageDrivenChannelAdapterFactory.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/JmsMessageDrivenChannelAdapterFactory.java index 6ed0a695..f3d411c9 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/JmsMessageDrivenChannelAdapterFactory.java +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/JmsMessageDrivenChannelAdapterFactory.java @@ -1,9 +1,9 @@ /* - ** TxEventQ Support for Spring Cloud Stream - ** Copyright (c) 2023, 2024 Oracle and/or its affiliates. - ** - ** This file has been modified by Oracle Corporation. - */ +** TxEventQ Support for Spring Cloud Stream +** Copyright (c) 2023, 2024 Oracle and/or its affiliates. +** +** This file has been modified by Oracle Corporation. +*/ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -32,6 +32,7 @@ import jakarta.jms.JMSException; import jakarta.jms.Message; import jakarta.jms.Session; +import oracle.jakarta.jms.AQjmsSession; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -50,171 +51,176 @@ import org.springframework.retry.support.RetryTemplate; public class JmsMessageDrivenChannelAdapterFactory - implements ApplicationContextAware, BeanFactoryAware { - - private final ListenerContainerFactory listenerContainerFactory; + implements ApplicationContextAware, BeanFactoryAware { + + private final ListenerContainerFactory listenerContainerFactory; + + private final MessageRecoverer messageRecoverer; + + private BeanFactory beanFactory; + + private ApplicationContext applicationContext; + + private Logger logger = LoggerFactory.getLogger(getClass()); + + public JmsMessageDrivenChannelAdapterFactory( + ListenerContainerFactory listenerContainerFactory, + MessageRecoverer messageRecoverer + ) { + this.listenerContainerFactory = listenerContainerFactory; + this.messageRecoverer = messageRecoverer; + } + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.beanFactory = beanFactory; + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) + throws BeansException { + this.applicationContext = applicationContext; + } + + public JmsMessageDrivenChannelAdapter build( + Destination destination, + String groupName, + RetryTemplate retryTemplate, + RecoveryCallback recoveryCallback, + MessageChannel errorChannel, + final ExtendedConsumerProperties properties, + int dbversion + ) { + ChannelPublishingJmsMessageListener listener = null; + if(!properties.isBatchMode()) { + listener = new RetryingChannelPublishingJmsMessageListener( + properties, + messageRecoverer, + properties.getExtension().getDlqName(), + retryTemplate, + recoveryCallback + ); + if(properties.isUseNativeDecoding()) { + ((RetryingChannelPublishingJmsMessageListener) listener) + .setDeSerializerClassName(properties.getExtension().getDeSerializer()); + } + } else { + listener = new TEQBatchMessageListener(); + ((TEQBatchMessageListener)listener).setRetryTemplate(retryTemplate); + ((TEQBatchMessageListener)listener).setRecoverer(recoveryCallback); + ((TEQBatchMessageListener)listener).setDeSerializerClassName(properties.getExtension().getDeSerializer()); + } + listener.setBeanFactory(this.beanFactory); + + if(dbversion == 19 && properties.getInstanceIndex() != -1) { + logger.warn("Exact dequeue from a specific instanceIndex is not supported in Oracle Database version 19c. " + + "Please use Oracle DB version 23c if you want to perform exact dequeue from a partition."); + } + + JmsMessageDrivenChannelAdapter adapter = new JmsMessageDrivenChannelAdapter( + listenerContainerFactory.build(destination, + groupName, + properties.getInstanceIndex(), + properties.getConcurrency(), + properties.isBatchMode(), + properties.getExtension().getBatchSize(), + properties.getExtension().getTimeout()), + listener + ); + adapter.setApplicationContext(this.applicationContext); + adapter.setBeanFactory(this.beanFactory); + adapter.setErrorChannel(errorChannel); + return adapter; + } + + private static class RetryingChannelPublishingJmsMessageListener + extends ChannelPublishingJmsMessageListener { + + private static final String RETRY_CONTEXT_MESSAGE_ATTRIBUTE = "message"; + + private final ConsumerProperties properties; private final MessageRecoverer messageRecoverer; - private BeanFactory beanFactory; - - private ApplicationContext applicationContext; - - private final Logger logger = LoggerFactory.getLogger(getClass()); - - public JmsMessageDrivenChannelAdapterFactory( - ListenerContainerFactory listenerContainerFactory, - MessageRecoverer messageRecoverer + private final String deadLetterQueueName; + + private final RetryTemplate retryTemplate; + + private RecoveryCallback recoverer; + + private String deSerializerClassName = null; + + SpecCompliantJmsHeaderMapper headerMapper = new SpecCompliantJmsHeaderMapper(); + + RetryingChannelPublishingJmsMessageListener( + ConsumerProperties properties, + MessageRecoverer messageRecoverer, + String deadLetterQueueName, + RetryTemplate retryTemplate, + RecoveryCallback recoverer ) { - this.listenerContainerFactory = listenerContainerFactory; - this.messageRecoverer = messageRecoverer; + this.properties = properties; + this.messageRecoverer = messageRecoverer; + this.deadLetterQueueName = deadLetterQueueName; + this.retryTemplate = retryTemplate; + this.recoverer = recoverer; } - - @Override - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - this.beanFactory = beanFactory; + + public void setDeSerializerClassName(String deSerializerClassName) { + this.deSerializerClassName = deSerializerClassName; } @Override - public void setApplicationContext(ApplicationContext applicationContext) - throws BeansException { - this.applicationContext = applicationContext; - } - - public JmsMessageDrivenChannelAdapter build( - Destination destination, - String groupName, - RetryTemplate retryTemplate, - RecoveryCallback recoveryCallback, - MessageChannel errorChannel, - final ExtendedConsumerProperties properties, - int dbversion - ) { - ChannelPublishingJmsMessageListener listener = null; - if (!properties.isBatchMode()) { - listener = new RetryingChannelPublishingJmsMessageListener( - properties, - messageRecoverer, - properties.getExtension().getDlqName(), - retryTemplate, - recoveryCallback - ); - if (properties.isUseNativeDecoding()) { - ((RetryingChannelPublishingJmsMessageListener) listener) - .setDeSerializerClassName(properties.getExtension().getDeSerializer()); + public void onMessage(final Message jmsMessage, final Session session) + throws JMSException { + this.retryTemplate + .execute( + new RetryCallback() { + @Override + public Object doWithRetry(RetryContext retryContext) + throws JMSException { + try { + // convert data if de-serialization is enabled + if(deSerializerClassName != null) { + // get class name and parameterized type name + CustomSerializationMessageConverter customConverter = new CustomSerializationMessageConverter(); + customConverter.setDeserializer(deSerializerClassName); + RetryingChannelPublishingJmsMessageListener.this.setMessageConverter( + customConverter + ); + } + + retryContext.setAttribute( + RETRY_CONTEXT_MESSAGE_ATTRIBUTE, + jmsMessage + ); + + headerMapper.setConnection(((AQjmsSession) session).getDBConnection()); + RetryingChannelPublishingJmsMessageListener.super.setHeaderMapper(headerMapper); + RetryingChannelPublishingJmsMessageListener.super.onMessage( + jmsMessage, + session + ); + } catch (JMSException e) { + logger.error(e, "Failed to send message"); + resetMessageIfRequired(jmsMessage); + throw e; + } catch (Exception e) { + resetMessageIfRequired(jmsMessage); + throw e; + } + return null; } - } else { - listener = new TEQBatchMessageListener(); - ((TEQBatchMessageListener) listener).setRetryTemplate(retryTemplate); - ((TEQBatchMessageListener) listener).setRecoverer(recoveryCallback); - ((TEQBatchMessageListener) listener).setDeSerializerClassName(properties.getExtension().getDeSerializer()); - } - listener.setBeanFactory(this.beanFactory); - - if (dbversion == 19 && properties.getInstanceIndex() != -1) { - logger.warn("Exact dequeue from a specific instanceIndex is not supported in Oracle Database version 19c. " - + "Please use Oracle DB version 23c if you want to perform exact dequeue from a partition."); - } - - JmsMessageDrivenChannelAdapter adapter = new JmsMessageDrivenChannelAdapter( - listenerContainerFactory.build(destination, - groupName, - properties.getInstanceIndex(), - properties.getConcurrency(), - properties.isBatchMode(), - properties.getExtension().getBatchSize(), - properties.getExtension().getTimeout()), - listener + }, + this.recoverer ); - adapter.setApplicationContext(this.applicationContext); - adapter.setBeanFactory(this.beanFactory); - adapter.setErrorChannel(errorChannel); - return adapter; } - private static class RetryingChannelPublishingJmsMessageListener - extends ChannelPublishingJmsMessageListener { - - private static final String RETRY_CONTEXT_MESSAGE_ATTRIBUTE = "message"; - - private final ConsumerProperties properties; - - private final MessageRecoverer messageRecoverer; - - private final String deadLetterQueueName; - - private final RetryTemplate retryTemplate; - - private final RecoveryCallback recoverer; - - private String deSerializerClassName = null; - - RetryingChannelPublishingJmsMessageListener( - ConsumerProperties properties, - MessageRecoverer messageRecoverer, - String deadLetterQueueName, - RetryTemplate retryTemplate, - RecoveryCallback recoverer - ) { - this.properties = properties; - this.messageRecoverer = messageRecoverer; - this.deadLetterQueueName = deadLetterQueueName; - this.retryTemplate = retryTemplate; - this.recoverer = recoverer; - } - - public void setDeSerializerClassName(String deSerializerClassName) { - this.deSerializerClassName = deSerializerClassName; - } - - @Override - public void onMessage(final Message jmsMessage, final Session session) - throws JMSException { - this.retryTemplate - .execute( - new RetryCallback() { - @Override - public Object doWithRetry(RetryContext retryContext) - throws JMSException { - try { - // convert data if de-serialization is enabled - if (deSerializerClassName != null) { - // get class name and parameterized type name - CustomSerializationMessageConverter customConverter = new CustomSerializationMessageConverter(); - customConverter.setDeserializer(deSerializerClassName); - RetryingChannelPublishingJmsMessageListener.this.setMessageConverter( - customConverter - ); - } - - retryContext.setAttribute( - RETRY_CONTEXT_MESSAGE_ATTRIBUTE, - jmsMessage - ); - RetryingChannelPublishingJmsMessageListener.super.onMessage( - jmsMessage, - session - ); - } catch (JMSException e) { - logger.error(e, "Failed to send message"); - resetMessageIfRequired(jmsMessage); - throw e; - } catch (Exception e) { - resetMessageIfRequired(jmsMessage); - throw e; - } - return null; - } - }, - this.recoverer - ); - } - - protected void resetMessageIfRequired(Message jmsMessage) - throws JMSException { - if (jmsMessage instanceof BytesMessage message) { - message.reset(); - } - } + protected void resetMessageIfRequired(Message jmsMessage) + throws JMSException { + if (jmsMessage instanceof BytesMessage message) { + message.reset(); + } } + } } diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/JmsSendingMessageHandlerFactory.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/JmsSendingMessageHandlerFactory.java index 88b69b83..578dacfe 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/JmsSendingMessageHandlerFactory.java +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/JmsSendingMessageHandlerFactory.java @@ -1,9 +1,9 @@ /* - ** TxEventQ Support for Spring Cloud Stream - ** Copyright (c) 2023, 2024 Oracle and/or its affiliates. - ** - ** This file has been modified by Oracle Corporation. - */ +** TxEventQ Support for Spring Cloud Stream +** Copyright (c) 2023, 2024 Oracle and/or its affiliates. +** +** This file has been modified by Oracle Corporation. +*/ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -37,48 +37,48 @@ public class JmsSendingMessageHandlerFactory implements ApplicationContextAware, BeanFactoryAware { - private final JmsTemplate template; + private final JmsTemplate template; - private ApplicationContext applicationContext; + private ApplicationContext applicationContext; - private BeanFactory beanFactory; + private BeanFactory beanFactory; - private final JmsHeaderMapper headerMapper; + private final JmsHeaderMapper headerMapper; - public JmsSendingMessageHandlerFactory(JmsTemplate template, - JmsHeaderMapper headerMapper) { - this.template = template; - this.headerMapper = headerMapper; - } + public JmsSendingMessageHandlerFactory(JmsTemplate template, + JmsHeaderMapper headerMapper) { + this.template = template; + this.headerMapper = headerMapper; + } - @Override - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - this.beanFactory = beanFactory; - } + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.beanFactory = beanFactory; + } - @Override - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { - this.applicationContext = applicationContext; - } + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + } - public PartitionAwareJmsSendingMessageHandler build(Destination destination, - MessageChannel errorChannel, - boolean mapHeaders, - String serializer, - int dbversion) { - template.setPubSubDomain(true); - PartitionAwareJmsSendingMessageHandler handler = new PartitionAwareJmsSendingMessageHandler( - this.template, - destination, - headerMapper, - errorChannel, - mapHeaders); - handler.setSerializerClassName(serializer); - handler.setApplicationContext(this.applicationContext); - handler.setBeanFactory(this.beanFactory); - handler.afterPropertiesSet(); - handler.setDBVersion(dbversion); - return handler; - } + public PartitionAwareJmsSendingMessageHandler build(Destination destination, + MessageChannel errorChannel, + boolean mapHeaders, + String serializer, + int dbversion) { + template.setPubSubDomain(true); + PartitionAwareJmsSendingMessageHandler handler = new PartitionAwareJmsSendingMessageHandler( + this.template, + destination, + headerMapper, + errorChannel, + mapHeaders); + handler.setSerializerClassName(serializer); + handler.setApplicationContext(this.applicationContext); + handler.setBeanFactory(this.beanFactory); + handler.afterPropertiesSet(); + handler.setDBVersion(dbversion); + return handler; + } } diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/ListenerContainerFactory.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/ListenerContainerFactory.java index 258a4cb0..b9154fc4 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/ListenerContainerFactory.java +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/ListenerContainerFactory.java @@ -1,9 +1,9 @@ /* - ** TxEventQ Support for Spring Cloud Stream - ** Copyright (c) 2023, 2024 Oracle and/or its affiliates. - ** - ** This file has been modified by Oracle Corporation. - */ +** TxEventQ Support for Spring Cloud Stream +** Copyright (c) 2023, 2024 Oracle and/or its affiliates. +** +** This file has been modified by Oracle Corporation. +*/ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -33,44 +33,45 @@ import org.springframework.jms.listener.DefaultMessageListenerContainer; public class ListenerContainerFactory { - private final ConnectionFactory factory; - - private static final Logger logger = LoggerFactory.getLogger(ListenerContainerFactory.class); - public ListenerContainerFactory(ConnectionFactory factory) { - this.factory = factory; - } + private ConnectionFactory factory; + + private static final Logger logger = LoggerFactory.getLogger(ListenerContainerFactory.class); - public AbstractMessageListenerContainer build( - Destination topic, - String group, - int partition, - int concurrency, - boolean isBatched, - int batchSize, - int timeout - ) { - DefaultMessageListenerContainer listenerContainer = null; - if (!isBatched) { - listenerContainer = new TEQMessageListenerContainer(); - ((TEQMessageListenerContainer) listenerContainer).setPartition(partition); - } else { - listenerContainer = new TEQBatchMessageListenerContainer(); - ((TEQBatchMessageListenerContainer) listenerContainer).setPartition(partition); - ((TEQBatchMessageListenerContainer) listenerContainer).setBatchSize(batchSize); - } - logger.info("Consuming from destination: {}, Group: {}", topic, group); - listenerContainer.setDestination(topic); - listenerContainer.setPubSubDomain(true); - listenerContainer.setSubscriptionName(group); - listenerContainer.setReceiveTimeout(timeout); + public ListenerContainerFactory(ConnectionFactory factory) { + this.factory = factory; + } - listenerContainer.setConcurrentConsumers(concurrency); - if (!group.contains("anonymous")) { - listenerContainer.setSubscriptionDurable(true); - } - listenerContainer.setConnectionFactory(factory); - listenerContainer.setSessionTransacted(true); - return listenerContainer; + public AbstractMessageListenerContainer build( + Destination topic, + String group, + int partition, + int concurrency, + boolean isBatched, + int batchSize, + int timeout + ) { + DefaultMessageListenerContainer listenerContainer = null; + if(!isBatched) { + listenerContainer = new TEQMessageListenerContainer(); + ((TEQMessageListenerContainer)listenerContainer).setPartition(partition); + } else { + listenerContainer = new TEQBatchMessageListenerContainer(); + ((TEQBatchMessageListenerContainer)listenerContainer).setPartition(partition); + ((TEQBatchMessageListenerContainer)listenerContainer).setBatchSize(batchSize); + } + logger.info("Consuming from destination: {}, Group: {}" , topic, group); + listenerContainer.setDestination(topic); + listenerContainer.setPubSubDomain(true); + listenerContainer.setSubscriptionName(group); + listenerContainer.setReceiveTimeout(timeout); + + listenerContainer.setConcurrentConsumers(concurrency); + if (!group.contains("anonymous")) { + listenerContainer.setSubscriptionDurable(true); } + listenerContainer.setConnectionFactory(factory); + listenerContainer.setSessionTransacted(true); + return listenerContainer; + } } diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/MessageRecoverer.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/MessageRecoverer.java index 6a30147b..6529cdbf 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/MessageRecoverer.java +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/MessageRecoverer.java @@ -1,9 +1,9 @@ /* - ** TxEventQ Support for Spring Cloud Stream - ** Copyright (c) 2023, 2024 Oracle and/or its affiliates. - ** - ** This file has been modified by Oracle Corporation. - */ +** TxEventQ Support for Spring Cloud Stream +** Copyright (c) 2023, 2024 Oracle and/or its affiliates. +** +** This file has been modified by Oracle Corporation. +*/ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -27,11 +27,11 @@ import jakarta.jms.Message; public interface MessageRecoverer { - /** - * Recover from the failure to deliver a message. - * - * @param undeliveredMessage the message that has not been delivered. - * @param cause the reason for the failure to deliver. - */ - void recover(Message undeliveredMessage, String dlq, Throwable cause); + /** + * Recover from the failure to deliver a message. + * + * @param undeliveredMessage the message that has not been delivered. + * @param cause the reason for the failure to deliver. + */ + void recover(Message undeliveredMessage, String dlq, Throwable cause); } diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/PartitionAwareJmsSendingMessageHandler.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/PartitionAwareJmsSendingMessageHandler.java index cf2c2997..9547f9bf 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/PartitionAwareJmsSendingMessageHandler.java +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/PartitionAwareJmsSendingMessageHandler.java @@ -1,9 +1,9 @@ /* - ** TxEventQ Support for Spring Cloud Stream - ** Copyright (c) 2023, 2024 Oracle and/or its affiliates. - ** - ** This file has been modified by Oracle Corporation. - */ +** TxEventQ Support for Spring Cloud Stream +** Copyright (c) 2023, 2024 Oracle and/or its affiliates. +** +** This file has been modified by Oracle Corporation. +*/ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -26,6 +26,13 @@ import jakarta.jms.Destination; import jakarta.jms.JMSException; +import jakarta.jms.MessageProducer; +import jakarta.jms.Session; +import oracle.jakarta.jms.AQjmsSession; +import oracle.jakarta.jms.AQjmsTopicConnectionFactory; + +import java.sql.Connection; +import java.util.function.Consumer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,6 +42,7 @@ import org.springframework.integration.jms.JmsHeaderMapper; import org.springframework.jms.core.JmsTemplate; import org.springframework.jms.core.MessagePostProcessor; +import org.springframework.jms.support.converter.MessageConverter; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.support.ErrorMessage; @@ -42,194 +50,233 @@ import com.oracle.cstream.serialize.Serializer; public class PartitionAwareJmsSendingMessageHandler - extends AbstractMessageHandler - implements Lifecycle { - - private final JmsTemplate jmsTemplate; - - private final Destination destination; - - private final JmsHeaderMapper headerMapper; - - private final MessageChannel errorChannel; - - private boolean mapHeaders = true; - - private String serializerClassName = null; - - private int dbversion = 23; - - private static final Logger logger = LoggerFactory.getLogger(PartitionAwareJmsSendingMessageHandler.class); - - public PartitionAwareJmsSendingMessageHandler( - JmsTemplate jmsTemplate, - Destination destination, - JmsHeaderMapper headerMapper, - MessageChannel errorChannel, - boolean mapHeaders - ) { - this.jmsTemplate = jmsTemplate; - this.destination = destination; - this.headerMapper = headerMapper; - this.errorChannel = errorChannel; - this.mapHeaders = mapHeaders; + extends AbstractMessageHandler + implements Lifecycle { + + private final JmsTemplate jmsTemplate; + + private final Destination destination; + + private final JmsHeaderMapper headerMapper; + + private final MessageChannel errorChannel; + + private boolean mapHeaders = true; + + private String serializerClassName = null; + + private int dbversion = 23; + + private static final Logger sendLogger = LoggerFactory.getLogger(PartitionAwareJmsSendingMessageHandler.class); + + public PartitionAwareJmsSendingMessageHandler( + JmsTemplate jmsTemplate, + Destination destination, + JmsHeaderMapper headerMapper, + MessageChannel errorChannel, + boolean mapHeaders + ) { + this.jmsTemplate = jmsTemplate; + this.destination = destination; + this.headerMapper = headerMapper; + this.errorChannel = errorChannel; + this.mapHeaders = mapHeaders; + } + + public void setSerializerClassName(String serializerClassName) { + this.serializerClassName = serializerClassName; + } + + public void setDBVersion(int dbversion) { + this.dbversion = dbversion; + } + + protected void handleMessageInternal(Message message) { + try { + this.handleJMSMessageInternal(message); + } catch(Exception e) { + sendLogger.error("An error occurred while trying to send message:", e); + if(this.errorChannel != null) { + this.errorChannel.send(new ErrorMessage(e, message)); + } + throw e; + } + } + + protected void handleJMSMessageInternal(Message message) { + if (message == null) { + throw new IllegalArgumentException("message must not be null"); } - - public void setSerializerClassName(String serializerClassName) { - this.serializerClassName = serializerClassName; + Object objectToSend = this.serializeMessageIfRequired(message.getPayload()); + + Integer partitionToSend = getPartition(message); + HeaderMappingMessagePostProcessor messagePostProcessor = new HeaderMappingMessagePostProcessor( + message, + this.headerMapper, + partitionToSend, + mapHeaders + ); + messagePostProcessor.setDBVersion(this.dbversion); + + // try to read ConnectionCallback - if set + @SuppressWarnings("unchecked") + Consumer connCallback = (Consumer) message.getHeaders().get(TxEventQBinderHeaderConstants.CONNECTION_CONSUMER); + if(connCallback == null) { + this.jmsTemplate.convertAndSend( + destination, + objectToSend, + messagePostProcessor + ); + return; } - - public void setDBVersion(int dbversion) { - this.dbversion = dbversion; - } - - protected void handleMessageInternal(Message message) { - try { - this.jmsHandleMessageInternal(message); - } catch (Exception e) { - logger.error("An error occurred while trying to send message: " + e); - if (this.errorChannel != null) { - this.errorChannel.send(new ErrorMessage(e, message)); - } - throw e; - } + + Connection c = (Connection) message.getHeaders().get(TxEventQBinderHeaderConstants.MESSAGE_CONTEXT); + if(c == null) { + final Object actualPayload = objectToSend; + jmsTemplate.send(destination, session -> { + Connection sessionConnection = ((AQjmsSession) session).getDBConnection(); + connCallback.accept(sessionConnection); + MessageConverter msgConverter = this.jmsTemplate.getMessageConverter(); + if (msgConverter == null) { + throw new IllegalStateException("No 'messageConverter' specified. Check configuration of JmsTemplate."); + } + jakarta.jms.Message msg = msgConverter.toMessage(actualPayload, session); + return messagePostProcessor.postProcessMessage(msg); + }); + } else { + connCallback.accept(c); + // create topic connection and session using c + try(jakarta.jms.Connection conn = AQjmsTopicConnectionFactory.createTopicConnection(c); + Session s = conn.createSession(true, this.jmsTemplate.getSessionAcknowledgeMode()); + MessageProducer p = s.createProducer(destination)) { + MessageConverter msgConverter = this.jmsTemplate.getMessageConverter(); + if(msgConverter == null) { + throw new IllegalStateException("No 'messageConverter' specified. Check configuration of JmsTemplate."); + } + jakarta.jms.Message msg = msgConverter.toMessage(objectToSend, s); + jakarta.jms.Message msgToSend = messagePostProcessor.postProcessMessage(msg); + p.send(msgToSend); + s.commit(); + } catch (JMSException e) { + e.printStackTrace(); + } } - - protected void jmsHandleMessageInternal(Message message) { - if (message == null) { - throw new IllegalArgumentException("message must not be null"); - } - Object objectToSend = message.getPayload(); - - if (this.serializerClassName != null) { - Class serializer = null; - try { - serializer = Class.forName(this.serializerClassName); - } catch (ClassNotFoundException ce) { - logger.debug("The class name: " + serializerClassName + "is invalid."); - throw new IllegalArgumentException(ce.getMessage()); - } - - boolean isInstanceOfSerializer = false; - for (Class inter_face : serializer.getInterfaces()) { - if (inter_face.toString().equals(Serializer.class.toString())) { - isInstanceOfSerializer = true; - break; - } - } - - if (!isInstanceOfSerializer) { - logger.debug("The configured serializer class is not an instance of 'com.oracle.cstream.serialize.Serializer'"); - throw new IllegalArgumentException("The configured serializer class is not an instance of 'com.oracle.cstream.serialize.Serializer'"); - } - Serializer s = null; - - try { - s = (Serializer) (serializer.getDeclaredConstructor().newInstance()); - } catch (Exception e) { - logger.debug("Serializer object could not be initiated."); - throw new IllegalArgumentException("Serializer object could not be initiated."); - } - - objectToSend = s.serialize(objectToSend); - } - - Integer partitionToSend = getPartition(message); - HeaderMappingMessagePostProcessor messagePostProcessor = new HeaderMappingMessagePostProcessor( - message, - this.headerMapper, - partitionToSend, - mapHeaders - ); - messagePostProcessor.setDBVersion(this.dbversion); - - this.jmsTemplate.convertAndSend( - destination, - objectToSend, - messagePostProcessor - ); - - + } + + private Object serializeMessageIfRequired(Object objectToSend) { + if(this.serializerClassName != null) { + Class serializer = null; + try { + serializer = Class.forName(this.serializerClassName); + } catch(ClassNotFoundException ce) { + sendLogger.debug("The class name: {} is invalid.", serializerClassName); + throw new IllegalArgumentException(ce.getMessage()); + } + + boolean isInstanceOfSerializer = false; + for(Class inter_face: serializer.getInterfaces()) { + if(inter_face.toString().equals(Serializer.class.toString())) { + isInstanceOfSerializer = true; + break; + } + } + + if(!isInstanceOfSerializer) { + sendLogger.debug("The configured serializer class is not an instance of 'com.oracle.cstream.serialize.Serializer'"); + throw new IllegalArgumentException("The configured serializer class is not an instance of 'com.oracle.cstream.serialize.Serializer'"); + } + Serializer s = null; + + try { + s = (Serializer)(serializer.getDeclaredConstructor().newInstance()); + } catch(Exception e) { + sendLogger.debug("Serializer object could not be initiated."); + throw new IllegalArgumentException("Serializer object could not be initiated."); + } + + objectToSend = s.serialize(objectToSend); + } + return objectToSend; + } + + private Integer getPartition(Message message) { + try { + return (Integer)(message.getHeaders().get(BinderHeaders.PARTITION_HEADER)); + } catch(Exception e) { + sendLogger.info("Invalid Partition Index"); + throw new IllegalArgumentException("The partition index cannot be converted to an integer"); } + } - private Integer getPartition(Message message) { - try { - return (Integer) (message.getHeaders().get(BinderHeaders.PARTITION_HEADER)); - } catch (Exception e) { - logger.info("Invalid Partition Index"); - throw new IllegalArgumentException("The partition index cannot be converted to an integer"); - } - } + private static final class HeaderMappingMessagePostProcessor + implements MessagePostProcessor { - private static final class HeaderMappingMessagePostProcessor - implements MessagePostProcessor { - - private final Message integrationMessage; - private final JmsHeaderMapper headerMapper; - private final Integer partition; - private final boolean mapHeaders; - private int dbversion = 23; - - private HeaderMappingMessagePostProcessor( - Message integrationMessage, - JmsHeaderMapper headerMapper, - Integer pNum, - boolean mapHeaders - ) { - this.integrationMessage = integrationMessage; - this.headerMapper = headerMapper; - this.partition = pNum; - this.mapHeaders = mapHeaders; - } - - public void setDBVersion(int dbversion) { - this.dbversion = dbversion; - } - - public jakarta.jms.Message postProcessMessage( - jakarta.jms.Message jmsMessage - ) throws JMSException { - if (this.mapHeaders) { - this.headerMapper.fromHeaders( - this.integrationMessage.getHeaders(), - jmsMessage - ); - } - - // set partition property if not null - if (this.partition != null) { - if (this.dbversion == 19) - jmsMessage.setJMSCorrelationID("" + this.partition); - else - jmsMessage.setLongProperty("AQINTERNAL_PARTITION", this.partition * 2); - } else { - // choose 0 by default - if (this.dbversion != 19) - jmsMessage.setLongProperty("AQINTERNAL_PARTITION", 0); - } - - return jmsMessage; - } - } - - /* - TODO: This has to be re factored, there is an open issue https://github.com/spring-cloud/spring-cloud-stream/issues/607 - that requires some love first - */ - private boolean running; - - @Override - public synchronized void start() { - running = true; - } + private final Message integrationMessage; + private final JmsHeaderMapper headerMapper; + private final Integer partition; + private final boolean mapHeaders; + private int dbversion = 23; - @Override - public synchronized void stop() { - running = false; + private HeaderMappingMessagePostProcessor( + Message integrationMessage, + JmsHeaderMapper headerMapper, + Integer pNum, + boolean mapHeaders + ) { + this.integrationMessage = integrationMessage; + this.headerMapper = headerMapper; + this.partition = pNum; + this.mapHeaders = mapHeaders; } - @Override - public synchronized boolean isRunning() { - return running; + public void setDBVersion(int dbversion) { + this.dbversion = dbversion; + } + + public jakarta.jms.Message postProcessMessage( + jakarta.jms.Message jmsMessage + ) throws JMSException { + if(this.mapHeaders) { + this.headerMapper.fromHeaders( + this.integrationMessage.getHeaders(), + jmsMessage + ); + } + + // set partition property if not null + if(this.partition != null) { + if(this.dbversion == 19) + jmsMessage.setJMSCorrelationID("" + this.partition); + else + jmsMessage.setLongProperty("AQINTERNAL_PARTITION", this.partition * 2L); + } else { + // choose 0 by default + if(this.dbversion != 19) + jmsMessage.setLongProperty("AQINTERNAL_PARTITION", 0L); + } + + return jmsMessage; } + } + + /* + TODO: This has to be re factored, there is an open issue https://github.com/spring-cloud/spring-cloud-stream/issues/607 + that requires some love first + */ + private boolean running; + + @Override + public synchronized void start() { + running = true; + } + + @Override + public synchronized void stop() { + running = false; + } + + @Override + public synchronized boolean isRunning() { + return running; + } } diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/RepublishMessageRecoverer.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/RepublishMessageRecoverer.java index 31116ffc..f3ee4a95 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/RepublishMessageRecoverer.java +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/RepublishMessageRecoverer.java @@ -1,9 +1,9 @@ /* - ** TxEventQ Support for Spring Cloud Stream - ** Copyright (c) 2023, 2024 Oracle and/or its affiliates. - ** - ** This file has been modified by Oracle Corporation. - */ +** TxEventQ Support for Spring Cloud Stream +** Copyright (c) 2023, 2024 Oracle and/or its affiliates. +** +** This file has been modified by Oracle Corporation. +*/ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -26,9 +26,7 @@ import jakarta.jms.JMSException; import jakarta.jms.Message; - import java.util.Map; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.integration.jms.JmsHeaderMapper; @@ -40,55 +38,55 @@ public class RepublishMessageRecoverer implements MessageRecoverer { - public static final String X_EXCEPTION_MESSAGE = "x_exception_message"; - public static final String X_ORIGINAL_QUEUE = "x_original_queue"; - public static final String X_EXCEPTION_STACKTRACE = "x_exception_stacktrace"; - - private final Log logger = LogFactory.getLog(getClass()); - - private final JmsTemplate jmsTemplate; - private final JmsHeaderMapper mapper; - - public RepublishMessageRecoverer( - JmsTemplate jmsTemplate, - JmsHeaderMapper mapper - ) { - this.jmsTemplate = jmsTemplate; - this.mapper = mapper; + public static final String X_EXCEPTION_MESSAGE = "x_exception_message"; + public static final String X_ORIGINAL_QUEUE = "x_original_queue"; + public static final String X_EXCEPTION_STACKTRACE = "x_exception_stacktrace"; + + private final Log logger = LogFactory.getLog(getClass()); + + private final JmsTemplate jmsTemplate; + private final JmsHeaderMapper mapper; + + public RepublishMessageRecoverer( + JmsTemplate jmsTemplate, + JmsHeaderMapper mapper + ) { + this.jmsTemplate = jmsTemplate; + this.mapper = mapper; + } + + @Override + public void recover(Message undeliveredMessage, String dlq, Throwable cause) { + //String deadLetterQueueName = destination.getDlq(); + + MessageConverter converter = new SimpleMessageConverter(); + Object payload = null; + + try { + payload = converter.fromMessage(undeliveredMessage); + } catch (JMSException e) { + logger.error( + "The message payload could not be retrieved. It will be lost.", + e + ); } - @Override - public void recover(Message undeliveredMessage, String dlq, Throwable cause) { - //String deadLetterQueueName = destination.getDlq(); - - MessageConverter converter = new SimpleMessageConverter(); - Object payload = null; - - try { - payload = converter.fromMessage(undeliveredMessage); - } catch (JMSException e) { - logger.error( - "The message payload could not be retrieved. It will be lost.", - e - ); - } - - final Map headers = mapper.toHeaders(undeliveredMessage); - headers.put(X_EXCEPTION_STACKTRACE, getStackTraceAsString(cause)); - headers.put( - X_EXCEPTION_MESSAGE, - cause.getCause() != null - ? cause.getCause().getMessage() - : cause.getMessage() - ); - try { - headers.put( - X_ORIGINAL_QUEUE, - undeliveredMessage.getJMSDestination().toString() - ); - } catch (JMSException e) { - logger.error("The message destination could not be retrieved", e); - } + final Map headers = mapper.toHeaders(undeliveredMessage); + headers.put(X_EXCEPTION_STACKTRACE, getStackTraceAsString(cause)); + headers.put( + X_EXCEPTION_MESSAGE, + cause.getCause() != null + ? cause.getCause().getMessage() + : cause.getMessage() + ); + try { + headers.put( + X_ORIGINAL_QUEUE, + undeliveredMessage.getJMSDestination().toString() + ); + } catch (JMSException e) { + logger.error("The message destination could not be retrieved", e); + } // TODO use in a sublcass for a later version // Map additionalHeaders = additionalHeaders( // undeliveredMessage, @@ -98,43 +96,43 @@ public void recover(Message undeliveredMessage, String dlq, Throwable cause) { // headers.putAll(additionalHeaders); // } - jmsTemplate.convertAndSend( - dlq, - payload, - new MessagePostProcessor() { - @Override - public Message postProcessMessage(Message message) throws JMSException { - mapper.fromHeaders(new MessageHeaders(headers), message); - return message; - } - } - ); - } - - /** - * Provide additional headers for the message. - * - *

Subclasses can override this method to add more headers to the - * undelivered message when it is republished to the DLQ. - * - * @param message The failed message. - * @param cause The cause. - * @return A {@link Map} of additional headers to add. - */ - protected Map additionalHeaders( - Message message, - Throwable cause - ) { - return null; - } - - private String getStackTraceAsString(Throwable cause) { + jmsTemplate.convertAndSend( + dlq, + payload, + new MessagePostProcessor() { + @Override + public Message postProcessMessage(Message message) throws JMSException { + mapper.fromHeaders(new MessageHeaders(headers), message); + return message; + } + } + ); + } + + /** + * Provide additional headers for the message. + * + *

Subclasses can override this method to add more headers to the + * undelivered message when it is republished to the DLQ. + * + * @param message The failed message. + * @param cause The cause. + * @return A {@link Map} of additional headers to add. + */ + protected Map additionalHeaders( + Message message, + Throwable cause + ) { + return null; + } + + private String getStackTraceAsString(Throwable cause) { /* StringWriter stringWriter = new StringWriter(); PrintWriter printWriter = new PrintWriter(stringWriter, true); cause.printStackTrace(printWriter); return stringWriter.getBuffer().toString(); */ - return cause.getMessage(); - } + return cause.getMessage(); + } } diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/SpecCompliantJmsHeaderMapper.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/SpecCompliantJmsHeaderMapper.java index 47a06c39..8400373f 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/SpecCompliantJmsHeaderMapper.java +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/SpecCompliantJmsHeaderMapper.java @@ -1,9 +1,9 @@ /* - ** TxEventQ Support for Spring Cloud Stream - ** Copyright (c) 2023, 2024 Oracle and/or its affiliates. - ** - ** This file has been modified by Oracle Corporation. - */ +** TxEventQ Support for Spring Cloud Stream +** Copyright (c) 2023, 2024 Oracle and/or its affiliates. +** +** This file has been modified by Oracle Corporation. +*/ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -27,6 +27,7 @@ import jakarta.jms.Message; import java.io.Serializable; +import java.sql.Connection; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -42,58 +43,71 @@ public class SpecCompliantJmsHeaderMapper extends DefaultJmsHeaderMapper { - private static final Logger logger = LoggerFactory.getLogger( - SpecCompliantJmsHeaderMapper.class - ); - - private static final List> SUPPORTED_PROPERTY_TYPES = - Arrays.asList(Boolean.class, Byte.class, Double.class, Float.class, Integer.class, Long.class, Short.class, - String.class, byte[].class, UUID.class); - - private static final List DEFAULT_TO_STRING_CLASSES = - Arrays.asList( - "org.springframework.util.MimeType", - "org.springframework.http.MediaType" - ); - - public void addDefaultToStringClass(String className) { - SpecCompliantJmsHeaderMapper.DEFAULT_TO_STRING_CLASSES.add(className); - } + private static final Logger logger = LoggerFactory.getLogger( + SpecCompliantJmsHeaderMapper.class + ); + + private static final List> SUPPORTED_PROPERTY_TYPES = + Arrays.asList(Boolean.class, Byte.class, Double.class, Float.class, Integer.class, Long.class, Short.class, + String.class, byte[].class, UUID.class); + + private static final List DEFAULT_TO_STRING_CLASSES = + Arrays.asList( + "org.springframework.util.MimeType", + "org.springframework.http.MediaType" + ); + + public void addDefaultToStringClass(String className) { + SpecCompliantJmsHeaderMapper.DEFAULT_TO_STRING_CLASSES.add(className); + } + + public List getDefaultToStringClasses() { + return new ArrayList<>(SpecCompliantJmsHeaderMapper.DEFAULT_TO_STRING_CLASSES); + } + + private Connection conn; + + public void setConnection(Connection c) { + this.conn = c; + } - public List getDefaultToStringClasses() { - return new ArrayList<>(SpecCompliantJmsHeaderMapper.DEFAULT_TO_STRING_CLASSES); + @Override + public void fromHeaders(MessageHeaders headers, Message jmsMessage) { + Map compliantHeaders = new HashMap<>(headers.size()); + for (Map.Entry entry : headers.entrySet()) { + if (entry.getKey().contains("-")) { + String key = entry.getKey().replace("-", "_"); + logger.trace("Rewriting header name '{}' to conform to JMS spec", key); + compliantHeaders.put(key, entry.getValue()); + } else { + compliantHeaders.put(entry.getKey(), entry.getValue()); + } } - - @Override - public void fromHeaders(MessageHeaders headers, Message jmsMessage) { - Map compliantHeaders = new HashMap<>(headers.size()); - for (Map.Entry entry : headers.entrySet()) { - if (entry.getKey().contains("-")) { - String key = entry.getKey().replaceAll("-", "_"); - logger.trace("Rewriting header name '{}' to conform to JMS spec", key); - compliantHeaders.put(key, entry.getValue()); - } else { - compliantHeaders.put(entry.getKey(), entry.getValue()); - } - } - - // for each header, if its value belongs to toString - // classes, convert to String - for (Map.Entry entry : compliantHeaders.entrySet()) { - Object value = entry.getValue(); - if (SpecCompliantJmsHeaderMapper.DEFAULT_TO_STRING_CLASSES.contains(value.getClass().getName())) { - compliantHeaders.put(entry.getKey(), value.toString()); - } else if (!SUPPORTED_PROPERTY_TYPES.contains(value.getClass())) { - if (value instanceof Serializable) { - logger.info("Serializing {} header object", value); - compliantHeaders.put(entry.getKey(), SerializationUtils.serialize(value)); - } else { - logger.info("Storing String representation for header: {}", entry.getKey()); - compliantHeaders.put(entry.getKey(), value.toString()); - } - } + + // for each header, if its value belongs to toString + // classes, convert to String + for (Map.Entry entry : compliantHeaders.entrySet()){ + Object value = entry.getValue(); + if(SpecCompliantJmsHeaderMapper.DEFAULT_TO_STRING_CLASSES.contains(value.getClass().getName())) { + compliantHeaders.put(entry.getKey(), value.toString()); + } else if(!SUPPORTED_PROPERTY_TYPES.contains(value.getClass())) { + if(value instanceof Serializable) { + logger.info("Serializing {} header object", value); + compliantHeaders.put(entry.getKey(), SerializationUtils.serialize(value)); + } else { + logger.info("Storing String representation for header: {}", entry.getKey()); + compliantHeaders.put(entry.getKey(), value.toString()); + } } - - super.fromHeaders(new MessageHeaders(compliantHeaders), jmsMessage); } + + super.fromHeaders(new MessageHeaders(compliantHeaders), jmsMessage); + } + + @Override + public Map toHeaders(Message jmsMessage) { + Map headers = super.toHeaders(jmsMessage); + headers.put(TxEventQBinderHeaderConstants.MESSAGE_CONNECTION, this.conn); + return headers; + } } diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/TEQBatchMessageListener.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/TEQBatchMessageListener.java index cdd950a3..591e8c95 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/TEQBatchMessageListener.java +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/TEQBatchMessageListener.java @@ -1,9 +1,9 @@ /* - ** TxEventQ Support for Spring Cloud Stream - ** Copyright (c) 2023, 2024 Oracle and/or its affiliates. - ** - ** This file has been modified by Oracle Corporation. - */ +** TxEventQ Support for Spring Cloud Stream +** Copyright (c) 2023, 2024 Oracle and/or its affiliates. +** +** This file has been modified by Oracle Corporation. +*/ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -54,276 +54,280 @@ import jakarta.jms.Session; public class TEQBatchMessageListener extends ChannelPublishingJmsMessageListener { - private final GatewayDelegate gatewayDelegate = new GatewayDelegate(); - - private boolean expectReply; - - private MessageConverter messageConverter = new SimpleMessageConverter(); - - private boolean extractRequestPayload = true; - - private JmsHeaderMapper headerMapper = new DefaultJmsHeaderMapper(); - - private BeanFactory beanFactory; - - private MessageBuilderFactory messageBuilderFactory = new DefaultMessageBuilderFactory(); - - private static final String TEQ_BATCHED_HEADERS = "teq_batched_headers"; - - private RetryTemplate retryTemplate; - - private RecoveryCallback recoverer; - - private final String RETRY_CONTEXT_MESSAGE_ATTRIBUTE = "message"; - - private String deSerializerClassName = null; - - public String getDeSerializerClassName() { - return deSerializerClassName; - } - - public void setDeSerializerClassName(String deSerializerClassName) { - this.deSerializerClassName = deSerializerClassName; - } - - @Override - public void setExpectReply(boolean expectReply) { - this.expectReply = expectReply; - } - - @Override - public void setComponentName(String componentName) { - this.gatewayDelegate.setComponentName(componentName); - } - - @Override - public void setRequestChannel(MessageChannel requestChannel) { - this.gatewayDelegate.setRequestChannel(requestChannel); - } - - @Override - public void setRequestChannelName(String requestChannelName) { - this.gatewayDelegate.setRequestChannelName(requestChannelName); - } - - public void setRetryTemplate(RetryTemplate retryTemplate) { - this.retryTemplate = retryTemplate; - } - - public void setRecoverer(RecoveryCallback recoverer) { - this.recoverer = recoverer; - } - - @Override - public void setReplyChannel(MessageChannel replyChannel) { - this.gatewayDelegate.setReplyChannel(replyChannel); - } - - @Override - public void setReplyChannelName(String replyChannelName) { - this.gatewayDelegate.setReplyChannelName(replyChannelName); - } - - @Override - public void setErrorChannel(MessageChannel errorChannel) { - this.gatewayDelegate.setErrorChannel(errorChannel); - } - - @Override - public void setErrorChannelName(String errorChannelName) { - this.gatewayDelegate.setErrorChannelName(errorChannelName); - } - - @Override - public void setRequestTimeout(long requestTimeout) { - this.gatewayDelegate.setRequestTimeout(requestTimeout); - } - - @Override - public void setReplyTimeout(long replyTimeout) { - this.gatewayDelegate.setReplyTimeout(replyTimeout); - } - - @Override - public void setErrorOnTimeout(boolean errorOnTimeout) { - this.gatewayDelegate.setErrorOnTimeout(errorOnTimeout); - } - - @Override - public void setShouldTrack(boolean shouldTrack) { - this.gatewayDelegate.setShouldTrack(shouldTrack); - } - - @Override - public String getComponentName() { - return this.gatewayDelegate.getComponentName(); - } - - @Override - public String getComponentType() { - return this.gatewayDelegate.getComponentType(); - } - - @Override - public void setMessageConverter(MessageConverter messageConverter) { - this.messageConverter = messageConverter; - } - - @Override - public void setHeaderMapper(JmsHeaderMapper headerMapper) { - this.headerMapper = headerMapper; - } - - @Override - public void setExtractRequestPayload(boolean extractRequestPayload) { - this.extractRequestPayload = extractRequestPayload; - } - - - @Override - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - this.beanFactory = beanFactory; - } - - @Override - public void onMessage(jakarta.jms.Message jmsMessage, Session session) throws JMSException { - throw new IllegalArgumentException("Single consumer message listener should not be called for batched listener!!"); - } - - public void onMessage(List jmsMessages, Session session) throws JMSException { - this.retryTemplate - .execute(retryContext -> { - try { - - if (deSerializerClassName != null) { - // get class name and parameterized type name - CustomSerializationMessageConverter customConverter = new CustomSerializationMessageConverter(); - customConverter.setDeserializer(deSerializerClassName); - TEQBatchMessageListener.this.setMessageConverter( - customConverter - ); - } - - retryContext.setAttribute( - RETRY_CONTEXT_MESSAGE_ATTRIBUTE, - jmsMessages - ); - - TEQBatchMessageListener.this.onMessageHelper( - jmsMessages, - session - ); - } catch (JMSException e) { - logger.error(e, "Failed to send message"); - for (jakarta.jms.Message jmsMessage : jmsMessages) - TEQBatchMessageListener.this.resetMessageIfRequired(jmsMessage); - throw new RuntimeException(e); - } catch (Exception e) { - for (jakarta.jms.Message jmsMessage : jmsMessages) - TEQBatchMessageListener.this.resetMessageIfRequired(jmsMessage); - throw e; - } - return null; - }, - this.recoverer + private final GatewayDelegate gatewayDelegate = new GatewayDelegate(); + + private boolean expectReply; + + private MessageConverter messageConverter = new SimpleMessageConverter(); + + private boolean extractRequestPayload = true; + + private JmsHeaderMapper headerMapper = new DefaultJmsHeaderMapper(); + + private BeanFactory beanFactory; + + private MessageBuilderFactory messageBuilderFactory = new DefaultMessageBuilderFactory(); + + private static final String TEQ_BATCHED_HEADERS = "teq_batched_headers"; + + private RetryTemplate retryTemplate; + + private RecoveryCallback recoverer; + + private String RETRY_CONTEXT_MESSAGE_ATTRIBUTE = "message"; + + private String deSerializerClassName = null; + + public String getDeSerializerClassName() { + return deSerializerClassName; + } + + public void setDeSerializerClassName(String deSerializerClassName) { + this.deSerializerClassName = deSerializerClassName; + } + + @Override + public void setExpectReply(boolean expectReply) { + this.expectReply = expectReply; + } + + @Override + public void setComponentName(String componentName) { + this.gatewayDelegate.setComponentName(componentName); + } + + @Override + public void setRequestChannel(MessageChannel requestChannel) { + this.gatewayDelegate.setRequestChannel(requestChannel); + } + + @Override + public void setRequestChannelName(String requestChannelName) { + this.gatewayDelegate.setRequestChannelName(requestChannelName); + } + + public void setRetryTemplate(RetryTemplate retryTemplate) { + this.retryTemplate = retryTemplate; + } + + public void setRecoverer(RecoveryCallback recoverer) { + this.recoverer = recoverer; + } + + @Override + public void setReplyChannel(MessageChannel replyChannel) { + this.gatewayDelegate.setReplyChannel(replyChannel); + } + + @Override + public void setReplyChannelName(String replyChannelName) { + this.gatewayDelegate.setReplyChannelName(replyChannelName); + } + + @Override + public void setErrorChannel(MessageChannel errorChannel) { + this.gatewayDelegate.setErrorChannel(errorChannel); + } + + @Override + public void setErrorChannelName(String errorChannelName) { + this.gatewayDelegate.setErrorChannelName(errorChannelName); + } + + @Override + public void setRequestTimeout(long requestTimeout) { + this.gatewayDelegate.setRequestTimeout(requestTimeout); + } + + @Override + public void setReplyTimeout(long replyTimeout) { + this.gatewayDelegate.setReplyTimeout(replyTimeout); + } + + @Override + public void setErrorOnTimeout(boolean errorOnTimeout) { + this.gatewayDelegate.setErrorOnTimeout(errorOnTimeout); + } + + @Override + public void setShouldTrack(boolean shouldTrack) { + this.gatewayDelegate.setShouldTrack(shouldTrack); + } + + @Override + public String getComponentName() { + return this.gatewayDelegate.getComponentName(); + } + + @Override + public String getComponentType() { + return this.gatewayDelegate.getComponentType(); + } + + @Override + public void setMessageConverter(MessageConverter messageConverter) { + this.messageConverter = messageConverter; + } + + @Override + public void setHeaderMapper(JmsHeaderMapper headerMapper) { + this.headerMapper = headerMapper; + } + + @Override + public void setExtractRequestPayload(boolean extractRequestPayload) { + this.extractRequestPayload = extractRequestPayload; + } + + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.beanFactory = beanFactory; + } + + @Override + public void onMessage(jakarta.jms.Message jmsMessage, Session session) throws JMSException { + throw new IllegalArgumentException("Single consumer message listener should not be called for batched listener!!"); + } + + public void onMessage(List jmsMessages, Session session) throws JMSException { + this.retryTemplate + .execute(retryContext -> { + try { + + if(deSerializerClassName != null) { + // get class name and parameterized type name + CustomSerializationMessageConverter customConverter = new CustomSerializationMessageConverter(); + customConverter.setDeserializer(deSerializerClassName); + TEQBatchMessageListener.this.setMessageConverter( + customConverter + ); + } + + retryContext.setAttribute( + RETRY_CONTEXT_MESSAGE_ATTRIBUTE, + jmsMessages + ); + + TEQBatchMessageListener.this.onMessageHelper( + jmsMessages, + session ); - } - - protected void resetMessageIfRequired(jakarta.jms.Message jmsMessage) - throws JMSException { - if (jmsMessage instanceof BytesMessage message) { - message.reset(); - } - } - - public void onMessageHelper(List jmsMessages, Session session) throws JMSException { - Message requestMessage = null; - try { - // result will store the list of appropriately converted message - final List result = new ArrayList<>(); - List> individual_headers = new ArrayList<>(); - if (this.extractRequestPayload) { - for (jakarta.jms.Message jmsMessage : jmsMessages) { - Object payload = this.messageConverter.fromMessage(jmsMessage); - result.add(payload); - Map headers = this.headerMapper.toHeaders(jmsMessage); - individual_headers.add(headers); - this.logger.debug(() -> "converted JMS Message [" + jmsMessage + "] to integration Message payload [" - + payload + "]"); - } - } else { - for (jakarta.jms.Message jmsMessage : jmsMessages) - result.add(jmsMessage); - } - - requestMessage = this.messageBuilderFactory - .withPayload(result) - .setHeader(TEQ_BATCHED_HEADERS, individual_headers) - .build(); - - } catch (RuntimeException e) { - MessageChannel errorChannel = this.gatewayDelegate.getErrorChannel(); - if (errorChannel == null) { + } catch (JMSException e) { + logger.error(e, "Failed to send message"); + for(jakarta.jms.Message jmsMessage: jmsMessages) + TEQBatchMessageListener.this.resetMessageIfRequired(jmsMessage); + throw new RuntimeException(e); + } catch (Exception e) { + for(jakarta.jms.Message jmsMessage: jmsMessages) + TEQBatchMessageListener.this.resetMessageIfRequired(jmsMessage); throw e; - } - this.gatewayDelegate.getMessagingTemplate() - .send(errorChannel, - this.gatewayDelegate.buildErrorMessage( - new MessagingException("Inbound conversion failed for: " + jmsMessages, e))); - return; - } - - this.gatewayDelegate.send(requestMessage); - } - - @Override - public void afterPropertiesSet() { - if (this.beanFactory != null) { - this.gatewayDelegate.setBeanFactory(this.beanFactory); - } - this.gatewayDelegate.afterPropertiesSet(); - this.messageBuilderFactory = IntegrationUtils.getMessageBuilderFactory(this.beanFactory); - } - - protected void start() { - this.gatewayDelegate.start(); - } - - protected void stop() { - this.gatewayDelegate.stop(); - } - - private class GatewayDelegate extends MessagingGatewaySupport { - - GatewayDelegate() { - } - - @Override - protected void send(Object request) { // NOSONAR - not useless, increases visibility - super.send(request); - } - - @Override - protected Message sendAndReceiveMessage(Object request) { // NOSONAR - not useless, increases visibility - return super.sendAndReceiveMessage(request); - } - - protected ErrorMessage buildErrorMessage(Throwable throwable) { - return super.buildErrorMessage(null, throwable); - } - - protected MessagingTemplate getMessagingTemplate() { - return this.messagingTemplate; - } - - @Override - public String getComponentType() { - if (TEQBatchMessageListener.this.expectReply) { - return "jms:inbound-gateway"; - } else { - return "jms:message-driven-channel-adapter"; - } - } - - } + } + return null; + }, + this.recoverer + ); + } + + protected void resetMessageIfRequired(jakarta.jms.Message jmsMessage) + throws JMSException { + if (jmsMessage instanceof BytesMessage) { + BytesMessage message = (BytesMessage) jmsMessage; + message.reset(); + } + } + + public void onMessageHelper(List jmsMessages, Session session) throws JMSException { + Message requestMessage = null; + try { + // result will store the list of appropriately converted message + final List result = new ArrayList<>(); + List> individual_headers = new ArrayList<>(); + if (this.extractRequestPayload) { + for(jakarta.jms.Message jmsMessage: jmsMessages) { + Object payload = this.messageConverter.fromMessage(jmsMessage); + result.add(payload); + Map headers = this.headerMapper.toHeaders(jmsMessage); + individual_headers.add(headers); + this.logger.debug(() -> "converted JMS Message [" + jmsMessage + "] to integration Message payload [" + + payload + "]"); + } + } + else { + for(jakarta.jms.Message jmsMessage: jmsMessages) + result.add((Object)jmsMessage); + } + + requestMessage = this.messageBuilderFactory + .withPayload(result) + .setHeader(TEQ_BATCHED_HEADERS, individual_headers) + .build(); + + } + catch (RuntimeException e) { + MessageChannel errorChannel = this.gatewayDelegate.getErrorChannel(); + if (errorChannel == null) { + throw e; + } + this.gatewayDelegate.getMessagingTemplate() + .send(errorChannel, + this.gatewayDelegate.buildErrorMessage( + new MessagingException("Inbound conversion failed for: " + jmsMessages, e))); + return; + } + + this.gatewayDelegate.send(requestMessage); + } + + @Override + public void afterPropertiesSet() { + if (this.beanFactory != null) { + this.gatewayDelegate.setBeanFactory(this.beanFactory); + } + this.gatewayDelegate.afterPropertiesSet(); + this.messageBuilderFactory = IntegrationUtils.getMessageBuilderFactory(this.beanFactory); + } + + protected void start() { + this.gatewayDelegate.start(); + } + + protected void stop() { + this.gatewayDelegate.stop(); + } + + private class GatewayDelegate extends MessagingGatewaySupport { + + GatewayDelegate() { + } + + @Override + protected void send(Object request) { // NOSONAR - not useless, increases visibility + super.send(request); + } + + @Override + protected Message sendAndReceiveMessage(Object request) { // NOSONAR - not useless, increases visibility + return super.sendAndReceiveMessage(request); + } + + protected ErrorMessage buildErrorMessage(Throwable throwable) { + return super.buildErrorMessage(null, throwable); + } + + protected MessagingTemplate getMessagingTemplate() { + return this.messagingTemplate; + } + + @Override + public String getComponentType() { + if (TEQBatchMessageListener.this.expectReply) { + return "jms:inbound-gateway"; + } + else { + return "jms:message-driven-channel-adapter"; + } + } + + } } \ No newline at end of file diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/TEQBatchMessageListenerContainer.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/TEQBatchMessageListenerContainer.java index 1c77dfd0..945d50b9 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/TEQBatchMessageListenerContainer.java +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/TEQBatchMessageListenerContainer.java @@ -1,9 +1,9 @@ /* - ** TxEventQ Support for Spring Cloud Stream - ** Copyright (c) 2023, 2024 Oracle and/or its affiliates. - ** - ** This file has been modified by Oracle Corporation. - */ +** TxEventQ Support for Spring Cloud Stream +** Copyright (c) 2023, 2024 Oracle and/or its affiliates. +** +** This file has been modified by Oracle Corporation. +*/ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -25,7 +25,6 @@ package com.oracle.cstream.utils; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import org.springframework.jms.connection.ConnectionFactoryUtils; @@ -47,308 +46,324 @@ import oracle.jakarta.jms.AQjmsConsumer; public class TEQBatchMessageListenerContainer extends DefaultMessageListenerContainer { - private final MessageListenerContainerResourceFactory transactionalResourceFactory = - new MessageListenerContainerResourceFactory(); - - /** - * Instance variable to consume from a specific partition - */ - private int partition = -1; - - /** - * Instance variable to consume messages in a batch - */ - private int batchSize = 10; - - /** - * Getters and setters for partition - */ - public int getPartition() { - return this.partition; - } - - public void setPartition(int partition) { - this.partition = partition; - } - - public int getBatchSize() { - return this.batchSize; - } - - public void setBatchSize(int bSize) { - this.batchSize = bSize; - } - - /** - * Create a JMS MessageConsumer for the given Session and Destination. - *

This implementation uses JMS 1.1 API. - * Also sets the corresponding partition on AQjmsConsumer - * - * @param session the JMS Session to create a MessageConsumer for - * @param destination the JMS Destination to create a MessageConsumer for - * @return the new JMS MessageConsumer - * @throws jakarta.jms.JMSException if thrown by JMS API methods - */ - @Override - protected MessageConsumer createConsumer(Session session, Destination destination) throws JMSException { - MessageConsumer consumer = super.createConsumer(session, destination); - if (this.partition != -1) - ((AQjmsConsumer) consumer).setPartition(this.partition); - return consumer; - } - - protected List receiveBatch(MessageConsumer consumer, long timeout) throws JMSException { - List msgs = new ArrayList(); - if (timeout > 0) { - Message[] messages = ((AQjmsConsumer) consumer).bulkReceive(this.batchSize, timeout); - if (messages == null) return null; - Collections.addAll(msgs, messages); - } else if (timeout < 0) { - Message[] messages = ((AQjmsConsumer) consumer).bulkReceiveNoWait(this.batchSize); - if (messages == null) return null; - Collections.addAll(msgs, messages); - } else { - Message[] messages = ((AQjmsConsumer) consumer).bulkReceive(this.batchSize); - if (messages == null) return null; - Collections.addAll(msgs, messages); - } - return msgs; - } - - // use -> to receive in batch - - /** - * Actually execute the listener for a message received from the given consumer, - * fetching all requires resources and invoking the listener. - * - * @param session the JMS Session to work on - * @param consumer the MessageConsumer to work on - * @param status the TransactionStatus (may be {@code null}) - * @return whether a message has been received - * @throws JMSException if thrown by JMS methods - * @see #doExecuteListener(jakarta.jms.Session, jakarta.jms.Message) - */ - @Override - protected boolean doReceiveAndExecute(Object invoker, @Nullable Session session, - @Nullable MessageConsumer consumer, @Nullable TransactionStatus status) throws JMSException { - - Connection conToClose = null; - Session sessionToClose = null; - MessageConsumer consumerToClose = null; - try { - Session sessionToUse = session; - boolean transactional = false; - if (sessionToUse == null) { - sessionToUse = ConnectionFactoryUtils.doGetTransactionalSession( - obtainConnectionFactory(), this.transactionalResourceFactory, true); - transactional = (sessionToUse != null); - } - if (sessionToUse == null) { - Connection conToUse; - if (sharedConnectionEnabled()) { - conToUse = getSharedConnection(); - } else { - conToUse = createConnection(); - conToClose = conToUse; - conToUse.start(); - } - sessionToUse = createSession(conToUse); - sessionToClose = sessionToUse; - } - MessageConsumer consumerToUse = consumer; - if (consumerToUse == null) { - consumerToUse = createListenerConsumer(sessionToUse); - consumerToClose = consumerToUse; - } - List messages = receiveBatch(consumer, getReceiveTimeout()); - if (messages != null) { - if (logger.isDebugEnabled()) { - logger.debug("Received message of type [" + messages.getClass() + "] from consumer [" + - consumerToUse + "] of " + (transactional ? "transactional " : "") + "session [" + - sessionToUse + "]"); - } - messageReceived(invoker, sessionToUse); - boolean exposeResource = (!transactional && isExposeListenerSession() && - !TransactionSynchronizationManager.hasResource(obtainConnectionFactory())); - if (exposeResource) { - TransactionSynchronizationManager.bindResource( - obtainConnectionFactory(), new LocallyExposedJmsResourceHolder(sessionToUse)); - } - try { - doExecuteListener(sessionToUse, messages); - } catch (Throwable ex) { - if (status != null) { - if (logger.isDebugEnabled()) { - logger.debug("Rolling back transaction because of listener exception thrown: " + ex); - } - status.setRollbackOnly(); - } - handleListenerException(ex); - // Rethrow JMSException to indicate an infrastructure problem - // that may have to trigger recovery... - if (ex instanceof JMSException jmsException) { - throw jmsException; - } - } finally { - if (exposeResource) { - TransactionSynchronizationManager.unbindResource(obtainConnectionFactory()); - } - } - // Indicate that a message has been received. - return true; - } else { - if (logger.isTraceEnabled()) { - logger.trace("Consumer [" + consumerToUse + "] of " + (transactional ? "transactional " : "") + - "session [" + sessionToUse + "] did not receive a message"); - } - noMessageReceived(invoker, sessionToUse); - // Nevertheless call commit, in order to reset the transaction timeout (if any). - if (shouldCommitAfterNoMessageReceived(sessionToUse)) { - super.commitIfNecessary(sessionToUse, null); - } - // Indicate that no message has been received. - return false; - } - } finally { - JmsUtils.closeMessageConsumer(consumerToClose); - JmsUtils.closeSession(sessionToClose); - ConnectionFactoryUtils.releaseConnection(conToClose, getConnectionFactory(), true); - } - } - - protected void doExecuteListener(Session session, List messages) throws JMSException { - if (!isAcceptMessagesWhileStopping() && !isRunning()) { - if (logger.isWarnEnabled()) { - logger.warn("Rejecting received messages because of the listener container " + - "having been stopped in the meantime: " + messages); - } - rollbackIfNecessary(session); - throw new MessageRejectedWhileStoppingException(); - } - - try { - invokeListener(session, messages); - } catch (JMSException | RuntimeException | Error ex) { - rollbackOnExceptionIfNecessary(session, ex); - throw ex; - } - commitIfNecessary(session, messages); - } - - protected void commitIfNecessary(Session session, @Nullable List messages) throws JMSException { - // Commit session or acknowledge message. - if (session.getTransacted()) { - // Commit necessary - but avoid commit call within a JTA transaction. - if (isSessionLocallyTransacted(session)) { - // Transacted session created by this container -> commit. - JmsUtils.commitIfNecessary(session); - } - } else if (messages != null && isClientAcknowledge(session)) { - // acknowledge last message of the group only - messages.get(messages.size() - 1).acknowledge(); - } - } - - @Override - protected boolean isSessionLocallyTransacted(Session session) { - if (!isSessionTransacted()) { - return false; - } - JmsResourceHolder resourceHolder = - (JmsResourceHolder) TransactionSynchronizationManager.getResource(obtainConnectionFactory()); - return (resourceHolder == null || resourceHolder instanceof LocallyExposedJmsResourceHolder || - !resourceHolder.containsSession(session)); - } - - protected void invokeListener(Session session, List messages) throws JMSException { - Object listener = getMessageListener(); - - if (listener instanceof TEQBatchMessageListener teqBatchListener) { - this.doInvokeListener(teqBatchListener, session, messages); - } else if (listener != null) { - throw new IllegalArgumentException( - "Only TEQBatchMessageListener supported: " + listener); - } else { - throw new IllegalStateException("No message listener specified - see property 'messageListener'"); - } - } - - protected void doInvokeListener(TEQBatchMessageListener listener, Session session, List messages) - throws JMSException { - - Connection conToClose = null; - Session sessionToClose = null; - try { - Session sessionToUse = session; - if (!isExposeListenerSession()) { - // We need to expose a separate Session. - conToClose = createConnection(); - sessionToClose = createSession(conToClose); - sessionToUse = sessionToClose; - } - // Actually invoke the message listener... - listener.setHeaderMapper(new SpecCompliantJmsHeaderMapper()); - listener.onMessage(messages, sessionToUse); - // Clean up specially exposed Session, if any. - if (sessionToUse != session) { - if (sessionToUse.getTransacted() && isSessionLocallyTransacted(sessionToUse)) { - // Transacted session created by this container -> commit. - JmsUtils.commitIfNecessary(sessionToUse); - } - } - } finally { - JmsUtils.closeSession(sessionToClose); - JmsUtils.closeConnection(conToClose); - } - } - - /** - * ResourceFactory implementation that delegates to this listener container's protected callback methods. - */ - private class MessageListenerContainerResourceFactory implements ConnectionFactoryUtils.ResourceFactory { - - @Override - @Nullable - public Connection getConnection(JmsResourceHolder holder) { - return TEQBatchMessageListenerContainer.this.getConnection(holder); - } - - @Override - @Nullable - public Session getSession(JmsResourceHolder holder) { - return TEQBatchMessageListenerContainer.this.getSession(holder); - } - - @Override - public Connection createConnection() throws JMSException { - if (TEQBatchMessageListenerContainer.this.sharedConnectionEnabled()) { - Connection sharedCon = TEQBatchMessageListenerContainer.this.getSharedConnection(); - return new SingleConnectionFactory(sharedCon).createConnection(); - } else { - return TEQBatchMessageListenerContainer.this.createConnection(); - } - } - - @Override - public Session createSession(Connection con) throws JMSException { - return TEQBatchMessageListenerContainer.this.createSession(con); - } - - @Override - public boolean isSynchedLocalTransactionAllowed() { - return TEQBatchMessageListenerContainer.this.isSessionTransacted(); - } - } - - @SuppressWarnings("serial") - private static class MessageRejectedWhileStoppingException extends RuntimeException { - } + private final MessageListenerContainerResourceFactory transactionalResourceFactory = + new MessageListenerContainerResourceFactory(); + + /** + * Instance variable to consume from a specific partition + */ + private int partition = -1; + + /** + * Instance variable to consume messages in a batch + */ + private int batchSize = 10; + + /** + * Getters and setters for partition + */ + public int getPartition() { + return this.partition; + } + + public void setPartition(int partition) { + this.partition = partition; + } + + public int getBatchSize() { + return this.batchSize; + } + + public void setBatchSize(int bSize) { + this.batchSize = bSize; + } + + /** + * Create a JMS MessageConsumer for the given Session and Destination. + *

This implementation uses JMS 1.1 API. + * Also sets the corresponding partition on AQjmsConsumer + * @param session the JMS Session to create a MessageConsumer for + * @param destination the JMS Destination to create a MessageConsumer for + * @return the new JMS MessageConsumer + * @throws jakarta.jms.JMSException if thrown by JMS API methods + */ + @Override + protected MessageConsumer createConsumer(Session session, Destination destination) throws JMSException { + MessageConsumer consumer = super.createConsumer(session, destination); + if(this.partition != -1) + ((AQjmsConsumer)consumer).setPartition(this.partition); + return consumer; + } + + protected List receiveBatch(MessageConsumer consumer, long timeout) throws JMSException { + List msgs = new ArrayList(); + if (timeout > 0) { + Message[] messages = ((AQjmsConsumer) consumer).bulkReceive(this.batchSize, timeout); + if(messages == null) return null; + for(Message msg: messages) { + msgs.add(msg); + } + } + else if (timeout < 0) { + Message[] messages = ((AQjmsConsumer) consumer).bulkReceiveNoWait(this.batchSize); + if(messages == null) return null; + for(Message msg: messages) { + msgs.add(msg); + } + } + else { + Message[] messages = ((AQjmsConsumer) consumer).bulkReceive(this.batchSize); + if(messages == null) return null; + for(Message msg: messages) { + msgs.add(msg); + } + } + return msgs; + } + + // use -> to receive in batch + /** + * Actually execute the listener for a message received from the given consumer, + * fetching all requires resources and invoking the listener. + * @param session the JMS Session to work on + * @param consumer the MessageConsumer to work on + * @param status the TransactionStatus (may be {@code null}) + * @return whether a message has been received + * @throws JMSException if thrown by JMS methods + * @see #doExecuteListener(jakarta.jms.Session, jakarta.jms.Message) + */ + @Override + protected boolean doReceiveAndExecute(Object invoker, @Nullable Session session, + @Nullable MessageConsumer consumer, @Nullable TransactionStatus status) throws JMSException { + + Connection conToClose = null; + Session sessionToClose = null; + MessageConsumer consumerToClose = null; + try { + Session sessionToUse = session; + boolean transactional = false; + if (sessionToUse == null) { + sessionToUse = ConnectionFactoryUtils.doGetTransactionalSession( + obtainConnectionFactory(), this.transactionalResourceFactory, true); + transactional = (sessionToUse != null); + } + if (sessionToUse == null) { + Connection conToUse; + if (sharedConnectionEnabled()) { + conToUse = getSharedConnection(); + } + else { + conToUse = createConnection(); + conToClose = conToUse; + conToUse.start(); + } + sessionToUse = createSession(conToUse); + sessionToClose = sessionToUse; + } + MessageConsumer consumerToUse = consumer; + if (consumerToUse == null) { + consumerToUse = createListenerConsumer(sessionToUse); + consumerToClose = consumerToUse; + } + List messages = receiveBatch(consumer, getReceiveTimeout()); + if (messages != null) { + if (logger.isDebugEnabled()) { + logger.debug("Received message of type [" + messages.getClass() + "] from consumer [" + + consumerToUse + "] of " + (transactional ? "transactional " : "") + "session [" + + sessionToUse + "]"); + } + messageReceived(invoker, sessionToUse); + boolean exposeResource = (!transactional && isExposeListenerSession() && + !TransactionSynchronizationManager.hasResource(obtainConnectionFactory())); + if (exposeResource) { + TransactionSynchronizationManager.bindResource( + obtainConnectionFactory(), new LocallyExposedJmsResourceHolder(sessionToUse)); + } + try { + doExecuteListener(sessionToUse, messages); + } + catch (Throwable ex) { + if (status != null) { + if (logger.isDebugEnabled()) { + logger.debug("Rolling back transaction because of listener exception thrown: " + ex); + } + status.setRollbackOnly(); + } + handleListenerException(ex); + // Rethrow JMSException to indicate an infrastructure problem + // that may have to trigger recovery... + if (ex instanceof JMSException jmsException) { + throw jmsException; + } + } + finally { + if (exposeResource) { + TransactionSynchronizationManager.unbindResource(obtainConnectionFactory()); + } + } + // Indicate that a message has been received. + return true; + } + else { + if (logger.isTraceEnabled()) { + logger.trace("Consumer [" + consumerToUse + "] of " + (transactional ? "transactional " : "") + + "session [" + sessionToUse + "] did not receive a message"); + } + noMessageReceived(invoker, sessionToUse); + // Nevertheless call commit, in order to reset the transaction timeout (if any). + if (shouldCommitAfterNoMessageReceived(sessionToUse)) { + super.commitIfNecessary(sessionToUse, null); + } + // Indicate that no message has been received. + return false; + } + } + finally { + JmsUtils.closeMessageConsumer(consumerToClose); + JmsUtils.closeSession(sessionToClose); + ConnectionFactoryUtils.releaseConnection(conToClose, getConnectionFactory(), true); + } + } + + protected void doExecuteListener(Session session, List messages) throws JMSException { + if (!isAcceptMessagesWhileStopping() && !isRunning()) { + if (logger.isWarnEnabled()) { + logger.warn("Rejecting received messages because of the listener container " + + "having been stopped in the meantime: " + messages); + } + rollbackIfNecessary(session); + throw new MessageRejectedWhileStoppingException(); + } + + try { + invokeListener(session, messages); + } + catch (JMSException | RuntimeException | Error ex) { + rollbackOnExceptionIfNecessary(session, ex); + throw ex; + } + commitIfNecessary(session, messages); + } + + protected void commitIfNecessary(Session session, @Nullable List messages) throws JMSException { + // Commit session or acknowledge message. + if (session.getTransacted()) { + // Commit necessary - but avoid commit call within a JTA transaction. + if (isSessionLocallyTransacted(session)) { + // Transacted session created by this container -> commit. + JmsUtils.commitIfNecessary(session); + } + } + else if (messages != null && isClientAcknowledge(session)) { + // acknowledge last message of the group only + messages.get(messages.size()-1).acknowledge(); + } + } + + @Override + protected boolean isSessionLocallyTransacted(Session session) { + if (!isSessionTransacted()) { + return false; + } + JmsResourceHolder resourceHolder = + (JmsResourceHolder) TransactionSynchronizationManager.getResource(obtainConnectionFactory()); + return (resourceHolder == null || resourceHolder instanceof LocallyExposedJmsResourceHolder || + !resourceHolder.containsSession(session)); + } + + protected void invokeListener(Session session, List messages) throws JMSException { + Object listener = getMessageListener(); + + if (listener instanceof TEQBatchMessageListener teqBatchListener) { + this.doInvokeListener((TEQBatchMessageListener)teqBatchListener, session, messages); + } + else if (listener != null) { + throw new IllegalArgumentException( + "Only TEQBatchMessageListener supported: " + listener); + } + else { + throw new IllegalStateException("No message listener specified - see property 'messageListener'"); + } + } + + protected void doInvokeListener(TEQBatchMessageListener listener, Session session, List messages) + throws JMSException { + + Connection conToClose = null; + Session sessionToClose = null; + try { + Session sessionToUse = session; + if (!isExposeListenerSession()) { + // We need to expose a separate Session. + conToClose = createConnection(); + sessionToClose = createSession(conToClose); + sessionToUse = sessionToClose; + } + // Actually invoke the message listener... + listener.setHeaderMapper(new SpecCompliantJmsHeaderMapper()); + listener.onMessage(messages, sessionToUse); + // Clean up specially exposed Session, if any. + if (sessionToUse != session) { + if (sessionToUse.getTransacted() && isSessionLocallyTransacted(sessionToUse)) { + // Transacted session created by this container -> commit. + JmsUtils.commitIfNecessary(sessionToUse); + } + } + } + finally { + JmsUtils.closeSession(sessionToClose); + JmsUtils.closeConnection(conToClose); + } + } + + /** + * ResourceFactory implementation that delegates to this listener container's protected callback methods. + */ + private class MessageListenerContainerResourceFactory implements ConnectionFactoryUtils.ResourceFactory { + + @Override + @Nullable + public Connection getConnection(JmsResourceHolder holder) { + return TEQBatchMessageListenerContainer.this.getConnection(holder); + } + + @Override + @Nullable + public Session getSession(JmsResourceHolder holder) { + return TEQBatchMessageListenerContainer.this.getSession(holder); + } + + @Override + public Connection createConnection() throws JMSException { + if (TEQBatchMessageListenerContainer.this.sharedConnectionEnabled()) { + Connection sharedCon = TEQBatchMessageListenerContainer.this.getSharedConnection(); + return new SingleConnectionFactory(sharedCon).createConnection(); + } + else { + return TEQBatchMessageListenerContainer.this.createConnection(); + } + } + + @Override + public Session createSession(Connection con) throws JMSException { + return TEQBatchMessageListenerContainer.this.createSession(con); + } + + @Override + public boolean isSynchedLocalTransactionAllowed() { + return TEQBatchMessageListenerContainer.this.isSessionTransacted(); + } + } + + @SuppressWarnings("serial") + private static class MessageRejectedWhileStoppingException extends RuntimeException { + } } class LocallyExposedJmsResourceHolder extends JmsResourceHolder { - - public LocallyExposedJmsResourceHolder(Session session) { - super(session); - } + + public LocallyExposedJmsResourceHolder(Session session) { + super(session); + } } diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/TEQMessageListenerContainer.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/TEQMessageListenerContainer.java index 025c4170..ce4d9782 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/TEQMessageListenerContainer.java +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/TEQMessageListenerContainer.java @@ -1,9 +1,9 @@ /* - ** TxEventQ Support for Spring Cloud Stream - ** Copyright (c) 2023, 2024 Oracle and/or its affiliates. - ** - ** This file has been modified by Oracle Corporation. - */ +** TxEventQ Support for Spring Cloud Stream +** Copyright (c) 2023, 2024 Oracle and/or its affiliates. +** +** This file has been modified by Oracle Corporation. +*/ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -33,38 +33,37 @@ import jakarta.jms.Session; public class TEQMessageListenerContainer extends DefaultMessageListenerContainer { - - /** - * Instance variable to consume from a specific partition - */ - private int partition = -1; - - /** - * Getters and setters for partition - */ - public int getPartition() { - return this.partition; - } - - public void setPartition(int partition) { - this.partition = partition; - } - - /** - * Create a JMS MessageConsumer for the given Session and Destination. - *

This implementation uses JMS 1.1 API. - * Also sets the corresponding partition on AQjmsConsumer - * - * @param session the JMS Session to create a MessageConsumer for - * @param destination the JMS Destination to create a MessageConsumer for - * @return the new JMS MessageConsumer - * @throws jakarta.jms.JMSException if thrown by JMS API methods - */ - @Override - protected MessageConsumer createConsumer(Session session, Destination destination) throws JMSException { - MessageConsumer consumer = super.createConsumer(session, destination); - if (this.partition != -1) - ((AQjmsConsumer) consumer).setPartition(this.partition); - return consumer; - } + + /** + * Instance variable to consume from a specific partition + */ + private int partition = -1; + + /** + * Getters and setters for partition + */ + public int getPartition() { + return this.partition; + } + + public void setPartition(int partition) { + this.partition = partition; + } + + /** + * Create a JMS MessageConsumer for the given Session and Destination. + *

This implementation uses JMS 1.1 API. + * Also sets the corresponding partition on AQjmsConsumer + * @param session the JMS Session to create a MessageConsumer for + * @param destination the JMS Destination to create a MessageConsumer for + * @return the new JMS MessageConsumer + * @throws jakarta.jms.JMSException if thrown by JMS API methods + */ + @Override + protected MessageConsumer createConsumer(Session session, Destination destination) throws JMSException { + MessageConsumer consumer = super.createConsumer(session, destination); + if(this.partition != -1) + ((AQjmsConsumer)consumer).setPartition(this.partition); + return consumer; + } } diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/TxEventQBinderHeaderConstants.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/TxEventQBinderHeaderConstants.java new file mode 100644 index 00000000..09a4ded7 --- /dev/null +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/TxEventQBinderHeaderConstants.java @@ -0,0 +1,9 @@ +package com.oracle.cstream.utils; + +public class TxEventQBinderHeaderConstants { + public static final String MESSAGE_CONNECTION = "oracle.jdbc.internal.connection"; + public static final String CONNECTION_CONSUMER = "oracle.jdbc.internal.callback"; + public static final String MESSAGE_CONTEXT = "oracle.jdbc.internal.message_context"; + + private TxEventQBinderHeaderConstants() {} +} diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/TxEventQMessageBuilder.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/TxEventQMessageBuilder.java new file mode 100644 index 00000000..5a13f84c --- /dev/null +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/TxEventQMessageBuilder.java @@ -0,0 +1,257 @@ +package com.oracle.cstream.utils; + +import java.sql.Connection; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.integration.IntegrationMessageHeaderAccessor; +import org.springframework.integration.support.AbstractIntegrationMessageBuilder; +import org.springframework.lang.Nullable; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.MessageHeaders; +import org.springframework.messaging.support.ErrorMessage; +import org.springframework.messaging.support.GenericMessage; +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; + +public final class TxEventQMessageBuilder extends AbstractIntegrationMessageBuilder { + + private static final Log LOGGER = LogFactory.getLog(TxEventQMessageBuilder.class); + + private final T payload; + + private final IntegrationMessageHeaderAccessor headerAccessor; + + @Nullable + private final Message originalMessage; + + private volatile boolean modified; + + private String[] readOnlyHeaders; + + private TxEventQMessageBuilder(T payload, @Nullable Message originalMessage) { + Assert.notNull(payload, "payload must not be null"); + this.payload = payload; + this.originalMessage = originalMessage; + this.headerAccessor = new IntegrationMessageHeaderAccessor(originalMessage); + if (originalMessage != null) { + this.modified = (!this.payload.equals(originalMessage.getPayload())); + } + } + + @Override + public T getPayload() { + return this.payload; + } + + @Override + public Map getHeaders() { + return this.headerAccessor.toMap(); + } + + @Nullable + @Override + public V getHeader(String key, Class type) { + return this.headerAccessor.getHeader(key, type); + } + + public static TxEventQMessageBuilder fromMessage(Message message) { + Assert.notNull(message, "message must not be null"); + return new TxEventQMessageBuilder<>(message.getPayload(), message); + } + + public static TxEventQMessageBuilder withPayload(T payload) { + return new TxEventQMessageBuilder<>(payload, null); + } + + @Override + public TxEventQMessageBuilder setHeader(String headerName, @Nullable Object headerValue) { + this.headerAccessor.setHeader(headerName, headerValue); + return this; + } + + @Override + public TxEventQMessageBuilder setHeaderIfAbsent(String headerName, Object headerValue) { + this.headerAccessor.setHeaderIfAbsent(headerName, headerValue); + return this; + } + + @Override + public TxEventQMessageBuilder removeHeaders(String... headerPatterns) { + this.headerAccessor.removeHeaders(headerPatterns); + return this; + } + + @Override + public TxEventQMessageBuilder removeHeader(String headerName) { + if (!this.headerAccessor.isReadOnly(headerName)) { + this.headerAccessor.removeHeader(headerName); + } + else if (LOGGER.isInfoEnabled()) { + LOGGER.info("The header [" + headerName + "] is ignored for removal because it is is readOnly."); + } + return this; + } + + @Override + public TxEventQMessageBuilder copyHeaders(@Nullable Map headersToCopy) { + this.headerAccessor.copyHeaders(headersToCopy); + return this; + } + + @Override + public TxEventQMessageBuilder copyHeadersIfAbsent(@Nullable Map headersToCopy) { + if (headersToCopy != null) { + for (Map.Entry entry : headersToCopy.entrySet()) { + String headerName = entry.getKey(); + if (!this.headerAccessor.isReadOnly(headerName)) { + this.headerAccessor.setHeaderIfAbsent(headerName, entry.getValue()); + } + } + } + return this; + } + + @SuppressWarnings("unchecked") + @Override + @Nullable + protected List> getSequenceDetails() { + return (List>) this.headerAccessor.getHeader(IntegrationMessageHeaderAccessor.SEQUENCE_DETAILS); + } + + @Override + @Nullable + protected Object getCorrelationId() { + return this.headerAccessor.getCorrelationId(); + } + + @Override + protected Object getSequenceNumber() { + return this.headerAccessor.getSequenceNumber(); + } + + @Override + protected Object getSequenceSize() { + return this.headerAccessor.getSequenceSize(); + } + + @Override + public TxEventQMessageBuilder pushSequenceDetails(Object correlationId, int sequenceNumber, int sequenceSize) { + super.pushSequenceDetails(correlationId, sequenceNumber, sequenceSize); + return this; + } + + @Override + public TxEventQMessageBuilder popSequenceDetails() { + super.popSequenceDetails(); + return this; + } + + @Override + public TxEventQMessageBuilder setExpirationDate(@Nullable Long expirationDate) { + super.setExpirationDate(expirationDate); + return this; + } + + @Override + public TxEventQMessageBuilder setExpirationDate(@Nullable Date expirationDate) { + super.setExpirationDate(expirationDate); + return this; + } + + @Override + public TxEventQMessageBuilder setCorrelationId(Object correlationId) { + super.setCorrelationId(correlationId); + return this; + } + + @Override + public TxEventQMessageBuilder setReplyChannel(MessageChannel replyChannel) { + super.setReplyChannel(replyChannel); + return this; + } + + @Override + public TxEventQMessageBuilder setReplyChannelName(String replyChannelName) { + super.setReplyChannelName(replyChannelName); + return this; + } + + @Override + public TxEventQMessageBuilder setErrorChannel(MessageChannel errorChannel) { + super.setErrorChannel(errorChannel); + return this; + } + + @Override + public TxEventQMessageBuilder setErrorChannelName(String errorChannelName) { + super.setErrorChannelName(errorChannelName); + return this; + } + + @Override + public TxEventQMessageBuilder setSequenceNumber(Integer sequenceNumber) { + super.setSequenceNumber(sequenceNumber); + return this; + } + + @Override + public TxEventQMessageBuilder setSequenceSize(Integer sequenceSize) { + super.setSequenceSize(sequenceSize); + return this; + } + + @Override + public TxEventQMessageBuilder setPriority(Integer priority) { + super.setPriority(priority); + return this; + } + + public TxEventQMessageBuilder setConnectionCallback(Consumer callback) { + this.setHeader(TxEventQBinderHeaderConstants.CONNECTION_CONSUMER, callback); + return this; + } + + public TxEventQMessageBuilder setConnectionCallbackContext(Consumer callback, Message message) { + this.setHeader(TxEventQBinderHeaderConstants.CONNECTION_CONSUMER, callback); + this.setHeader(TxEventQBinderHeaderConstants.MESSAGE_CONTEXT, TxEventQUtils.getDBConnection(message)); + return this; + } + + public TxEventQMessageBuilder readOnlyHeaders(String... readOnlyHeaders) { + this.readOnlyHeaders = readOnlyHeaders != null ? Arrays.copyOf(readOnlyHeaders, readOnlyHeaders.length) : null; + this.headerAccessor.setReadOnlyHeaders(readOnlyHeaders); + return this; + } + + @Override + @SuppressWarnings("unchecked") + public Message build() { + if (!this.modified && !this.headerAccessor.isModified() && this.originalMessage != null + && !containsReadOnly(this.originalMessage.getHeaders())) { + return this.originalMessage; + } + if (this.payload instanceof Throwable) { + return (Message) new ErrorMessage((Throwable) this.payload, this.headerAccessor.toMap()); + } + return new GenericMessage<>(this.payload, this.headerAccessor.toMap()); + } + + private boolean containsReadOnly(MessageHeaders headers) { + if (!ObjectUtils.isEmpty(this.readOnlyHeaders)) { + for (String readOnly : this.readOnlyHeaders) { + if (headers.containsKey(readOnly)) { + return true; + } + } + } + return false; + } + +} diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/TxEventQUtils.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/TxEventQUtils.java new file mode 100644 index 00000000..08973bd0 --- /dev/null +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/cstream/utils/TxEventQUtils.java @@ -0,0 +1,34 @@ +package com.oracle.cstream.utils; + +import java.sql.Connection; +import java.util.function.Consumer; + +import org.springframework.messaging.Message; + +public class TxEventQUtils { + /* Private constructor to avoid creating object of class TxEventQUtils */ + private TxEventQUtils() { } + + /* Static Utility Methods */ + public static Connection getDBConnection(Message message) { + return (Connection) message + .getHeaders() + .getOrDefault(TxEventQBinderHeaderConstants.MESSAGE_CONNECTION, null); + } + + public static Message setConnectionCallbackContext(Message message, + Consumer callback, + Message oldMessage) { + return TxEventQMessageBuilder + .fromMessage(message) + .setConnectionCallbackContext(callback, oldMessage) + .build(); + } + + public static Message setConnectionCallback(Message message, Consumer callback) { + return TxEventQMessageBuilder + .fromMessage(message) + .setConnectionCallback(callback) + .build(); + } +} diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/resources/META-INF/spring.binders b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/resources/META-INF/spring.binders index 2f38604e..406fb60a 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/resources/META-INF/spring.binders +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/resources/META-INF/spring.binders @@ -1,2 +1,2 @@ -txeventqjms=\ +txeventqjms:\ com.oracle.cstream.config.TxEventQJmsConfiguration \ No newline at end of file From 48d5652b5311464a3071a962a66e52479dab3414 Mon Sep 17 00:00:00 2001 From: Anders Swanson Date: Thu, 20 Mar 2025 11:59:15 -0700 Subject: [PATCH 2/4] 23.7 Signed-off-by: Anders Swanson --- .../cloud/stream/binder/sample/TxEventQSampleAppTest.java | 2 +- .../com/oracle/database/spring/cloud/stream/binder/Util.java | 2 +- .../src/test/java/com/oracle/spring/json/JsonCollectionsIT.java | 2 +- .../spring/jsonduality/JSONDualitySampleApplicationTest.java | 2 +- .../oracle/database/spring/jsonevents/JSONEventsSampleTest.java | 2 +- .../com/oracle/database/spring/okafka/OKafkaSampleTest.java | 2 +- .../oracle/database/spring/sample/UCPSampleApplicationTest.java | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/spring-cloud-stream-binder-txeventq-sample/src/test/java/com/oracle/database/spring/cloud/stream/binder/sample/TxEventQSampleAppTest.java b/database/spring-cloud-stream-binder-oracle-txeventq/spring-cloud-stream-binder-txeventq-sample/src/test/java/com/oracle/database/spring/cloud/stream/binder/sample/TxEventQSampleAppTest.java index b87a9f13..9c736969 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/spring-cloud-stream-binder-txeventq-sample/src/test/java/com/oracle/database/spring/cloud/stream/binder/sample/TxEventQSampleAppTest.java +++ b/database/spring-cloud-stream-binder-oracle-txeventq/spring-cloud-stream-binder-txeventq-sample/src/test/java/com/oracle/database/spring/cloud/stream/binder/sample/TxEventQSampleAppTest.java @@ -20,7 +20,7 @@ @Testcontainers public class TxEventQSampleAppTest { @Container - static OracleContainer oracleContainer = new OracleContainer("gvenzl/oracle-free:23.6-slim-faststart") + static OracleContainer oracleContainer = new OracleContainer("gvenzl/oracle-free:23.7-slim-faststart") .withStartupTimeout(Duration.ofMinutes(2)) .withUsername("testuser") .withPassword("testpwd"); diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/test/java/com/oracle/database/spring/cloud/stream/binder/Util.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/test/java/com/oracle/database/spring/cloud/stream/binder/Util.java index 1100b437..1b6b2c7e 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/test/java/com/oracle/database/spring/cloud/stream/binder/Util.java +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/test/java/com/oracle/database/spring/cloud/stream/binder/Util.java @@ -11,7 +11,7 @@ public class Util { public static OracleContainer oracleContainer() { - return new OracleContainer("gvenzl/oracle-free:23.6-slim-faststart") + return new OracleContainer("gvenzl/oracle-free:23.7-slim-faststart") .withStartupTimeout(Duration.ofMinutes(2)) // Needed for M1 Mac .withUsername("testuser") .withPassword("testpwd"); diff --git a/database/starters/oracle-spring-boot-starter-json-collections/src/test/java/com/oracle/spring/json/JsonCollectionsIT.java b/database/starters/oracle-spring-boot-starter-json-collections/src/test/java/com/oracle/spring/json/JsonCollectionsIT.java index b085973d..6afacdbc 100644 --- a/database/starters/oracle-spring-boot-starter-json-collections/src/test/java/com/oracle/spring/json/JsonCollectionsIT.java +++ b/database/starters/oracle-spring-boot-starter-json-collections/src/test/java/com/oracle/spring/json/JsonCollectionsIT.java @@ -29,7 +29,7 @@ @SpringBootTest(classes = JsonCollectionsAutoConfiguration.class) @Testcontainers public class JsonCollectionsIT { - static OracleContainer oracleContainer = new OracleContainer("gvenzl/oracle-free:23.6-slim-faststart") + static OracleContainer oracleContainer = new OracleContainer("gvenzl/oracle-free:23.7-slim-faststart") .withStartupTimeout(Duration.ofMinutes(2)) .withUsername("testuser") .withPassword("testpwd"); diff --git a/database/starters/oracle-spring-boot-starter-samples/oracle-spring-boot-sample-json-duality/src/test/java/com/oracle/database/spring/jsonduality/JSONDualitySampleApplicationTest.java b/database/starters/oracle-spring-boot-starter-samples/oracle-spring-boot-sample-json-duality/src/test/java/com/oracle/database/spring/jsonduality/JSONDualitySampleApplicationTest.java index 0ea05903..0f266fe3 100644 --- a/database/starters/oracle-spring-boot-starter-samples/oracle-spring-boot-sample-json-duality/src/test/java/com/oracle/database/spring/jsonduality/JSONDualitySampleApplicationTest.java +++ b/database/starters/oracle-spring-boot-starter-samples/oracle-spring-boot-sample-json-duality/src/test/java/com/oracle/database/spring/jsonduality/JSONDualitySampleApplicationTest.java @@ -27,7 +27,7 @@ public class JSONDualitySampleApplicationTest { * The Testcontainers Oracle Free module let's us create an Oracle database container in a junit context. */ @Container - static OracleContainer oracleContainer = new OracleContainer("gvenzl/oracle-free:23.6-slim-faststart") + static OracleContainer oracleContainer = new OracleContainer("gvenzl/oracle-free:23.7-slim-faststart") .withStartupTimeout(Duration.ofMinutes(2)) .withUsername("testuser") .withPassword("testpwd"); diff --git a/database/starters/oracle-spring-boot-starter-samples/oracle-spring-boot-sample-json-events/src/test/java/com/oracle/database/spring/jsonevents/JSONEventsSampleTest.java b/database/starters/oracle-spring-boot-starter-samples/oracle-spring-boot-sample-json-events/src/test/java/com/oracle/database/spring/jsonevents/JSONEventsSampleTest.java index 6124f7f6..1806e93b 100644 --- a/database/starters/oracle-spring-boot-starter-samples/oracle-spring-boot-sample-json-events/src/test/java/com/oracle/database/spring/jsonevents/JSONEventsSampleTest.java +++ b/database/starters/oracle-spring-boot-starter-samples/oracle-spring-boot-sample-json-events/src/test/java/com/oracle/database/spring/jsonevents/JSONEventsSampleTest.java @@ -33,7 +33,7 @@ public class JSONEventsSampleTest { * The Testcontainers Oracle Free module let's us create an Oracle database container in a junit context. */ @Container - static OracleContainer oracleContainer = new OracleContainer("gvenzl/oracle-free:23.6-slim-faststart") + static OracleContainer oracleContainer = new OracleContainer("gvenzl/oracle-free:23.7-slim-faststart") .withStartupTimeout(Duration.ofMinutes(2)) .withUsername("testuser") .withPassword("testpwd"); diff --git a/database/starters/oracle-spring-boot-starter-samples/oracle-spring-boot-sample-okafka/src/test/java/com/oracle/database/spring/okafka/OKafkaSampleTest.java b/database/starters/oracle-spring-boot-starter-samples/oracle-spring-boot-sample-okafka/src/test/java/com/oracle/database/spring/okafka/OKafkaSampleTest.java index 3667c0e1..73bdcc52 100644 --- a/database/starters/oracle-spring-boot-starter-samples/oracle-spring-boot-sample-okafka/src/test/java/com/oracle/database/spring/okafka/OKafkaSampleTest.java +++ b/database/starters/oracle-spring-boot-starter-samples/oracle-spring-boot-sample-okafka/src/test/java/com/oracle/database/spring/okafka/OKafkaSampleTest.java @@ -30,7 +30,7 @@ @Testcontainers public class OKafkaSampleTest { // Oracle Database 23ai Free container image - private static final String oracleImage = "gvenzl/oracle-free:23.6-slim-faststart"; + private static final String oracleImage = "gvenzl/oracle-free:23.7-slim-faststart"; private static final String testUser = "testuser"; private static final String testPassword = "Welcome123#"; diff --git a/database/starters/oracle-spring-boot-starter-samples/oracle-spring-boot-sample-ucp-jpa/src/test/java/com/oracle/database/spring/sample/UCPSampleApplicationTest.java b/database/starters/oracle-spring-boot-starter-samples/oracle-spring-boot-sample-ucp-jpa/src/test/java/com/oracle/database/spring/sample/UCPSampleApplicationTest.java index 6ba9d874..b19a9609 100644 --- a/database/starters/oracle-spring-boot-starter-samples/oracle-spring-boot-sample-ucp-jpa/src/test/java/com/oracle/database/spring/sample/UCPSampleApplicationTest.java +++ b/database/starters/oracle-spring-boot-starter-samples/oracle-spring-boot-sample-ucp-jpa/src/test/java/com/oracle/database/spring/sample/UCPSampleApplicationTest.java @@ -28,7 +28,7 @@ public class UCPSampleApplicationTest { * The Testcontainers Oracle Free module let's us create an Oracle database container in a junit context. */ @Container - static OracleContainer oracleContainer = new OracleContainer("gvenzl/oracle-free:23.6-slim-faststart") + static OracleContainer oracleContainer = new OracleContainer("gvenzl/oracle-free:23.7-slim-faststart") .withStartupTimeout(Duration.ofMinutes(2)) .withUsername("testuser") .withPassword("testpwd"); From 61d798834193c5d96990027b3ec3bebf65e3f915 Mon Sep 17 00:00:00 2001 From: Anders Swanson Date: Thu, 20 Mar 2025 12:01:30 -0700 Subject: [PATCH 3/4] Reformat code Signed-off-by: Anders Swanson --- .../binder/JMSMessageChannelBinder.java | 281 ++++---- .../binder/TxEventQQueueProvisioner.java | 454 ++++++------ .../config/JmsBinderAutoConfiguration.java | 14 +- .../config/JmsBinderGlobalConfiguration.java | 171 +++-- .../binder/config/JmsBindingProperties.java | 38 +- .../binder/config/JmsConsumerProperties.java | 80 +-- .../config/JmsExtendedBindingProperties.java | 43 +- .../binder/config/JmsProducerProperties.java | 28 +- .../config/TxEventQJmsConfiguration.java | 86 ++- .../stream/binder/plsql/OracleDBUtils.java | 200 +++--- .../provisioning/JmsConsumerDestination.java | 54 +- .../provisioning/JmsProducerDestination.java | 86 +-- .../CustomSerializationMessageConverter.java | 117 ++-- .../stream/binder/serialize/Deserializer.java | 12 +- .../stream/binder/serialize/Serializer.java | 12 +- .../binder/utils/AnonymousNamingStrategy.java | 14 +- .../binder/utils/Base64UrlNamingStrategy.java | 65 +- .../binder/utils/DestinationNameResolver.java | 26 +- .../utils/JmsMessageDrivenChannelAdapter.java | 204 +++--- ...JmsMessageDrivenChannelAdapterFactory.java | 330 +++++---- .../JmsSendingMessageHandlerFactory.java | 85 ++- .../utils/ListenerContainerFactory.java | 85 ++- .../stream/binder/utils/MessageRecoverer.java | 24 +- ...artitionAwareJmsSendingMessageHandler.java | 456 ++++++------- .../utils/RepublishMessageRecoverer.java | 173 ++--- .../utils/SpecCompliantJmsHeaderMapper.java | 137 ++-- .../binder/utils/TEQBatchMessageListener.java | 563 ++++++++------- .../TEQBatchMessageListenerContainer.java | 646 +++++++++--------- .../utils/TEQMessageListenerContainer.java | 82 +-- .../utils/TxEventQBinderHeaderConstants.java | 9 +- .../binder/utils/TxEventQMessageBuilder.java | 461 +++++++------ .../stream/binder/utils/TxEventQUtils.java | 45 +- 32 files changed, 2525 insertions(+), 2556 deletions(-) diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/JMSMessageChannelBinder.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/JMSMessageChannelBinder.java index 09b2b667..266da17c 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/JMSMessageChannelBinder.java +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/JMSMessageChannelBinder.java @@ -1,10 +1,10 @@ /* -** TxEventQ Support for Spring Cloud Stream -** Copyright (c) 2023, 2024 Oracle and/or its affiliates. -** -** This file has been modified by Oracle Corporation. -** -*/ + ** TxEventQ Support for Spring Cloud Stream + ** Copyright (c) 2023, 2024 Oracle and/or its affiliates. + ** + ** This file has been modified by Oracle Corporation. + ** + */ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -30,8 +30,6 @@ import com.oracle.database.spring.cloud.stream.binder.config.JmsProducerProperties; import com.oracle.database.spring.cloud.stream.binder.provisioning.JmsConsumerDestination; import com.oracle.database.spring.cloud.stream.binder.provisioning.JmsProducerDestination; - - import com.oracle.database.spring.cloud.stream.binder.utils.DestinationNameResolver; import com.oracle.database.spring.cloud.stream.binder.utils.JmsMessageDrivenChannelAdapterFactory; import com.oracle.database.spring.cloud.stream.binder.utils.JmsSendingMessageHandlerFactory; @@ -39,7 +37,6 @@ import jakarta.jms.ConnectionFactory; import jakarta.jms.Session; import jakarta.jms.Topic; - import org.springframework.cloud.stream.binder.AbstractMessageChannelBinder; import org.springframework.cloud.stream.binder.BinderSpecificPropertiesProvider; import org.springframework.cloud.stream.binder.ExtendedConsumerProperties; @@ -58,138 +55,138 @@ public class JMSMessageChannelBinder - extends AbstractMessageChannelBinder, ExtendedProducerProperties, ProvisioningProvider, ExtendedProducerProperties>> - implements - ExtendedPropertiesBinder { - - private JmsExtendedBindingProperties extendedBindingProperties = new JmsExtendedBindingProperties(); - - private final JmsSendingMessageHandlerFactory jmsSendingMessageHandlerFactory; - private final JmsMessageDrivenChannelAdapterFactory jmsMessageDrivenChannelAdapterFactory; - private final ConnectionFactory connectionFactory; - - private final DestinationResolver destinationResolver; - - private DestinationNameResolver destinationNameResolver; - - public JMSMessageChannelBinder( - ProvisioningProvider, ExtendedProducerProperties> provisioningProvider, - JmsSendingMessageHandlerFactory jmsSendingMessageHandlerFactory, - JmsMessageDrivenChannelAdapterFactory jmsMessageDrivenChannelAdapterFactory, - JmsTemplate jmsTemplate, - ConnectionFactory connectionFactory - ) { - super(null, provisioningProvider); - this.jmsSendingMessageHandlerFactory = jmsSendingMessageHandlerFactory; - this.jmsMessageDrivenChannelAdapterFactory = - jmsMessageDrivenChannelAdapterFactory; - this.connectionFactory = connectionFactory; - this.destinationResolver = jmsTemplate.getDestinationResolver(); - } - - public void setExtendedBindingProperties( - JmsExtendedBindingProperties extendedBindingProperties - ) { - this.extendedBindingProperties = extendedBindingProperties; - } - - public void setDestinationNameResolver(DestinationNameResolver destinationNameResolver) { - this.destinationNameResolver = destinationNameResolver; - } - - @Override - protected MessageHandler createProducerMessageHandler( - ProducerDestination producerDestination, - ExtendedProducerProperties producerProperties, - MessageChannel errorChannel - ) throws Exception { - Topic topic = null; - try(Connection conn = connectionFactory.createConnection()) { - Session session = conn.createSession(true, 1); - - String destination = producerDestination.getName(); - topic = (Topic) destinationResolver.resolveDestinationName( - session, - destination, - true); - } - - if(producerProperties.isUseNativeEncoding()) { - return this.jmsSendingMessageHandlerFactory - .build(topic, errorChannel, - producerProperties.getHeaderMode() == null - || producerProperties.getHeaderMode().equals(HeaderMode.headers), - producerProperties.getExtension().getSerializer(), - ((JmsProducerDestination)producerDestination).getDBVersion()); + extends AbstractMessageChannelBinder, ExtendedProducerProperties, ProvisioningProvider, ExtendedProducerProperties>> + implements + ExtendedPropertiesBinder { + + private JmsExtendedBindingProperties extendedBindingProperties = new JmsExtendedBindingProperties(); + + private final JmsSendingMessageHandlerFactory jmsSendingMessageHandlerFactory; + private final JmsMessageDrivenChannelAdapterFactory jmsMessageDrivenChannelAdapterFactory; + private final ConnectionFactory connectionFactory; + + private final DestinationResolver destinationResolver; + + private DestinationNameResolver destinationNameResolver; + + public JMSMessageChannelBinder( + ProvisioningProvider, ExtendedProducerProperties> provisioningProvider, + JmsSendingMessageHandlerFactory jmsSendingMessageHandlerFactory, + JmsMessageDrivenChannelAdapterFactory jmsMessageDrivenChannelAdapterFactory, + JmsTemplate jmsTemplate, + ConnectionFactory connectionFactory + ) { + super(null, provisioningProvider); + this.jmsSendingMessageHandlerFactory = jmsSendingMessageHandlerFactory; + this.jmsMessageDrivenChannelAdapterFactory = + jmsMessageDrivenChannelAdapterFactory; + this.connectionFactory = connectionFactory; + this.destinationResolver = jmsTemplate.getDestinationResolver(); + } + + public void setExtendedBindingProperties( + JmsExtendedBindingProperties extendedBindingProperties + ) { + this.extendedBindingProperties = extendedBindingProperties; + } + + public void setDestinationNameResolver(DestinationNameResolver destinationNameResolver) { + this.destinationNameResolver = destinationNameResolver; + } + + @Override + protected MessageHandler createProducerMessageHandler( + ProducerDestination producerDestination, + ExtendedProducerProperties producerProperties, + MessageChannel errorChannel + ) throws Exception { + Topic topic = null; + try (Connection conn = connectionFactory.createConnection()) { + Session session = conn.createSession(true, 1); + + String destination = producerDestination.getName(); + topic = (Topic) destinationResolver.resolveDestinationName( + session, + destination, + true); + } + + if (producerProperties.isUseNativeEncoding()) { + return this.jmsSendingMessageHandlerFactory + .build(topic, errorChannel, + producerProperties.getHeaderMode() == null + || producerProperties.getHeaderMode().equals(HeaderMode.headers), + producerProperties.getExtension().getSerializer(), + ((JmsProducerDestination) producerDestination).getDBVersion()); + } + + return this.jmsSendingMessageHandlerFactory + .build(topic, errorChannel, + producerProperties.getHeaderMode() == null || + producerProperties.getHeaderMode().equals(HeaderMode.headers), + null, + ((JmsProducerDestination) producerDestination).getDBVersion()); + } + + @Override + protected org.springframework.integration.core.MessageProducer createConsumerEndpoint( + ConsumerDestination consumerDestination, + String group, + ExtendedConsumerProperties properties + ) throws Exception { + group = this.destinationNameResolver.resolveGroupName(group); + Topic topic = null; + try (Connection conn = connectionFactory.createConnection()) { + Session session = conn.createSession(true, 1); + + topic = (Topic) destinationResolver.resolveDestinationName( + session, + consumerDestination.getName(), + true); + } + + + RetryTemplate retryTemplate = buildRetryTemplate(properties); + ErrorInfrastructure errorInfrastructure = registerErrorInfrastructure(consumerDestination, group, properties); + RecoveryCallback recoveryCallback = errorInfrastructure + .getRecoverer(); + + return jmsMessageDrivenChannelAdapterFactory.build( + topic, + group, + retryTemplate, + recoveryCallback, + errorInfrastructure.getErrorChannel(), + properties, + ((JmsConsumerDestination) consumerDestination).getDBVersion() + ); + } + + @Override + public JmsConsumerProperties getExtendedConsumerProperties( + String channelName + ) { + return this.extendedBindingProperties.getExtendedConsumerProperties( + channelName + ); + } + + @Override + public JmsProducerProperties getExtendedProducerProperties( + String channelName + ) { + return this.extendedBindingProperties.getExtendedProducerProperties( + channelName + ); + } + + @Override + public String getDefaultsPrefix() { + return this.extendedBindingProperties.getDefaultsPrefix(); + } + + @Override + public Class getExtendedPropertiesEntryClass() { + return this.extendedBindingProperties.getExtendedPropertiesEntryClass(); } - - return this.jmsSendingMessageHandlerFactory - .build(topic, errorChannel, - producerProperties.getHeaderMode() == null || - producerProperties.getHeaderMode().equals(HeaderMode.headers), - null, - ((JmsProducerDestination)producerDestination).getDBVersion()); - } - - @Override - protected org.springframework.integration.core.MessageProducer createConsumerEndpoint( - ConsumerDestination consumerDestination, - String group, - ExtendedConsumerProperties properties - ) throws Exception { - group = this.destinationNameResolver.resolveGroupName(group); - Topic topic = null; - try(Connection conn = connectionFactory.createConnection()) { - Session session = conn.createSession(true, 1); - - topic = (Topic) destinationResolver.resolveDestinationName( - session, - consumerDestination.getName(), - true); - } - - - RetryTemplate retryTemplate = buildRetryTemplate(properties); - ErrorInfrastructure errorInfrastructure = registerErrorInfrastructure(consumerDestination, group, properties); - RecoveryCallback recoveryCallback = errorInfrastructure - .getRecoverer(); - - return jmsMessageDrivenChannelAdapterFactory.build( - topic, - group, - retryTemplate, - recoveryCallback, - errorInfrastructure.getErrorChannel(), - properties, - ((JmsConsumerDestination)consumerDestination).getDBVersion() - ); - } - - @Override - public JmsConsumerProperties getExtendedConsumerProperties( - String channelName - ) { - return this.extendedBindingProperties.getExtendedConsumerProperties( - channelName - ); - } - - @Override - public JmsProducerProperties getExtendedProducerProperties( - String channelName - ) { - return this.extendedBindingProperties.getExtendedProducerProperties( - channelName - ); - } - - @Override - public String getDefaultsPrefix() { - return this.extendedBindingProperties.getDefaultsPrefix(); - } - - @Override - public Class getExtendedPropertiesEntryClass() { - return this.extendedBindingProperties.getExtendedPropertiesEntryClass(); - } } diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/TxEventQQueueProvisioner.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/TxEventQQueueProvisioner.java index a4ec6c3f..9ea7b231 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/TxEventQQueueProvisioner.java +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/TxEventQQueueProvisioner.java @@ -1,9 +1,9 @@ /* -** TxEventQ Support for Spring Cloud Stream -** Copyright (c) 2023, 2025 Oracle and/or its affiliates. -** -** This file has been modified by Oracle Corporation. -*/ + ** TxEventQ Support for Spring Cloud Stream + ** Copyright (c) 2023, 2025 Oracle and/or its affiliates. + ** + ** This file has been modified by Oracle Corporation. + */ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -24,6 +24,8 @@ package com.oracle.database.spring.cloud.stream.binder; +import java.sql.SQLException; + import com.oracle.database.spring.cloud.stream.binder.config.JmsConsumerProperties; import com.oracle.database.spring.cloud.stream.binder.config.JmsProducerProperties; import com.oracle.database.spring.cloud.stream.binder.plsql.OracleDBUtils; @@ -34,9 +36,6 @@ import jakarta.jms.JMSException; import jakarta.jms.Session; import jakarta.jms.Topic; - -import java.sql.SQLException; - import oracle.jakarta.jms.AQjmsException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -48,225 +47,222 @@ import org.springframework.jms.support.JmsUtils; public class TxEventQQueueProvisioner - implements - ProvisioningProvider, ExtendedProducerProperties> { - private static final int CODE_TOPIC_NOT_FOUND = 243; - - private final ConnectionFactory connectionFactory; - - private final Logger logger = LoggerFactory.getLogger(TxEventQQueueProvisioner.class); - - private OracleDBUtils dbutils; - - public void setDBUtils(OracleDBUtils dbutils) { - this.dbutils = dbutils; - } - - public TxEventQQueueProvisioner( - ConnectionFactory connectionFactory, - OracleDBUtils dbutils - ) { - this.connectionFactory = connectionFactory; - this.dbutils = dbutils; - } - - @Override - public ProducerDestination provisionProducerDestination( - final String name, - ExtendedProducerProperties properties - ) { - // Step 1: Get correct topic name - String topicName = formatName(name); - logger.info("Binding topic {} for {}", topicName, properties.getBindingName()); - - // Step 2: Provision topic for producer binding - // along with required groups (if any) - Topic topic = provisionProducerTopic(topicName, properties); - - // Step 3: Return the required ProducerDestination - return new JmsProducerDestination(topic, properties.getPartitionCount(), this.dbutils.getDBVersion()); - } - - @Override - public ConsumerDestination provisionConsumerDestination( - String name, - String group, - ExtendedConsumerProperties properties - ) { - if(properties.isMultiplex()) { - throw new - IllegalArgumentException("The property 'multiplex:true' is not supported."); - } - if(properties.getInstanceIndexList() != null && - !properties.getInstanceIndexList().isEmpty()) { - throw new - IllegalArgumentException("The property 'instanceIndexList' is not supported."); - } - - return provideSingleConsumerDestination(name, group, properties); - } - - /* - * Helper function to bind one consumer destination name - */ - public ConsumerDestination provideSingleConsumerDestination(String name, - String group, - ExtendedConsumerProperties properties) { - String topicName = formatName(name); - logger.info("Binding topic {} for {} for group {}", topicName, properties.getBindingName(), group); - - // Step 2: Provision topic for consumer binding - provisionConsumerTopic(topicName, properties); - - // Step 3: Return the required ConsumerDestination - return new JmsConsumerDestination(topicName, this.dbutils.getDBVersion()); - } - - /* - * Utility function to allocate necessary resources - * for a producer destination with the configured properties - * If the topic does not exist, it creates one with the configured - * partition count. - * By default, if the producer is not partitioned, then - * topic with only one partition is created. - * Throws an error if topic exists and is partitioned - * but the configured partitionCount does not match - * actual partitions on the topic - */ - private Topic provisionProducerTopic(String topicName, - ExtendedProducerProperties properties) { - Connection aQConnection = null; - Session session = null; - Topic topic; - try { - aQConnection = connectionFactory.createConnection(); - session = aQConnection.createSession(true, Session.CLIENT_ACKNOWLEDGE); - - topic = getTopicInstance(topicName, session); - - if(topic == null) { - logger.info("Creating topic: {} as it does not exist.", topicName); - // create Key based TEQ with specified number of partitions - int partitionNum = properties.getPartitionCount(); - createTopicWithPartitions(topicName, partitionNum); - topic = getTopicInstance(topicName, session); - } else if(properties.isPartitioned()){ - // topic exists - // match its partition Count - int partitionNum = properties.getPartitionCount(); - checkPartitionCount(topicName, partitionNum); - } - - // create necessary required subscriptions - for(String requiredGroup: properties.getRequiredGroups()) { - session.createDurableSubscriber(topic, requiredGroup); - } - - JmsUtils.commitIfNecessary(session); - } catch (JMSException e) { - throw new IllegalStateException(e); - } finally { - JmsUtils.closeSession(session); - JmsUtils.closeConnection(aQConnection); - } - - return topic; - } - - private Topic provisionConsumerTopic(String topicName, - ExtendedConsumerProperties properties) { - Connection aQConnection = null; - Session session = null; - Topic topic; - try { - aQConnection = connectionFactory.createConnection(); - session = aQConnection.createSession(true, Session.CLIENT_ACKNOWLEDGE); - - topic = getTopicInstance(topicName, session); - - if(topic == null) { - logger.info("Creating topic: {} as it does not exist.", topicName); - createTopicWithInstances(topicName, properties); - topic = getTopicInstance(topicName, session); - } else if(properties.isPartitioned()){ - // topic exists - // match its instance Count - int instCnt = properties.getInstanceCount(); - checkPartitionCount(topicName, instCnt); - } - - /* Set instance index to -1 if not partitioned */ - if(!properties.isPartitioned()) properties.setInstanceIndex(-1); - - JmsUtils.commitIfNecessary(session); - } catch (JMSException e) { - throw new IllegalStateException(e); - } finally { - JmsUtils.closeSession(session); - JmsUtils.closeConnection(aQConnection); - } - - return topic; - } - - private void createTopicWithPartitions(String topicName, int pCount) { - try { - this.dbutils.createKBQ(topicName, pCount); - } catch (SQLException e) { - throw new IllegalArgumentException("Error when creating procedures & topic.", e); - } - } - - private void createTopicWithInstances(String topicName, - ExtendedConsumerProperties properties) { - try { - if(properties.isPartitioned()) { - // the consumer is partitioned - // create KBQ with configured instance count - int instCnt = properties.getInstanceCount(); - this.dbutils.createKBQ(topicName, instCnt); - } - else { - // The consumer is not partitioned - // create default KBQ with 1 partition - this.dbutils.createKBQ(topicName, 1); - } - } - catch (SQLException e) { - throw new IllegalArgumentException("Error when creating topic.", e); - } - } - - private void checkPartitionCount(String topicName, int pCount) { - try { - int topicParts = this.dbutils.getTopicPartitions(topicName); - if(pCount != topicParts) { - throw new IllegalArgumentException("Partition Count mismatch, Expected: " - + topicParts + ", Found: " + pCount); - } - } catch(SQLException e) { - throw new IllegalArgumentException("Error when checking partitionCount on topics.", e); - } - } - - - - private String formatName(String name) { - // surround with double quotes - // to use exact name for topic - return "\"" + name + "\""; - } - - /* Utility function to check if the topic exists or not - * If the topic does not exist, returns null - * Otherwise returns the actual Topic object - * associated with the name topicName - */ - private Topic getTopicInstance(String topicName, Session session) { - Topic topic = null; - try { - topic = session.createTopic(topicName); - } catch (JMSException e) { + implements + ProvisioningProvider, ExtendedProducerProperties> { + private static final int CODE_TOPIC_NOT_FOUND = 243; + + private final ConnectionFactory connectionFactory; + + private final Logger logger = LoggerFactory.getLogger(TxEventQQueueProvisioner.class); + + private OracleDBUtils dbutils; + + public void setDBUtils(OracleDBUtils dbutils) { + this.dbutils = dbutils; + } + + public TxEventQQueueProvisioner( + ConnectionFactory connectionFactory, + OracleDBUtils dbutils + ) { + this.connectionFactory = connectionFactory; + this.dbutils = dbutils; + } + + @Override + public ProducerDestination provisionProducerDestination( + final String name, + ExtendedProducerProperties properties + ) { + // Step 1: Get correct topic name + String topicName = formatName(name); + logger.info("Binding topic {} for {}", topicName, properties.getBindingName()); + + // Step 2: Provision topic for producer binding + // along with required groups (if any) + Topic topic = provisionProducerTopic(topicName, properties); + + // Step 3: Return the required ProducerDestination + return new JmsProducerDestination(topic, properties.getPartitionCount(), this.dbutils.getDBVersion()); + } + + @Override + public ConsumerDestination provisionConsumerDestination( + String name, + String group, + ExtendedConsumerProperties properties + ) { + if (properties.isMultiplex()) { + throw new + IllegalArgumentException("The property 'multiplex:true' is not supported."); + } + if (properties.getInstanceIndexList() != null && + !properties.getInstanceIndexList().isEmpty()) { + throw new + IllegalArgumentException("The property 'instanceIndexList' is not supported."); + } + + return provideSingleConsumerDestination(name, group, properties); + } + + /* + * Helper function to bind one consumer destination name + */ + public ConsumerDestination provideSingleConsumerDestination(String name, + String group, + ExtendedConsumerProperties properties) { + String topicName = formatName(name); + logger.info("Binding topic {} for {} for group {}", topicName, properties.getBindingName(), group); + + // Step 2: Provision topic for consumer binding + provisionConsumerTopic(topicName, properties); + + // Step 3: Return the required ConsumerDestination + return new JmsConsumerDestination(topicName, this.dbutils.getDBVersion()); + } + + /* + * Utility function to allocate necessary resources + * for a producer destination with the configured properties + * If the topic does not exist, it creates one with the configured + * partition count. + * By default, if the producer is not partitioned, then + * topic with only one partition is created. + * Throws an error if topic exists and is partitioned + * but the configured partitionCount does not match + * actual partitions on the topic + */ + private Topic provisionProducerTopic(String topicName, + ExtendedProducerProperties properties) { + Connection aQConnection = null; + Session session = null; + Topic topic; + try { + aQConnection = connectionFactory.createConnection(); + session = aQConnection.createSession(true, Session.CLIENT_ACKNOWLEDGE); + + topic = getTopicInstance(topicName, session); + + if (topic == null) { + logger.info("Creating topic: {} as it does not exist.", topicName); + // create Key based TEQ with specified number of partitions + int partitionNum = properties.getPartitionCount(); + createTopicWithPartitions(topicName, partitionNum); + topic = getTopicInstance(topicName, session); + } else if (properties.isPartitioned()) { + // topic exists + // match its partition Count + int partitionNum = properties.getPartitionCount(); + checkPartitionCount(topicName, partitionNum); + } + + // create necessary required subscriptions + for (String requiredGroup : properties.getRequiredGroups()) { + session.createDurableSubscriber(topic, requiredGroup); + } + + JmsUtils.commitIfNecessary(session); + } catch (JMSException e) { + throw new IllegalStateException(e); + } finally { + JmsUtils.closeSession(session); + JmsUtils.closeConnection(aQConnection); + } + + return topic; + } + + private Topic provisionConsumerTopic(String topicName, + ExtendedConsumerProperties properties) { + Connection aQConnection = null; + Session session = null; + Topic topic; + try { + aQConnection = connectionFactory.createConnection(); + session = aQConnection.createSession(true, Session.CLIENT_ACKNOWLEDGE); + + topic = getTopicInstance(topicName, session); + + if (topic == null) { + logger.info("Creating topic: {} as it does not exist.", topicName); + createTopicWithInstances(topicName, properties); + topic = getTopicInstance(topicName, session); + } else if (properties.isPartitioned()) { + // topic exists + // match its instance Count + int instCnt = properties.getInstanceCount(); + checkPartitionCount(topicName, instCnt); + } + + /* Set instance index to -1 if not partitioned */ + if (!properties.isPartitioned()) properties.setInstanceIndex(-1); + + JmsUtils.commitIfNecessary(session); + } catch (JMSException e) { + throw new IllegalStateException(e); + } finally { + JmsUtils.closeSession(session); + JmsUtils.closeConnection(aQConnection); + } + + return topic; + } + + private void createTopicWithPartitions(String topicName, int pCount) { + try { + this.dbutils.createKBQ(topicName, pCount); + } catch (SQLException e) { + throw new IllegalArgumentException("Error when creating procedures & topic.", e); + } + } + + private void createTopicWithInstances(String topicName, + ExtendedConsumerProperties properties) { + try { + if (properties.isPartitioned()) { + // the consumer is partitioned + // create KBQ with configured instance count + int instCnt = properties.getInstanceCount(); + this.dbutils.createKBQ(topicName, instCnt); + } else { + // The consumer is not partitioned + // create default KBQ with 1 partition + this.dbutils.createKBQ(topicName, 1); + } + } catch (SQLException e) { + throw new IllegalArgumentException("Error when creating topic.", e); + } + } + + private void checkPartitionCount(String topicName, int pCount) { + try { + int topicParts = this.dbutils.getTopicPartitions(topicName); + if (pCount != topicParts) { + throw new IllegalArgumentException("Partition Count mismatch, Expected: " + + topicParts + ", Found: " + pCount); + } + } catch (SQLException e) { + throw new IllegalArgumentException("Error when checking partitionCount on topics.", e); + } + } + + + private String formatName(String name) { + // surround with double quotes + // to use exact name for topic + return "\"" + name + "\""; + } + + /* Utility function to check if the topic exists or not + * If the topic does not exist, returns null + * Otherwise returns the actual Topic object + * associated with the name topicName + */ + private Topic getTopicInstance(String topicName, Session session) { + Topic topic = null; + try { + topic = session.createTopic(topicName); + } catch (JMSException e) { if (e instanceof AQjmsException aqe && aqe.getErrorNumber() == CODE_TOPIC_NOT_FOUND) { logger.debug(e.getMessage()); } else { @@ -274,7 +270,7 @@ private Topic getTopicInstance(String topicName, Session session) { } return null; } - return topic; - } + return topic; + } } diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/config/JmsBinderAutoConfiguration.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/config/JmsBinderAutoConfiguration.java index 13e03288..129ada18 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/config/JmsBinderAutoConfiguration.java +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/config/JmsBinderAutoConfiguration.java @@ -1,10 +1,10 @@ /* -** TxEventQ Support for Spring Cloud Stream -** Copyright (c) 2023, 2024 Oracle and/or its affiliates. -** -** This file has been modified by Oracle Corporation. -** -*/ + ** TxEventQ Support for Spring Cloud Stream + ** Copyright (c) 2023, 2024 Oracle and/or its affiliates. + ** + ** This file has been modified by Oracle Corporation. + ** + */ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -34,5 +34,5 @@ @ConditionalOnMissingBean(Binder.class) @Import({JmsBinderGlobalConfiguration.class}) public class JmsBinderAutoConfiguration { - + } diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/config/JmsBinderGlobalConfiguration.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/config/JmsBinderGlobalConfiguration.java index 96a4a78b..0a17c62f 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/config/JmsBinderGlobalConfiguration.java +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/config/JmsBinderGlobalConfiguration.java @@ -1,9 +1,9 @@ /* -** TxEventQ Support for Spring Cloud Stream -** Copyright (c) 2023, 2024 Oracle and/or its affiliates. -** -** This file has been modified by Oracle Corporation. -*/ + ** TxEventQ Support for Spring Cloud Stream + ** Copyright (c) 2023, 2024 Oracle and/or its affiliates. + ** + ** This file has been modified by Oracle Corporation. + */ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -46,89 +46,88 @@ @Configuration public class JmsBinderGlobalConfiguration { - private ConnectionFactory connectionFactory; - - public JmsBinderGlobalConfiguration(ConnectionFactory connectionFactory) { - this.connectionFactory = connectionFactory; - } - - @Bean - public DestinationNameResolver queueNameResolver() { - return new DestinationNameResolver( - new Base64UrlNamingStrategy("anonymous_") - ); - } - - @Bean - @ConditionalOnMissingBean(MessageRecoverer.class) - MessageRecoverer defaultMessageRecoverer() { - return new RepublishMessageRecoverer( - jmsTemplate(), - new SpecCompliantJmsHeaderMapper() - ); - } - - @Bean - ListenerContainerFactory listenerContainerFactory() { - return new ListenerContainerFactory(connectionFactory); - } - - @Bean - public JmsMessageDrivenChannelAdapterFactory jmsMessageDrivenChannelAdapterFactory( - MessageRecoverer messageRecoverer, - ListenerContainerFactory listenerContainerFactory - ) { - return new JmsMessageDrivenChannelAdapterFactory( - listenerContainerFactory, - messageRecoverer - ); - } - - @Bean - @ConditionalOnMissingBean(JmsSendingMessageHandlerFactory.class) - public JmsSendingMessageHandlerFactory jmsSendingMessageHandlerFactory() - { - return new JmsSendingMessageHandlerFactory( - jmsTemplate(), - new SpecCompliantJmsHeaderMapper() - ); - } - - @Bean - @ConditionalOnMissingBean(JmsTemplate.class) - public JmsTemplate jmsTemplate() { - return new JmsTemplate(connectionFactory); - } - - @Configuration - @EnableConfigurationProperties(JmsExtendedBindingProperties.class) - public static class JmsBinderConfiguration { + private ConnectionFactory connectionFactory; + + public JmsBinderGlobalConfiguration(ConnectionFactory connectionFactory) { + this.connectionFactory = connectionFactory; + } + + @Bean + public DestinationNameResolver queueNameResolver() { + return new DestinationNameResolver( + new Base64UrlNamingStrategy("anonymous_") + ); + } + + @Bean + @ConditionalOnMissingBean(MessageRecoverer.class) + MessageRecoverer defaultMessageRecoverer() { + return new RepublishMessageRecoverer( + jmsTemplate(), + new SpecCompliantJmsHeaderMapper() + ); + } + + @Bean + ListenerContainerFactory listenerContainerFactory() { + return new ListenerContainerFactory(connectionFactory); + } @Bean - JMSMessageChannelBinder jmsMessageChannelBinder( - JmsMessageDrivenChannelAdapterFactory jmsMessageDrivenChannelAdapterFactory, - JmsSendingMessageHandlerFactory jmsSendingMessageHandlerFactory, - JmsTemplate jmsTemplate, - ProvisioningProvider, ExtendedProducerProperties> provisioningProvider, - ConnectionFactory connectionFactory, - JmsExtendedBindingProperties jmsExtendedBindingProperties, - DestinationNameResolver destinationNameResolver + public JmsMessageDrivenChannelAdapterFactory jmsMessageDrivenChannelAdapterFactory( + MessageRecoverer messageRecoverer, + ListenerContainerFactory listenerContainerFactory ) { - JMSMessageChannelBinder jmsMessageChannelBinder = new JMSMessageChannelBinder( - provisioningProvider, - jmsSendingMessageHandlerFactory, - jmsMessageDrivenChannelAdapterFactory, - jmsTemplate, - connectionFactory - ); - - jmsMessageChannelBinder.setExtendedBindingProperties( - jmsExtendedBindingProperties - ); - - jmsMessageChannelBinder.setDestinationNameResolver(destinationNameResolver); - - return jmsMessageChannelBinder; + return new JmsMessageDrivenChannelAdapterFactory( + listenerContainerFactory, + messageRecoverer + ); + } + + @Bean + @ConditionalOnMissingBean(JmsSendingMessageHandlerFactory.class) + public JmsSendingMessageHandlerFactory jmsSendingMessageHandlerFactory() { + return new JmsSendingMessageHandlerFactory( + jmsTemplate(), + new SpecCompliantJmsHeaderMapper() + ); + } + + @Bean + @ConditionalOnMissingBean(JmsTemplate.class) + public JmsTemplate jmsTemplate() { + return new JmsTemplate(connectionFactory); + } + + @Configuration + @EnableConfigurationProperties(JmsExtendedBindingProperties.class) + public static class JmsBinderConfiguration { + + @Bean + JMSMessageChannelBinder jmsMessageChannelBinder( + JmsMessageDrivenChannelAdapterFactory jmsMessageDrivenChannelAdapterFactory, + JmsSendingMessageHandlerFactory jmsSendingMessageHandlerFactory, + JmsTemplate jmsTemplate, + ProvisioningProvider, ExtendedProducerProperties> provisioningProvider, + ConnectionFactory connectionFactory, + JmsExtendedBindingProperties jmsExtendedBindingProperties, + DestinationNameResolver destinationNameResolver + ) { + JMSMessageChannelBinder jmsMessageChannelBinder = new JMSMessageChannelBinder( + provisioningProvider, + jmsSendingMessageHandlerFactory, + jmsMessageDrivenChannelAdapterFactory, + jmsTemplate, + connectionFactory + ); + + jmsMessageChannelBinder.setExtendedBindingProperties( + jmsExtendedBindingProperties + ); + + jmsMessageChannelBinder.setDestinationNameResolver(destinationNameResolver); + + return jmsMessageChannelBinder; + } } - } } diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/config/JmsBindingProperties.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/config/JmsBindingProperties.java index 6ddcd979..ef459795 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/config/JmsBindingProperties.java +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/config/JmsBindingProperties.java @@ -1,9 +1,9 @@ /* -** TxEventQ Support for Spring Cloud Stream -** Copyright (c) 2023, 2024 Oracle and/or its affiliates. -** -** This file has been modified by Oracle Corporation. -*/ + ** TxEventQ Support for Spring Cloud Stream + ** Copyright (c) 2023, 2024 Oracle and/or its affiliates. + ** + ** This file has been modified by Oracle Corporation. + */ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -28,23 +28,23 @@ public class JmsBindingProperties implements BinderSpecificPropertiesProvider { - private JmsConsumerProperties consumer = new JmsConsumerProperties(); + private JmsConsumerProperties consumer = new JmsConsumerProperties(); - private JmsProducerProperties producer = new JmsProducerProperties(); + private JmsProducerProperties producer = new JmsProducerProperties(); - public JmsConsumerProperties getConsumer() { - return consumer; - } + public JmsConsumerProperties getConsumer() { + return consumer; + } - public void setConsumer(JmsConsumerProperties consumer) { - this.consumer = consumer; - } + public void setConsumer(JmsConsumerProperties consumer) { + this.consumer = consumer; + } - public JmsProducerProperties getProducer() { - return producer; - } + public JmsProducerProperties getProducer() { + return producer; + } - public void setProducer(JmsProducerProperties producer) { - this.producer = producer; - } + public void setProducer(JmsProducerProperties producer) { + this.producer = producer; + } } diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/config/JmsConsumerProperties.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/config/JmsConsumerProperties.java index 137b7615..5a9dca7b 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/config/JmsConsumerProperties.java +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/config/JmsConsumerProperties.java @@ -1,9 +1,9 @@ /* -** TxEventQ Support for Spring Cloud Stream -** Copyright (c) 2023, 2024 Oracle and/or its affiliates. -** -** This file has been modified by Oracle Corporation. -*/ + ** TxEventQ Support for Spring Cloud Stream + ** Copyright (c) 2023, 2024 Oracle and/or its affiliates. + ** + ** This file has been modified by Oracle Corporation. + */ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -25,47 +25,49 @@ package com.oracle.database.spring.cloud.stream.binder.config; public class JmsConsumerProperties { - private static final String DEFAULT_DLQ_NAME = "Spring_Cloud_Stream_dlq"; + private static final String DEFAULT_DLQ_NAME = "Spring_Cloud_Stream_dlq"; + + /* Properties relevant for batching of messages */ + private int batchSize = 10; + + private int timeout = 1000; // in milliseconds, default => 1 second + + /** + * the name of the dead letter queue + **/ + private String dlqName = DEFAULT_DLQ_NAME; - /* Properties relevant for batching of messages */ - private int batchSize = 10; - - private int timeout = 1000; // in milliseconds, default => 1 second - - /** the name of the dead letter queue **/ - private String dlqName = DEFAULT_DLQ_NAME; - - private String deSerializer = null; + private String deSerializer = null; - public String getDeSerializer() { - return deSerializer; - } + public String getDeSerializer() { + return deSerializer; + } - public void setDeSerializer(String deSerializer) { - this.deSerializer = deSerializer; - } + public void setDeSerializer(String deSerializer) { + this.deSerializer = deSerializer; + } - public String getDlqName() { - return dlqName; - } + public String getDlqName() { + return dlqName; + } - public void setDlqName(String dlqName) { - this.dlqName = dlqName; - } + public void setDlqName(String dlqName) { + this.dlqName = dlqName; + } - public int getTimeout() { - return this.timeout; - } + public int getTimeout() { + return this.timeout; + } - public int getBatchSize() { - return batchSize; - } + public int getBatchSize() { + return batchSize; + } - public void setBatchSize(int batchSize) { - this.batchSize = batchSize; - } + public void setBatchSize(int batchSize) { + this.batchSize = batchSize; + } - public void setTimeout(int timeout) { - this.timeout = timeout; - } + public void setTimeout(int timeout) { + this.timeout = timeout; + } } diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/config/JmsExtendedBindingProperties.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/config/JmsExtendedBindingProperties.java index ddd88f0e..7311fd1f 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/config/JmsExtendedBindingProperties.java +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/config/JmsExtendedBindingProperties.java @@ -1,9 +1,9 @@ /* -** TxEventQ Support for Spring Cloud Stream -** Copyright (c) 2023, 2024 Oracle and/or its affiliates. -** -** This file has been modified by Oracle Corporation. -*/ + ** TxEventQ Support for Spring Cloud Stream + ** Copyright (c) 2023, 2024 Oracle and/or its affiliates. + ** + ** This file has been modified by Oracle Corporation. + */ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -25,29 +25,30 @@ package com.oracle.database.spring.cloud.stream.binder.config; import java.util.Map; + import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.cloud.stream.binder.BinderSpecificPropertiesProvider; import org.springframework.cloud.stream.binder.AbstractExtendedBindingProperties; +import org.springframework.cloud.stream.binder.BinderSpecificPropertiesProvider; @ConfigurationProperties("spring.cloud.stream.txeventq") public class JmsExtendedBindingProperties - extends - AbstractExtendedBindingProperties { + extends + AbstractExtendedBindingProperties { - private static final String DEFAULT_PREFIX = "spring.cloud.stream.txeventq.default"; + private static final String DEFAULT_PREFIX = "spring.cloud.stream.txeventq.default"; - @Override - public Map getBindings() { - return this.doGetBindings(); - } + @Override + public Map getBindings() { + return this.doGetBindings(); + } - @Override - public String getDefaultsPrefix() { - return DEFAULT_PREFIX; - } + @Override + public String getDefaultsPrefix() { + return DEFAULT_PREFIX; + } - @Override - public Class getExtendedPropertiesEntryClass() { - return JmsBindingProperties.class; - } + @Override + public Class getExtendedPropertiesEntryClass() { + return JmsBindingProperties.class; + } } diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/config/JmsProducerProperties.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/config/JmsProducerProperties.java index 6f37c870..60bab761 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/config/JmsProducerProperties.java +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/config/JmsProducerProperties.java @@ -1,9 +1,9 @@ /* -** TxEventQ Support for Spring Cloud Stream -** Copyright (c) 2023, 2024 Oracle and/or its affiliates. -** -** This file has been modified by Oracle Corporation. -*/ + ** TxEventQ Support for Spring Cloud Stream + ** Copyright (c) 2023, 2024 Oracle and/or its affiliates. + ** + ** This file has been modified by Oracle Corporation. + */ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -25,13 +25,13 @@ package com.oracle.database.spring.cloud.stream.binder.config; public class JmsProducerProperties { - private String serializer = null; - - public String getSerializer() { - return this.serializer; - } - - public void setSerializer(String serializer) { - this.serializer = serializer; - } + private String serializer = null; + + public String getSerializer() { + return this.serializer; + } + + public void setSerializer(String serializer) { + this.serializer = serializer; + } } diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/config/TxEventQJmsConfiguration.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/config/TxEventQJmsConfiguration.java index 889fa05b..426d27f0 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/config/TxEventQJmsConfiguration.java +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/config/TxEventQJmsConfiguration.java @@ -1,9 +1,9 @@ /* -** TxEventQ Support for Spring Cloud Stream -** Copyright (c) 2023, 2024 Oracle and/or its affiliates. -** -** This file has been modified by Oracle Corporation. -*/ + ** TxEventQ Support for Spring Cloud Stream + ** Copyright (c) 2023, 2024 Oracle and/or its affiliates. + ** + ** This file has been modified by Oracle Corporation. + */ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -24,17 +24,15 @@ package com.oracle.database.spring.cloud.stream.binder.config; +import java.sql.SQLException; + import com.oracle.database.spring.cloud.stream.binder.TxEventQQueueProvisioner; import com.oracle.database.spring.cloud.stream.binder.plsql.OracleDBUtils; - import jakarta.jms.ConnectionFactory; import jakarta.jms.JMSException; import oracle.jakarta.jms.AQjmsConnectionFactory; import oracle.jakarta.jms.AQjmsFactory; import oracle.ucp.jdbc.PoolDataSource; - -import java.sql.SQLException; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.autoconfigure.AutoConfigureAfter; @@ -51,43 +49,43 @@ @Configuration //It is important to include the root JMS configuration class. @Import(JmsBinderAutoConfiguration.class) -@AutoConfigureAfter({ JndiConnectionFactoryAutoConfiguration.class }) -@ConditionalOnClass({ ConnectionFactory.class, AQjmsConnectionFactory.class }) +@AutoConfigureAfter({JndiConnectionFactoryAutoConfiguration.class}) +@ConditionalOnClass({ConnectionFactory.class, AQjmsConnectionFactory.class}) public class TxEventQJmsConfiguration { - - private final Logger logger = LoggerFactory.getLogger(TxEventQJmsConfiguration.class); - @Bean - @ConditionalOnMissingBean(ConnectionFactory.class) - public ConnectionFactory aqJmsConnectionFactory(PoolDataSource ds) { - ConnectionFactory connectionFactory = null; - try { - connectionFactory = AQjmsFactory.getConnectionFactory(ds); - } catch (JMSException ignore) { - logger.error("Error creating connection factory bean.", ignore); - throw new IllegalArgumentException("Error while trying to obtain connectionFactory."); + private final Logger logger = LoggerFactory.getLogger(TxEventQJmsConfiguration.class); + + @Bean + @ConditionalOnMissingBean(ConnectionFactory.class) + public ConnectionFactory aqJmsConnectionFactory(PoolDataSource ds) { + ConnectionFactory connectionFactory = null; + try { + connectionFactory = AQjmsFactory.getConnectionFactory(ds); + } catch (JMSException ignore) { + logger.error("Error creating connection factory bean.", ignore); + throw new IllegalArgumentException("Error while trying to obtain connectionFactory."); + } + return connectionFactory; } - return connectionFactory; - } - - @Bean - public OracleDBUtils getOracleDBUtils(PoolDataSource pds) { - try(java.sql.Connection conn = pds.getConnection()) { - return new OracleDBUtils(pds, conn.getMetaData().getDatabaseMajorVersion()); - } catch (SQLException e) { - logger.error("Error creating OracleDBUtils Bean."); - throw new IllegalArgumentException("Cannot initialize OracleDBUtils", e); - } - } - @Bean - ProvisioningProvider,ExtendedProducerProperties> txeventQQueueProvisioner( - ConnectionFactory connectionFactory, - OracleDBUtils dbutils - ) { - return new TxEventQQueueProvisioner( - connectionFactory, - dbutils - ); - } + @Bean + public OracleDBUtils getOracleDBUtils(PoolDataSource pds) { + try (java.sql.Connection conn = pds.getConnection()) { + return new OracleDBUtils(pds, conn.getMetaData().getDatabaseMajorVersion()); + } catch (SQLException e) { + logger.error("Error creating OracleDBUtils Bean."); + throw new IllegalArgumentException("Cannot initialize OracleDBUtils", e); + } + } + + @Bean + ProvisioningProvider, ExtendedProducerProperties> txeventQQueueProvisioner( + ConnectionFactory connectionFactory, + OracleDBUtils dbutils + ) { + return new TxEventQQueueProvisioner( + connectionFactory, + dbutils + ); + } } diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/plsql/OracleDBUtils.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/plsql/OracleDBUtils.java index bcba7320..d8779177 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/plsql/OracleDBUtils.java +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/plsql/OracleDBUtils.java @@ -1,9 +1,9 @@ /* -** TxEventQ Support for Spring Cloud Stream -** Copyright (c) 2023, 2024 Oracle and/or its affiliates. -** -** This file has been modified by Oracle Corporation. -*/ + ** TxEventQ Support for Spring Cloud Stream + ** Copyright (c) 2023, 2024 Oracle and/or its affiliates. + ** + ** This file has been modified by Oracle Corporation. + */ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -29,104 +29,102 @@ import java.sql.SQLException; import java.sql.Types; +import oracle.ucp.jdbc.PoolDataSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import oracle.ucp.jdbc.PoolDataSource; - public class OracleDBUtils { - - private PoolDataSource pds = null; - private int dbversion; - private final Logger logger = LoggerFactory.getLogger(getClass()); - - private static final String CREATE_KB2_TEQ = - "BEGIN " - + "dbms_aqadm.create_transactional_event_queue(?, multiple_consumers => true);" - + "dbms_aqadm.set_queue_parameter(?, 'KEY_BASED_ENQUEUE', 2); " - + "dbms_aqadm.set_queue_parameter(?, 'SHARD_NUM', ?); " - + "dbms_aqadm.start_queue(?); " - + "END;"; - - private static final String CREATE_KB1_TEQ = - "BEGIN " - + "dbms_aqadm.create_sharded_queue(?, multiple_consumers => true);" - + "dbms_aqadm.set_queue_parameter(?, 'KEY_BASED_ENQUEUE', 1); " - + "dbms_aqadm.set_queue_parameter(?, 'SHARD_NUM', ?); " - + "FOR i in 0..?-1 " - + "LOOP " - + "dbms_aqadm.set_queue_parameter(?, 'AQ$KEY_TO_SHARD_MAP='||i, i*2); " - + "END LOOP; " - + "dbms_aqadm.start_queue(?); " - + "END;"; - - private static final String GET_PARTITION_COUNT = - "BEGIN " - + "dbms_aqadm.get_queue_parameter(?, 'SHARD_NUM', ?);" - + "END;"; - - public OracleDBUtils(PoolDataSource pds, int dbversion) { - this.pds = pds; - if(dbversion < 19) { - logger.error("DB version: {} not supported.", dbversion); - throw new IllegalArgumentException("The TxEventQ Binder is compatible with database versions >= 19. The current database version is: " + dbversion); - } - this.dbversion = dbversion; - } - - public int getDBVersion() { - return this.dbversion; - } - - public void createKBQ(String qname, int pCount) throws SQLException { - CallableStatement cstmt = null; - try(Connection conn = this.pds.getConnection()) { - if(this.dbversion != 19) { - /* For DB Versions > 19 */ - cstmt = conn.prepareCall(CREATE_KB2_TEQ); - cstmt.setString(1, qname); - cstmt.setString(2, qname); - cstmt.setString(3, qname); - cstmt.setInt(4, pCount); - cstmt.setString(5, qname); - } - else { - /* For DB Version 19*/ - cstmt = conn.prepareCall(CREATE_KB1_TEQ); - cstmt.setString(1, qname); - cstmt.setString(2, qname); - cstmt.setString(3, qname); - cstmt.setInt(4, pCount); - cstmt.setInt(5, pCount); - cstmt.setString(6, qname); - cstmt.setString(7, qname); - } - cstmt.execute(); - } finally { - try { - if(cstmt != null) - cstmt.close(); - } catch(SQLException e) { - logger.error("Error while closing callable statement."); - } - } - } - - public int getTopicPartitions(String qname) throws SQLException { - CallableStatement cstmt = null; - try(Connection conn = pds.getConnection()) { - cstmt = conn.prepareCall(GET_PARTITION_COUNT); - cstmt.setString(1, qname); - cstmt.registerOutParameter(2, Types.INTEGER); - cstmt.execute(); - return cstmt.getInt(2); - } finally { - try { - if(cstmt != null) - cstmt.close(); - } catch(SQLException e) { - logger.error("Error while closing callable statement."); - } - } - } + + private PoolDataSource pds = null; + private int dbversion; + private final Logger logger = LoggerFactory.getLogger(getClass()); + + private static final String CREATE_KB2_TEQ = + "BEGIN " + + "dbms_aqadm.create_transactional_event_queue(?, multiple_consumers => true);" + + "dbms_aqadm.set_queue_parameter(?, 'KEY_BASED_ENQUEUE', 2); " + + "dbms_aqadm.set_queue_parameter(?, 'SHARD_NUM', ?); " + + "dbms_aqadm.start_queue(?); " + + "END;"; + + private static final String CREATE_KB1_TEQ = + "BEGIN " + + "dbms_aqadm.create_sharded_queue(?, multiple_consumers => true);" + + "dbms_aqadm.set_queue_parameter(?, 'KEY_BASED_ENQUEUE', 1); " + + "dbms_aqadm.set_queue_parameter(?, 'SHARD_NUM', ?); " + + "FOR i in 0..?-1 " + + "LOOP " + + "dbms_aqadm.set_queue_parameter(?, 'AQ$KEY_TO_SHARD_MAP='||i, i*2); " + + "END LOOP; " + + "dbms_aqadm.start_queue(?); " + + "END;"; + + private static final String GET_PARTITION_COUNT = + "BEGIN " + + "dbms_aqadm.get_queue_parameter(?, 'SHARD_NUM', ?);" + + "END;"; + + public OracleDBUtils(PoolDataSource pds, int dbversion) { + this.pds = pds; + if (dbversion < 19) { + logger.error("DB version: {} not supported.", dbversion); + throw new IllegalArgumentException("The TxEventQ Binder is compatible with database versions >= 19. The current database version is: " + dbversion); + } + this.dbversion = dbversion; + } + + public int getDBVersion() { + return this.dbversion; + } + + public void createKBQ(String qname, int pCount) throws SQLException { + CallableStatement cstmt = null; + try (Connection conn = this.pds.getConnection()) { + if (this.dbversion != 19) { + /* For DB Versions > 19 */ + cstmt = conn.prepareCall(CREATE_KB2_TEQ); + cstmt.setString(1, qname); + cstmt.setString(2, qname); + cstmt.setString(3, qname); + cstmt.setInt(4, pCount); + cstmt.setString(5, qname); + } else { + /* For DB Version 19*/ + cstmt = conn.prepareCall(CREATE_KB1_TEQ); + cstmt.setString(1, qname); + cstmt.setString(2, qname); + cstmt.setString(3, qname); + cstmt.setInt(4, pCount); + cstmt.setInt(5, pCount); + cstmt.setString(6, qname); + cstmt.setString(7, qname); + } + cstmt.execute(); + } finally { + try { + if (cstmt != null) + cstmt.close(); + } catch (SQLException e) { + logger.error("Error while closing callable statement."); + } + } + } + + public int getTopicPartitions(String qname) throws SQLException { + CallableStatement cstmt = null; + try (Connection conn = pds.getConnection()) { + cstmt = conn.prepareCall(GET_PARTITION_COUNT); + cstmt.setString(1, qname); + cstmt.registerOutParameter(2, Types.INTEGER); + cstmt.execute(); + return cstmt.getInt(2); + } finally { + try { + if (cstmt != null) + cstmt.close(); + } catch (SQLException e) { + logger.error("Error while closing callable statement."); + } + } + } } diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/provisioning/JmsConsumerDestination.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/provisioning/JmsConsumerDestination.java index ea9553a1..85b19acc 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/provisioning/JmsConsumerDestination.java +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/provisioning/JmsConsumerDestination.java @@ -1,9 +1,9 @@ /* -** TxEventQ Support for Spring Cloud Stream -** Copyright (c) 2023, 2024 Oracle and/or its affiliates. -** -** This file has been modified by Oracle Corporation. -*/ + ** TxEventQ Support for Spring Cloud Stream + ** Copyright (c) 2023, 2024 Oracle and/or its affiliates. + ** + ** This file has been modified by Oracle Corporation. + */ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -28,26 +28,26 @@ public class JmsConsumerDestination implements ConsumerDestination { - private final String topicName; - - private final int dbversion; - - public JmsConsumerDestination(final String topicName, final int dbversion) { - this.topicName = topicName; - this.dbversion = dbversion; - } - - @Override - public String getName() { - return this.topicName; - } - - @Override - public String toString() { - return "JmsConsumerDestination{" + "topic=" + topicName + '}'; - } - - public int getDBVersion() { - return this.dbversion; - } + private final String topicName; + + private final int dbversion; + + public JmsConsumerDestination(final String topicName, final int dbversion) { + this.topicName = topicName; + this.dbversion = dbversion; + } + + @Override + public String getName() { + return this.topicName; + } + + @Override + public String toString() { + return "JmsConsumerDestination{" + "topic=" + topicName + '}'; + } + + public int getDBVersion() { + return this.dbversion; + } } diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/provisioning/JmsProducerDestination.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/provisioning/JmsProducerDestination.java index 33bf5c03..355a8e58 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/provisioning/JmsProducerDestination.java +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/provisioning/JmsProducerDestination.java @@ -1,9 +1,9 @@ /* -** TxEventQ Support for Spring Cloud Stream -** Copyright (c) 2023, 2024 Oracle and/or its affiliates. -** -** This file has been modified by Oracle Corporation. -*/ + ** TxEventQ Support for Spring Cloud Stream + ** Copyright (c) 2023, 2024 Oracle and/or its affiliates. + ** + ** This file has been modified by Oracle Corporation. + */ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -32,48 +32,48 @@ public class JmsProducerDestination implements ProducerDestination { - private final Topic topic; - private final int partitionCount; - private int dbversion; + private final Topic topic; + private final int partitionCount; + private int dbversion; - public JmsProducerDestination(Topic topic, int pCount, int dbversion) { - this.topic = topic; - this.partitionCount = pCount; - this.dbversion = dbversion; - } + public JmsProducerDestination(Topic topic, int pCount, int dbversion) { + this.topic = topic; + this.partitionCount = pCount; + this.dbversion = dbversion; + } - @Override - public String getName() { - try { - return topic.getTopicName(); - } catch (JMSException e) { - throw new ProvisioningException( - "Error getting topic name", - JmsUtils.convertJmsAccessException(e) - ); + @Override + public String getName() { + try { + return topic.getTopicName(); + } catch (JMSException e) { + throw new ProvisioningException( + "Error getting topic name", + JmsUtils.convertJmsAccessException(e) + ); + } } - } - @Override - public String getNameForPartition(int partition) { - try { - return topic.getTopicName(); - } catch (JMSException e) { - throw new ProvisioningException( - "Error getting topic name", - JmsUtils.convertJmsAccessException(e) - ); + @Override + public String getNameForPartition(int partition) { + try { + return topic.getTopicName(); + } catch (JMSException e) { + throw new ProvisioningException( + "Error getting topic name", + JmsUtils.convertJmsAccessException(e) + ); + } } - } - - public int getDBVersion() { - return this.dbversion; - } - @Override - public String toString() { - return ( - "JmsProducerDestination{" + "topic=" + topic + ", partitions=" + partitionCount + ", DB Version: " + this.dbversion + "}" - ); - } + public int getDBVersion() { + return this.dbversion; + } + + @Override + public String toString() { + return ( + "JmsProducerDestination{" + "topic=" + topic + ", partitions=" + partitionCount + ", DB Version: " + this.dbversion + "}" + ); + } } diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/serialize/CustomSerializationMessageConverter.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/serialize/CustomSerializationMessageConverter.java index 6ba5622a..0d309763 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/serialize/CustomSerializationMessageConverter.java +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/serialize/CustomSerializationMessageConverter.java @@ -1,9 +1,9 @@ /* -** TxEventQ Support for Spring Cloud Stream -** Copyright (c) 2023, 2025 Oracle and/or its affiliates. -** -** This file has been modified by Oracle Corporation. -*/ + ** TxEventQ Support for Spring Cloud Stream + ** Copyright (c) 2023, 2025 Oracle and/or its affiliates. + ** + ** This file has been modified by Oracle Corporation. + */ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -24,63 +24,62 @@ package com.oracle.database.spring.cloud.stream.binder.serialize; +import jakarta.jms.JMSException; +import jakarta.jms.Message; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.jms.support.converter.SimpleMessageConverter; -import jakarta.jms.JMSException; -import jakarta.jms.Message; - public class CustomSerializationMessageConverter extends SimpleMessageConverter { - public String deserializer = null; - - private static final Logger logger = LoggerFactory.getLogger(CustomSerializationMessageConverter.class); - - public String getDeserializer() { - return deserializer; - } - - public void setDeserializer(String deserializer) { - this.deserializer = deserializer; - } - - @Override - public Object fromMessage(Message jmsMessage) throws JMSException { - Object result = super.fromMessage(jmsMessage); - - // get class object - Class deserializeClass = null; - try { - deserializeClass = Class.forName(deserializer); - } catch (ClassNotFoundException e) { - throw new IllegalArgumentException("Deserialization class not found: " + this.deserializer); - } - - //verify that it is correct instance - boolean isInstanceOfDeserializer = false; - for(Class inter_face: deserializeClass.getInterfaces()) { - if(inter_face.toString().equals(Deserializer.class.toString())) { - isInstanceOfDeserializer = true; - break; - } - } - - if(!isInstanceOfDeserializer) { - logger.debug("The configured deserializer class is not an instance of 'com.oracle.cstream.serialize.DeSerializer'"); - throw new IllegalArgumentException("The configured serializer class is not an instance of 'com.oracle.cstream.serialize.DeSerializer'"); - } - - Deserializer s = null; - - try { - s = (Deserializer)(deserializeClass.getDeclaredConstructor().newInstance()); - } catch(Exception e) { - logger.debug("Serializer object could not be initiated."); - throw new IllegalArgumentException("Serializer object could not be initiated."); - } - - result = (Object)(s.deserialize((byte[])result)); - - return result; - } + public String deserializer = null; + + private static final Logger logger = LoggerFactory.getLogger(CustomSerializationMessageConverter.class); + + public String getDeserializer() { + return deserializer; + } + + public void setDeserializer(String deserializer) { + this.deserializer = deserializer; + } + + @Override + public Object fromMessage(Message jmsMessage) throws JMSException { + Object result = super.fromMessage(jmsMessage); + + // get class object + Class deserializeClass = null; + try { + deserializeClass = Class.forName(deserializer); + } catch (ClassNotFoundException e) { + throw new IllegalArgumentException("Deserialization class not found: " + this.deserializer); + } + + //verify that it is correct instance + boolean isInstanceOfDeserializer = false; + for (Class inter_face : deserializeClass.getInterfaces()) { + if (inter_face.toString().equals(Deserializer.class.toString())) { + isInstanceOfDeserializer = true; + break; + } + } + + if (!isInstanceOfDeserializer) { + logger.debug("The configured deserializer class is not an instance of 'com.oracle.cstream.serialize.DeSerializer'"); + throw new IllegalArgumentException("The configured serializer class is not an instance of 'com.oracle.cstream.serialize.DeSerializer'"); + } + + Deserializer s = null; + + try { + s = (Deserializer) (deserializeClass.getDeclaredConstructor().newInstance()); + } catch (Exception e) { + logger.debug("Serializer object could not be initiated."); + throw new IllegalArgumentException("Serializer object could not be initiated."); + } + + result = (Object) (s.deserialize((byte[]) result)); + + return result; + } } diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/serialize/Deserializer.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/serialize/Deserializer.java index 5dfb4e63..dfc6ea91 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/serialize/Deserializer.java +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/serialize/Deserializer.java @@ -1,9 +1,9 @@ /* -** TxEventQ Support for Spring Cloud Stream -** Copyright (c) 2023, 2024 Oracle and/or its affiliates. -** -** This file has been modified by Oracle Corporation. -*/ + ** TxEventQ Support for Spring Cloud Stream + ** Copyright (c) 2023, 2024 Oracle and/or its affiliates. + ** + ** This file has been modified by Oracle Corporation. + */ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -25,5 +25,5 @@ package com.oracle.database.spring.cloud.stream.binder.serialize; public interface Deserializer { - public T deserialize(byte[] bytes); + public T deserialize(byte[] bytes); } diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/serialize/Serializer.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/serialize/Serializer.java index fbc97299..4afd1540 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/serialize/Serializer.java +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/serialize/Serializer.java @@ -1,9 +1,9 @@ /* -** TxEventQ Support for Spring Cloud Stream -** Copyright (c) 2023, 2024 Oracle and/or its affiliates. -** -** This file has been modified by Oracle Corporation. -*/ + ** TxEventQ Support for Spring Cloud Stream + ** Copyright (c) 2023, 2024 Oracle and/or its affiliates. + ** + ** This file has been modified by Oracle Corporation. + */ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -25,5 +25,5 @@ package com.oracle.database.spring.cloud.stream.binder.serialize; public interface Serializer { - public byte[] serialize(Object data); + public byte[] serialize(Object data); } diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/AnonymousNamingStrategy.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/AnonymousNamingStrategy.java index 5a785cee..9697a8c9 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/AnonymousNamingStrategy.java +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/AnonymousNamingStrategy.java @@ -1,9 +1,9 @@ /* -** TxEventQ Support for Spring Cloud Stream -** Copyright (c) 2023, 2024 Oracle and/or its affiliates. -** -** This file has been modified by Oracle Corporation. -*/ + ** TxEventQ Support for Spring Cloud Stream + ** Copyright (c) 2023, 2024 Oracle and/or its affiliates. + ** + ** This file has been modified by Oracle Corporation. + */ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -26,7 +26,7 @@ public interface AnonymousNamingStrategy { - String generateName(); + String generateName(); - String generateName(String prefix); + String generateName(String prefix); } diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/Base64UrlNamingStrategy.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/Base64UrlNamingStrategy.java index 3b9b3fe0..30483fdb 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/Base64UrlNamingStrategy.java +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/Base64UrlNamingStrategy.java @@ -1,9 +1,9 @@ /* -** TxEventQ Support for Spring Cloud Stream -** Copyright (c) 2023, 2024 Oracle and/or its affiliates. -** -** This file has been modified by Oracle Corporation. -*/ + ** TxEventQ Support for Spring Cloud Stream + ** Copyright (c) 2023, 2024 Oracle and/or its affiliates. + ** + ** This file has been modified by Oracle Corporation. + */ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -30,31 +30,32 @@ public class Base64UrlNamingStrategy implements AnonymousNamingStrategy { - private String prefix = "spring.gen-"; - - public Base64UrlNamingStrategy() {} - - public Base64UrlNamingStrategy(String prefix) { - this.prefix = prefix; - } - - @Override - public String generateName() { - return generateName(this.prefix); - } - - @Override - public String generateName(String prefix) { - UUID uuid = UUID.randomUUID(); - ByteBuffer bb = ByteBuffer.wrap(new byte[16]); - bb - .putLong(uuid.getMostSignificantBits()) - .putLong(uuid.getLeastSignificantBits()); - // Convert to base64 and remove trailing = - Base64.Encoder encoder = Base64.getUrlEncoder().withoutPadding(); - return ( - prefix + - encoder.encodeToString(bb.array()).replace("=", "").replace("-", "\\$") - ); - } + private String prefix = "spring.gen-"; + + public Base64UrlNamingStrategy() { + } + + public Base64UrlNamingStrategy(String prefix) { + this.prefix = prefix; + } + + @Override + public String generateName() { + return generateName(this.prefix); + } + + @Override + public String generateName(String prefix) { + UUID uuid = UUID.randomUUID(); + ByteBuffer bb = ByteBuffer.wrap(new byte[16]); + bb + .putLong(uuid.getMostSignificantBits()) + .putLong(uuid.getLeastSignificantBits()); + // Convert to base64 and remove trailing = + Base64.Encoder encoder = Base64.getUrlEncoder().withoutPadding(); + return ( + prefix + + encoder.encodeToString(bb.array()).replace("=", "").replace("-", "\\$") + ); + } } diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/DestinationNameResolver.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/DestinationNameResolver.java index 435cb7dd..118a4d77 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/DestinationNameResolver.java +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/DestinationNameResolver.java @@ -1,9 +1,9 @@ /* -** TxEventQ Support for Spring Cloud Stream -** Copyright (c) 2023, 2024 Oracle and/or its affiliates. -** -** This file has been modified by Oracle Corporation. -*/ + ** TxEventQ Support for Spring Cloud Stream + ** Copyright (c) 2023, 2024 Oracle and/or its affiliates. + ** + ** This file has been modified by Oracle Corporation. + */ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -27,14 +27,14 @@ import org.springframework.util.StringUtils; public class DestinationNameResolver { - private AnonymousNamingStrategy namingStrategy; + private AnonymousNamingStrategy namingStrategy; - public DestinationNameResolver(AnonymousNamingStrategy namingStrategy) { - this.namingStrategy = namingStrategy; - } + public DestinationNameResolver(AnonymousNamingStrategy namingStrategy) { + this.namingStrategy = namingStrategy; + } - public String resolveGroupName(String group) { - boolean anonymous = !StringUtils.hasText(group); - return anonymous ? namingStrategy.generateName() : group; - } + public String resolveGroupName(String group) { + boolean anonymous = !StringUtils.hasText(group); + return anonymous ? namingStrategy.generateName() : group; + } } diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/JmsMessageDrivenChannelAdapter.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/JmsMessageDrivenChannelAdapter.java index 6fffc5f9..06867e91 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/JmsMessageDrivenChannelAdapter.java +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/JmsMessageDrivenChannelAdapter.java @@ -1,9 +1,9 @@ /* -** TxEventQ Support for Spring Cloud Stream -** Copyright (c) 2023, 2024 Oracle and/or its affiliates. -** -** This file has been modified by Oracle Corporation. -*/ + ** TxEventQ Support for Spring Cloud Stream + ** Copyright (c) 2023, 2024 Oracle and/or its affiliates. + ** + ** This file has been modified by Oracle Corporation. + */ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -34,102 +34,102 @@ import org.springframework.messaging.MessageChannel; public class JmsMessageDrivenChannelAdapter extends MessageProducerSupport implements -OrderlyShutdownCapable { - -private final JmsMessageDrivenEndpoint endpoint; - -private final ChannelPublishingJmsMessageListener listener; - -public JmsMessageDrivenChannelAdapter(AbstractMessageListenerContainer listenerContainer, - ChannelPublishingJmsMessageListener listener) { -this.endpoint = new JmsMessageDrivenEndpoint(listenerContainer, listener); -this.listener = listener; -} - -@Override -public void setOutputChannel(MessageChannel requestChannel) { -super.setOutputChannel(requestChannel); -this.listener.setRequestChannel(requestChannel); -} - -@Override -public void setOutputChannelName(String requestChannelName) { -super.setOutputChannelName(requestChannelName); -this.listener.setRequestChannelName(requestChannelName); -} - -@Override -public void setErrorChannel(MessageChannel errorChannel) { -super.setErrorChannel(errorChannel); -this.listener.setErrorChannel(errorChannel); -} - -@Override -public void setErrorChannelName(String errorChannelName) { -this.listener.setErrorChannelName(errorChannelName); -} - -@Override -public void setSendTimeout(long requestTimeout) { -this.listener.setRequestTimeout(requestTimeout); -} - -@Override -public void setShouldTrack(boolean shouldTrack) { -this.listener.setShouldTrack(shouldTrack); -} - -@Override -public String getComponentType() { -return "jms:message-driven-channel-adapter"; -} - -@Override -public void setComponentName(String componentName) { -super.setComponentName(componentName); -this.endpoint.setComponentName(getComponentName()); -} - -@Override -public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { -super.setApplicationContext(applicationContext); -this.endpoint.setApplicationContext(applicationContext); -this.endpoint.setBeanFactory(applicationContext); -this.listener.setBeanFactory(applicationContext); -} - -@Override -protected void onInit() { -this.endpoint.afterPropertiesSet(); -} - -ChannelPublishingJmsMessageListener getListener() { -return this.listener; -} - -@Override -protected void doStart() { -this.endpoint.start(); -} - -@Override -protected void doStop() { -this.endpoint.stop(); -} - -@Override -public void destroy() { -this.endpoint.destroy(); -} - -@Override -public int beforeShutdown() { -return this.endpoint.beforeShutdown(); -} - -@Override -public int afterShutdown() { -return this.endpoint.afterShutdown(); -} + OrderlyShutdownCapable { + + private final JmsMessageDrivenEndpoint endpoint; + + private final ChannelPublishingJmsMessageListener listener; + + public JmsMessageDrivenChannelAdapter(AbstractMessageListenerContainer listenerContainer, + ChannelPublishingJmsMessageListener listener) { + this.endpoint = new JmsMessageDrivenEndpoint(listenerContainer, listener); + this.listener = listener; + } + + @Override + public void setOutputChannel(MessageChannel requestChannel) { + super.setOutputChannel(requestChannel); + this.listener.setRequestChannel(requestChannel); + } + + @Override + public void setOutputChannelName(String requestChannelName) { + super.setOutputChannelName(requestChannelName); + this.listener.setRequestChannelName(requestChannelName); + } + + @Override + public void setErrorChannel(MessageChannel errorChannel) { + super.setErrorChannel(errorChannel); + this.listener.setErrorChannel(errorChannel); + } + + @Override + public void setErrorChannelName(String errorChannelName) { + this.listener.setErrorChannelName(errorChannelName); + } + + @Override + public void setSendTimeout(long requestTimeout) { + this.listener.setRequestTimeout(requestTimeout); + } + + @Override + public void setShouldTrack(boolean shouldTrack) { + this.listener.setShouldTrack(shouldTrack); + } + + @Override + public String getComponentType() { + return "jms:message-driven-channel-adapter"; + } + + @Override + public void setComponentName(String componentName) { + super.setComponentName(componentName); + this.endpoint.setComponentName(getComponentName()); + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + super.setApplicationContext(applicationContext); + this.endpoint.setApplicationContext(applicationContext); + this.endpoint.setBeanFactory(applicationContext); + this.listener.setBeanFactory(applicationContext); + } + + @Override + protected void onInit() { + this.endpoint.afterPropertiesSet(); + } + + ChannelPublishingJmsMessageListener getListener() { + return this.listener; + } + + @Override + protected void doStart() { + this.endpoint.start(); + } + + @Override + protected void doStop() { + this.endpoint.stop(); + } + + @Override + public void destroy() { + this.endpoint.destroy(); + } + + @Override + public int beforeShutdown() { + return this.endpoint.beforeShutdown(); + } + + @Override + public int afterShutdown() { + return this.endpoint.afterShutdown(); + } } diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/JmsMessageDrivenChannelAdapterFactory.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/JmsMessageDrivenChannelAdapterFactory.java index 86c17a58..24269034 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/JmsMessageDrivenChannelAdapterFactory.java +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/JmsMessageDrivenChannelAdapterFactory.java @@ -1,9 +1,9 @@ /* -** TxEventQ Support for Spring Cloud Stream -** Copyright (c) 2023, 2024 Oracle and/or its affiliates. -** -** This file has been modified by Oracle Corporation. -*/ + ** TxEventQ Support for Spring Cloud Stream + ** Copyright (c) 2023, 2024 Oracle and/or its affiliates. + ** + ** This file has been modified by Oracle Corporation. + */ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -26,14 +26,12 @@ import com.oracle.database.spring.cloud.stream.binder.config.JmsConsumerProperties; import com.oracle.database.spring.cloud.stream.binder.serialize.CustomSerializationMessageConverter; - import jakarta.jms.BytesMessage; import jakarta.jms.Destination; import jakarta.jms.JMSException; import jakarta.jms.Message; import jakarta.jms.Session; import oracle.jakarta.jms.AQjmsSession; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; @@ -51,176 +49,176 @@ import org.springframework.retry.support.RetryTemplate; public class JmsMessageDrivenChannelAdapterFactory - implements ApplicationContextAware, BeanFactoryAware { - - private final ListenerContainerFactory listenerContainerFactory; - - private final MessageRecoverer messageRecoverer; - - private BeanFactory beanFactory; - - private ApplicationContext applicationContext; - - private Logger logger = LoggerFactory.getLogger(getClass()); - - public JmsMessageDrivenChannelAdapterFactory( - ListenerContainerFactory listenerContainerFactory, - MessageRecoverer messageRecoverer - ) { - this.listenerContainerFactory = listenerContainerFactory; - this.messageRecoverer = messageRecoverer; - } - - @Override - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - this.beanFactory = beanFactory; - } - - @Override - public void setApplicationContext(ApplicationContext applicationContext) - throws BeansException { - this.applicationContext = applicationContext; - } - - public JmsMessageDrivenChannelAdapter build( - Destination destination, - String groupName, - RetryTemplate retryTemplate, - RecoveryCallback recoveryCallback, - MessageChannel errorChannel, - final ExtendedConsumerProperties properties, - int dbversion - ) { - ChannelPublishingJmsMessageListener listener = null; - if(!properties.isBatchMode()) { - listener = new RetryingChannelPublishingJmsMessageListener( - properties, - messageRecoverer, - properties.getExtension().getDlqName(), - retryTemplate, - recoveryCallback - ); - if(properties.isUseNativeDecoding()) { - ((RetryingChannelPublishingJmsMessageListener) listener) - .setDeSerializerClassName(properties.getExtension().getDeSerializer()); - } - } else { - listener = new TEQBatchMessageListener(); - ((TEQBatchMessageListener)listener).setRetryTemplate(retryTemplate); - ((TEQBatchMessageListener)listener).setRecoverer(recoveryCallback); - ((TEQBatchMessageListener)listener).setDeSerializerClassName(properties.getExtension().getDeSerializer()); - } - listener.setBeanFactory(this.beanFactory); - - if(dbversion == 19 && properties.getInstanceIndex() != -1) { - logger.warn("Exact dequeue from a specific instanceIndex is not supported in Oracle Database version 19c. " - + "Please use Oracle DB version 23c if you want to perform exact dequeue from a partition."); - } - - JmsMessageDrivenChannelAdapter adapter = new JmsMessageDrivenChannelAdapter( - listenerContainerFactory.build(destination, - groupName, - properties.getInstanceIndex(), - properties.getConcurrency(), - properties.isBatchMode(), - properties.getExtension().getBatchSize(), - properties.getExtension().getTimeout()), - listener - ); - adapter.setApplicationContext(this.applicationContext); - adapter.setBeanFactory(this.beanFactory); - adapter.setErrorChannel(errorChannel); - return adapter; - } - - private static class RetryingChannelPublishingJmsMessageListener - extends ChannelPublishingJmsMessageListener { - - private static final String RETRY_CONTEXT_MESSAGE_ATTRIBUTE = "message"; - - private final ConsumerProperties properties; + implements ApplicationContextAware, BeanFactoryAware { + + private final ListenerContainerFactory listenerContainerFactory; private final MessageRecoverer messageRecoverer; - private final String deadLetterQueueName; - - private final RetryTemplate retryTemplate; - - private RecoveryCallback recoverer; - - private String deSerializerClassName = null; - - SpecCompliantJmsHeaderMapper headerMapper = new SpecCompliantJmsHeaderMapper(); - - RetryingChannelPublishingJmsMessageListener( - ConsumerProperties properties, - MessageRecoverer messageRecoverer, - String deadLetterQueueName, - RetryTemplate retryTemplate, - RecoveryCallback recoverer + private BeanFactory beanFactory; + + private ApplicationContext applicationContext; + + private Logger logger = LoggerFactory.getLogger(getClass()); + + public JmsMessageDrivenChannelAdapterFactory( + ListenerContainerFactory listenerContainerFactory, + MessageRecoverer messageRecoverer ) { - this.properties = properties; - this.messageRecoverer = messageRecoverer; - this.deadLetterQueueName = deadLetterQueueName; - this.retryTemplate = retryTemplate; - this.recoverer = recoverer; + this.listenerContainerFactory = listenerContainerFactory; + this.messageRecoverer = messageRecoverer; } - - public void setDeSerializerClassName(String deSerializerClassName) { - this.deSerializerClassName = deSerializerClassName; + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.beanFactory = beanFactory; } @Override - public void onMessage(final Message jmsMessage, final Session session) - throws JMSException { - this.retryTemplate - .execute( - new RetryCallback() { - @Override - public Object doWithRetry(RetryContext retryContext) - throws JMSException { - try { - // convert data if de-serialization is enabled - if(deSerializerClassName != null) { - // get class name and parameterized type name - CustomSerializationMessageConverter customConverter = new CustomSerializationMessageConverter(); - customConverter.setDeserializer(deSerializerClassName); - RetryingChannelPublishingJmsMessageListener.this.setMessageConverter( - customConverter - ); - } - - retryContext.setAttribute( - RETRY_CONTEXT_MESSAGE_ATTRIBUTE, - jmsMessage - ); - - headerMapper.setConnection(((AQjmsSession) session).getDBConnection()); - RetryingChannelPublishingJmsMessageListener.super.setHeaderMapper(headerMapper); - RetryingChannelPublishingJmsMessageListener.super.onMessage( - jmsMessage, - session - ); - } catch (JMSException e) { - logger.error(e, "Failed to send message"); - resetMessageIfRequired(jmsMessage); - throw e; - } catch (Exception e) { - resetMessageIfRequired(jmsMessage); - throw e; - } - return null; + public void setApplicationContext(ApplicationContext applicationContext) + throws BeansException { + this.applicationContext = applicationContext; + } + + public JmsMessageDrivenChannelAdapter build( + Destination destination, + String groupName, + RetryTemplate retryTemplate, + RecoveryCallback recoveryCallback, + MessageChannel errorChannel, + final ExtendedConsumerProperties properties, + int dbversion + ) { + ChannelPublishingJmsMessageListener listener = null; + if (!properties.isBatchMode()) { + listener = new RetryingChannelPublishingJmsMessageListener( + properties, + messageRecoverer, + properties.getExtension().getDlqName(), + retryTemplate, + recoveryCallback + ); + if (properties.isUseNativeDecoding()) { + ((RetryingChannelPublishingJmsMessageListener) listener) + .setDeSerializerClassName(properties.getExtension().getDeSerializer()); } - }, - this.recoverer + } else { + listener = new TEQBatchMessageListener(); + ((TEQBatchMessageListener) listener).setRetryTemplate(retryTemplate); + ((TEQBatchMessageListener) listener).setRecoverer(recoveryCallback); + ((TEQBatchMessageListener) listener).setDeSerializerClassName(properties.getExtension().getDeSerializer()); + } + listener.setBeanFactory(this.beanFactory); + + if (dbversion == 19 && properties.getInstanceIndex() != -1) { + logger.warn("Exact dequeue from a specific instanceIndex is not supported in Oracle Database version 19c. " + + "Please use Oracle DB version 23c if you want to perform exact dequeue from a partition."); + } + + JmsMessageDrivenChannelAdapter adapter = new JmsMessageDrivenChannelAdapter( + listenerContainerFactory.build(destination, + groupName, + properties.getInstanceIndex(), + properties.getConcurrency(), + properties.isBatchMode(), + properties.getExtension().getBatchSize(), + properties.getExtension().getTimeout()), + listener ); + adapter.setApplicationContext(this.applicationContext); + adapter.setBeanFactory(this.beanFactory); + adapter.setErrorChannel(errorChannel); + return adapter; } - protected void resetMessageIfRequired(Message jmsMessage) - throws JMSException { - if (jmsMessage instanceof BytesMessage message) { - message.reset(); - } + private static class RetryingChannelPublishingJmsMessageListener + extends ChannelPublishingJmsMessageListener { + + private static final String RETRY_CONTEXT_MESSAGE_ATTRIBUTE = "message"; + + private final ConsumerProperties properties; + + private final MessageRecoverer messageRecoverer; + + private final String deadLetterQueueName; + + private final RetryTemplate retryTemplate; + + private RecoveryCallback recoverer; + + private String deSerializerClassName = null; + + SpecCompliantJmsHeaderMapper headerMapper = new SpecCompliantJmsHeaderMapper(); + + RetryingChannelPublishingJmsMessageListener( + ConsumerProperties properties, + MessageRecoverer messageRecoverer, + String deadLetterQueueName, + RetryTemplate retryTemplate, + RecoveryCallback recoverer + ) { + this.properties = properties; + this.messageRecoverer = messageRecoverer; + this.deadLetterQueueName = deadLetterQueueName; + this.retryTemplate = retryTemplate; + this.recoverer = recoverer; + } + + public void setDeSerializerClassName(String deSerializerClassName) { + this.deSerializerClassName = deSerializerClassName; + } + + @Override + public void onMessage(final Message jmsMessage, final Session session) + throws JMSException { + this.retryTemplate + .execute( + new RetryCallback() { + @Override + public Object doWithRetry(RetryContext retryContext) + throws JMSException { + try { + // convert data if de-serialization is enabled + if (deSerializerClassName != null) { + // get class name and parameterized type name + CustomSerializationMessageConverter customConverter = new CustomSerializationMessageConverter(); + customConverter.setDeserializer(deSerializerClassName); + RetryingChannelPublishingJmsMessageListener.this.setMessageConverter( + customConverter + ); + } + + retryContext.setAttribute( + RETRY_CONTEXT_MESSAGE_ATTRIBUTE, + jmsMessage + ); + + headerMapper.setConnection(((AQjmsSession) session).getDBConnection()); + RetryingChannelPublishingJmsMessageListener.super.setHeaderMapper(headerMapper); + RetryingChannelPublishingJmsMessageListener.super.onMessage( + jmsMessage, + session + ); + } catch (JMSException e) { + logger.error(e, "Failed to send message"); + resetMessageIfRequired(jmsMessage); + throw e; + } catch (Exception e) { + resetMessageIfRequired(jmsMessage); + throw e; + } + return null; + } + }, + this.recoverer + ); + } + + protected void resetMessageIfRequired(Message jmsMessage) + throws JMSException { + if (jmsMessage instanceof BytesMessage message) { + message.reset(); + } + } } - } } diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/JmsSendingMessageHandlerFactory.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/JmsSendingMessageHandlerFactory.java index 6911d142..8680ffb3 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/JmsSendingMessageHandlerFactory.java +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/JmsSendingMessageHandlerFactory.java @@ -1,9 +1,9 @@ /* -** TxEventQ Support for Spring Cloud Stream -** Copyright (c) 2023, 2024 Oracle and/or its affiliates. -** -** This file has been modified by Oracle Corporation. -*/ + ** TxEventQ Support for Spring Cloud Stream + ** Copyright (c) 2023, 2024 Oracle and/or its affiliates. + ** + ** This file has been modified by Oracle Corporation. + */ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -24,6 +24,7 @@ package com.oracle.database.spring.cloud.stream.binder.utils; +import jakarta.jms.Destination; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; @@ -33,52 +34,50 @@ import org.springframework.jms.core.JmsTemplate; import org.springframework.messaging.MessageChannel; -import jakarta.jms.Destination; - public class JmsSendingMessageHandlerFactory implements ApplicationContextAware, BeanFactoryAware { - private final JmsTemplate template; + private final JmsTemplate template; - private ApplicationContext applicationContext; + private ApplicationContext applicationContext; - private BeanFactory beanFactory; + private BeanFactory beanFactory; - private final JmsHeaderMapper headerMapper; + private final JmsHeaderMapper headerMapper; - public JmsSendingMessageHandlerFactory(JmsTemplate template, - JmsHeaderMapper headerMapper) { - this.template = template; - this.headerMapper = headerMapper; - } + public JmsSendingMessageHandlerFactory(JmsTemplate template, + JmsHeaderMapper headerMapper) { + this.template = template; + this.headerMapper = headerMapper; + } - @Override - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - this.beanFactory = beanFactory; - } + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.beanFactory = beanFactory; + } - @Override - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { - this.applicationContext = applicationContext; - } + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + } - public PartitionAwareJmsSendingMessageHandler build(Destination destination, - MessageChannel errorChannel, - boolean mapHeaders, - String serializer, - int dbversion) { - template.setPubSubDomain(true); - PartitionAwareJmsSendingMessageHandler handler = new PartitionAwareJmsSendingMessageHandler( - this.template, - destination, - headerMapper, - errorChannel, - mapHeaders); - handler.setSerializerClassName(serializer); - handler.setApplicationContext(this.applicationContext); - handler.setBeanFactory(this.beanFactory); - handler.afterPropertiesSet(); - handler.setDBVersion(dbversion); - return handler; - } + public PartitionAwareJmsSendingMessageHandler build(Destination destination, + MessageChannel errorChannel, + boolean mapHeaders, + String serializer, + int dbversion) { + template.setPubSubDomain(true); + PartitionAwareJmsSendingMessageHandler handler = new PartitionAwareJmsSendingMessageHandler( + this.template, + destination, + headerMapper, + errorChannel, + mapHeaders); + handler.setSerializerClassName(serializer); + handler.setApplicationContext(this.applicationContext); + handler.setBeanFactory(this.beanFactory); + handler.afterPropertiesSet(); + handler.setDBVersion(dbversion); + return handler; + } } diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/ListenerContainerFactory.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/ListenerContainerFactory.java index a414215a..a2d2a029 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/ListenerContainerFactory.java +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/ListenerContainerFactory.java @@ -1,9 +1,9 @@ /* -** TxEventQ Support for Spring Cloud Stream -** Copyright (c) 2023, 2024 Oracle and/or its affiliates. -** -** This file has been modified by Oracle Corporation. -*/ + ** TxEventQ Support for Spring Cloud Stream + ** Copyright (c) 2023, 2024 Oracle and/or its affiliates. + ** + ** This file has been modified by Oracle Corporation. + */ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -26,7 +26,6 @@ import jakarta.jms.ConnectionFactory; import jakarta.jms.Destination; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.jms.listener.AbstractMessageListenerContainer; @@ -34,44 +33,44 @@ public class ListenerContainerFactory { - private ConnectionFactory factory; - - private static final Logger logger = LoggerFactory.getLogger(ListenerContainerFactory.class); + private ConnectionFactory factory; + + private static final Logger logger = LoggerFactory.getLogger(ListenerContainerFactory.class); + + public ListenerContainerFactory(ConnectionFactory factory) { + this.factory = factory; + } - public ListenerContainerFactory(ConnectionFactory factory) { - this.factory = factory; - } + public AbstractMessageListenerContainer build( + Destination topic, + String group, + int partition, + int concurrency, + boolean isBatched, + int batchSize, + int timeout + ) { + DefaultMessageListenerContainer listenerContainer = null; + if (!isBatched) { + listenerContainer = new TEQMessageListenerContainer(); + ((TEQMessageListenerContainer) listenerContainer).setPartition(partition); + } else { + listenerContainer = new TEQBatchMessageListenerContainer(); + ((TEQBatchMessageListenerContainer) listenerContainer).setPartition(partition); + ((TEQBatchMessageListenerContainer) listenerContainer).setBatchSize(batchSize); + } + logger.info("Consuming from destination: {}, Group: {}", topic, group); + listenerContainer.setDestination(topic); + listenerContainer.setPubSubDomain(true); + listenerContainer.setSubscriptionName(group); + listenerContainer.setReceiveTimeout(timeout); - public AbstractMessageListenerContainer build( - Destination topic, - String group, - int partition, - int concurrency, - boolean isBatched, - int batchSize, - int timeout - ) { - DefaultMessageListenerContainer listenerContainer = null; - if(!isBatched) { - listenerContainer = new TEQMessageListenerContainer(); - ((TEQMessageListenerContainer)listenerContainer).setPartition(partition); - } else { - listenerContainer = new TEQBatchMessageListenerContainer(); - ((TEQBatchMessageListenerContainer)listenerContainer).setPartition(partition); - ((TEQBatchMessageListenerContainer)listenerContainer).setBatchSize(batchSize); - } - logger.info("Consuming from destination: {}, Group: {}" , topic, group); - listenerContainer.setDestination(topic); - listenerContainer.setPubSubDomain(true); - listenerContainer.setSubscriptionName(group); - listenerContainer.setReceiveTimeout(timeout); - - listenerContainer.setConcurrentConsumers(concurrency); - if (!group.contains("anonymous")) { - listenerContainer.setSubscriptionDurable(true); + listenerContainer.setConcurrentConsumers(concurrency); + if (!group.contains("anonymous")) { + listenerContainer.setSubscriptionDurable(true); + } + listenerContainer.setConnectionFactory(factory); + listenerContainer.setSessionTransacted(true); + return listenerContainer; } - listenerContainer.setConnectionFactory(factory); - listenerContainer.setSessionTransacted(true); - return listenerContainer; - } } diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/MessageRecoverer.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/MessageRecoverer.java index 92b2407e..05fb031c 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/MessageRecoverer.java +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/MessageRecoverer.java @@ -1,9 +1,9 @@ /* -** TxEventQ Support for Spring Cloud Stream -** Copyright (c) 2023, 2024 Oracle and/or its affiliates. -** -** This file has been modified by Oracle Corporation. -*/ + ** TxEventQ Support for Spring Cloud Stream + ** Copyright (c) 2023, 2024 Oracle and/or its affiliates. + ** + ** This file has been modified by Oracle Corporation. + */ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -27,11 +27,11 @@ import jakarta.jms.Message; public interface MessageRecoverer { - /** - * Recover from the failure to deliver a message. - * - * @param undeliveredMessage the message that has not been delivered. - * @param cause the reason for the failure to deliver. - */ - void recover(Message undeliveredMessage, String dlq, Throwable cause); + /** + * Recover from the failure to deliver a message. + * + * @param undeliveredMessage the message that has not been delivered. + * @param cause the reason for the failure to deliver. + */ + void recover(Message undeliveredMessage, String dlq, Throwable cause); } diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/PartitionAwareJmsSendingMessageHandler.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/PartitionAwareJmsSendingMessageHandler.java index 30c94383..046d79a7 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/PartitionAwareJmsSendingMessageHandler.java +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/PartitionAwareJmsSendingMessageHandler.java @@ -1,9 +1,9 @@ /* -** TxEventQ Support for Spring Cloud Stream -** Copyright (c) 2023, 2025 Oracle and/or its affiliates. -** -** This file has been modified by Oracle Corporation. -*/ + ** TxEventQ Support for Spring Cloud Stream + ** Copyright (c) 2023, 2025 Oracle and/or its affiliates. + ** + ** This file has been modified by Oracle Corporation. + */ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -24,16 +24,16 @@ package com.oracle.database.spring.cloud.stream.binder.utils; +import java.sql.Connection; +import java.util.function.Consumer; + +import com.oracle.database.spring.cloud.stream.binder.serialize.Serializer; import jakarta.jms.Destination; import jakarta.jms.JMSException; import jakarta.jms.MessageProducer; import jakarta.jms.Session; import oracle.jakarta.jms.AQjmsSession; import oracle.jakarta.jms.AQjmsTopicConnectionFactory; - -import java.sql.Connection; -import java.util.function.Consumer; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.cloud.stream.binder.BinderHeaders; @@ -47,236 +47,234 @@ import org.springframework.messaging.MessageChannel; import org.springframework.messaging.support.ErrorMessage; -import com.oracle.database.spring.cloud.stream.binder.serialize.Serializer; - public class PartitionAwareJmsSendingMessageHandler - extends AbstractMessageHandler - implements Lifecycle { - - private final JmsTemplate jmsTemplate; - - private final Destination destination; - - private final JmsHeaderMapper headerMapper; - - private final MessageChannel errorChannel; - - private boolean mapHeaders = true; - - private String serializerClassName = null; - - private int dbversion = 23; - - private static final Logger sendLogger = LoggerFactory.getLogger(PartitionAwareJmsSendingMessageHandler.class); - - public PartitionAwareJmsSendingMessageHandler( - JmsTemplate jmsTemplate, - Destination destination, - JmsHeaderMapper headerMapper, - MessageChannel errorChannel, - boolean mapHeaders - ) { - this.jmsTemplate = jmsTemplate; - this.destination = destination; - this.headerMapper = headerMapper; - this.errorChannel = errorChannel; - this.mapHeaders = mapHeaders; - } - - public void setSerializerClassName(String serializerClassName) { - this.serializerClassName = serializerClassName; - } - - public void setDBVersion(int dbversion) { - this.dbversion = dbversion; - } - - protected void handleMessageInternal(Message message) { - try { - this.handleJMSMessageInternal(message); - } catch(Exception e) { - sendLogger.error("An error occurred while trying to send message:", e); - if(this.errorChannel != null) { - this.errorChannel.send(new ErrorMessage(e, message)); - } - throw e; - } - } - - protected void handleJMSMessageInternal(Message message) { - if (message == null) { - throw new IllegalArgumentException("message must not be null"); - } - Object objectToSend = this.serializeMessageIfRequired(message.getPayload()); - - Integer partitionToSend = getPartition(message); - HeaderMappingMessagePostProcessor messagePostProcessor = new HeaderMappingMessagePostProcessor( - message, - this.headerMapper, - partitionToSend, - mapHeaders - ); - messagePostProcessor.setDBVersion(this.dbversion); - - // try to read ConnectionCallback - if set - @SuppressWarnings("unchecked") - Consumer connCallback = (Consumer) message.getHeaders().get(TxEventQBinderHeaderConstants.CONNECTION_CONSUMER); - if(connCallback == null) { - this.jmsTemplate.convertAndSend( - destination, - objectToSend, - messagePostProcessor - ); - return; - } + extends AbstractMessageHandler + implements Lifecycle { - Connection c = (Connection) message.getHeaders().get(TxEventQBinderHeaderConstants.MESSAGE_CONTEXT); - if(c == null) { - final Object actualPayload = objectToSend; - jmsTemplate.send(destination, session -> { - Connection sessionConnection = ((AQjmsSession) session).getDBConnection(); - connCallback.accept(sessionConnection); - MessageConverter msgConverter = this.jmsTemplate.getMessageConverter(); - if (msgConverter == null) { - throw new IllegalStateException("No 'messageConverter' specified. Check configuration of JmsTemplate."); - } - jakarta.jms.Message msg = msgConverter.toMessage(actualPayload, session); - return messagePostProcessor.postProcessMessage(msg); - }); - } else { - connCallback.accept(c); - // create topic connection and session using c - try(jakarta.jms.Connection conn = AQjmsTopicConnectionFactory.createTopicConnection(c); - Session s = conn.createSession(true, this.jmsTemplate.getSessionAcknowledgeMode()); - MessageProducer p = s.createProducer(destination)) { - MessageConverter msgConverter = this.jmsTemplate.getMessageConverter(); - if(msgConverter == null) { - throw new IllegalStateException("No 'messageConverter' specified. Check configuration of JmsTemplate."); - } - jakarta.jms.Message msg = msgConverter.toMessage(objectToSend, s); - jakarta.jms.Message msgToSend = messagePostProcessor.postProcessMessage(msg); - p.send(msgToSend); - s.commit(); - } catch (JMSException e) { - e.printStackTrace(); - } - } - } - - private Object serializeMessageIfRequired(Object objectToSend) { - if(this.serializerClassName != null) { - Class serializer = null; - try { - serializer = Class.forName(this.serializerClassName); - } catch(ClassNotFoundException ce) { - sendLogger.debug("The class name: {} is invalid.", serializerClassName); - throw new IllegalArgumentException(ce.getMessage()); - } - - boolean isInstanceOfSerializer = false; - for(Class inter_face: serializer.getInterfaces()) { - if(inter_face.toString().equals(Serializer.class.toString())) { - isInstanceOfSerializer = true; - break; - } - } - - if(!isInstanceOfSerializer) { - sendLogger.debug("The configured serializer class is not an instance of 'com.oracle.cstream.serialize.Serializer'"); - throw new IllegalArgumentException("The configured serializer class is not an instance of 'com.oracle.cstream.serialize.Serializer'"); - } - Serializer s = null; - - try { - s = (Serializer)(serializer.getDeclaredConstructor().newInstance()); - } catch(Exception e) { - sendLogger.debug("Serializer object could not be initiated."); - throw new IllegalArgumentException("Serializer object could not be initiated."); - } - - objectToSend = s.serialize(objectToSend); - } - return objectToSend; - } - - private Integer getPartition(Message message) { - try { - return (Integer)(message.getHeaders().get(BinderHeaders.PARTITION_HEADER)); - } catch(Exception e) { - sendLogger.info("Invalid Partition Index"); - throw new IllegalArgumentException("The partition index cannot be converted to an integer"); - } - } + private final JmsTemplate jmsTemplate; - private static final class HeaderMappingMessagePostProcessor - implements MessagePostProcessor { + private final Destination destination; - private final Message integrationMessage; private final JmsHeaderMapper headerMapper; - private final Integer partition; - private final boolean mapHeaders; + + private final MessageChannel errorChannel; + + private boolean mapHeaders = true; + + private String serializerClassName = null; + private int dbversion = 23; - private HeaderMappingMessagePostProcessor( - Message integrationMessage, - JmsHeaderMapper headerMapper, - Integer pNum, - boolean mapHeaders + private static final Logger sendLogger = LoggerFactory.getLogger(PartitionAwareJmsSendingMessageHandler.class); + + public PartitionAwareJmsSendingMessageHandler( + JmsTemplate jmsTemplate, + Destination destination, + JmsHeaderMapper headerMapper, + MessageChannel errorChannel, + boolean mapHeaders ) { - this.integrationMessage = integrationMessage; - this.headerMapper = headerMapper; - this.partition = pNum; - this.mapHeaders = mapHeaders; + this.jmsTemplate = jmsTemplate; + this.destination = destination; + this.headerMapper = headerMapper; + this.errorChannel = errorChannel; + this.mapHeaders = mapHeaders; + } + + public void setSerializerClassName(String serializerClassName) { + this.serializerClassName = serializerClassName; } public void setDBVersion(int dbversion) { - this.dbversion = dbversion; - } - - public jakarta.jms.Message postProcessMessage( - jakarta.jms.Message jmsMessage - ) throws JMSException { - if(this.mapHeaders) { - this.headerMapper.fromHeaders( - this.integrationMessage.getHeaders(), - jmsMessage - ); - } - - // set partition property if not null - if(this.partition != null) { - if(this.dbversion == 19) - jmsMessage.setJMSCorrelationID("" + this.partition); - else - jmsMessage.setLongProperty("AQINTERNAL_PARTITION", this.partition * 2L); - } else { - // choose 0 by default - if(this.dbversion != 19) - jmsMessage.setLongProperty("AQINTERNAL_PARTITION", 0L); - } - - return jmsMessage; + this.dbversion = dbversion; + } + + protected void handleMessageInternal(Message message) { + try { + this.handleJMSMessageInternal(message); + } catch (Exception e) { + sendLogger.error("An error occurred while trying to send message:", e); + if (this.errorChannel != null) { + this.errorChannel.send(new ErrorMessage(e, message)); + } + throw e; + } + } + + protected void handleJMSMessageInternal(Message message) { + if (message == null) { + throw new IllegalArgumentException("message must not be null"); + } + Object objectToSend = this.serializeMessageIfRequired(message.getPayload()); + + Integer partitionToSend = getPartition(message); + HeaderMappingMessagePostProcessor messagePostProcessor = new HeaderMappingMessagePostProcessor( + message, + this.headerMapper, + partitionToSend, + mapHeaders + ); + messagePostProcessor.setDBVersion(this.dbversion); + + // try to read ConnectionCallback - if set + @SuppressWarnings("unchecked") + Consumer connCallback = (Consumer) message.getHeaders().get(TxEventQBinderHeaderConstants.CONNECTION_CONSUMER); + if (connCallback == null) { + this.jmsTemplate.convertAndSend( + destination, + objectToSend, + messagePostProcessor + ); + return; + } + + Connection c = (Connection) message.getHeaders().get(TxEventQBinderHeaderConstants.MESSAGE_CONTEXT); + if (c == null) { + final Object actualPayload = objectToSend; + jmsTemplate.send(destination, session -> { + Connection sessionConnection = ((AQjmsSession) session).getDBConnection(); + connCallback.accept(sessionConnection); + MessageConverter msgConverter = this.jmsTemplate.getMessageConverter(); + if (msgConverter == null) { + throw new IllegalStateException("No 'messageConverter' specified. Check configuration of JmsTemplate."); + } + jakarta.jms.Message msg = msgConverter.toMessage(actualPayload, session); + return messagePostProcessor.postProcessMessage(msg); + }); + } else { + connCallback.accept(c); + // create topic connection and session using c + try (jakarta.jms.Connection conn = AQjmsTopicConnectionFactory.createTopicConnection(c); + Session s = conn.createSession(true, this.jmsTemplate.getSessionAcknowledgeMode()); + MessageProducer p = s.createProducer(destination)) { + MessageConverter msgConverter = this.jmsTemplate.getMessageConverter(); + if (msgConverter == null) { + throw new IllegalStateException("No 'messageConverter' specified. Check configuration of JmsTemplate."); + } + jakarta.jms.Message msg = msgConverter.toMessage(objectToSend, s); + jakarta.jms.Message msgToSend = messagePostProcessor.postProcessMessage(msg); + p.send(msgToSend); + s.commit(); + } catch (JMSException e) { + e.printStackTrace(); + } + } + } + + private Object serializeMessageIfRequired(Object objectToSend) { + if (this.serializerClassName != null) { + Class serializer = null; + try { + serializer = Class.forName(this.serializerClassName); + } catch (ClassNotFoundException ce) { + sendLogger.debug("The class name: {} is invalid.", serializerClassName); + throw new IllegalArgumentException(ce.getMessage()); + } + + boolean isInstanceOfSerializer = false; + for (Class inter_face : serializer.getInterfaces()) { + if (inter_face.toString().equals(Serializer.class.toString())) { + isInstanceOfSerializer = true; + break; + } + } + + if (!isInstanceOfSerializer) { + sendLogger.debug("The configured serializer class is not an instance of 'com.oracle.cstream.serialize.Serializer'"); + throw new IllegalArgumentException("The configured serializer class is not an instance of 'com.oracle.cstream.serialize.Serializer'"); + } + Serializer s = null; + + try { + s = (Serializer) (serializer.getDeclaredConstructor().newInstance()); + } catch (Exception e) { + sendLogger.debug("Serializer object could not be initiated."); + throw new IllegalArgumentException("Serializer object could not be initiated."); + } + + objectToSend = s.serialize(objectToSend); + } + return objectToSend; + } + + private Integer getPartition(Message message) { + try { + return (Integer) (message.getHeaders().get(BinderHeaders.PARTITION_HEADER)); + } catch (Exception e) { + sendLogger.info("Invalid Partition Index"); + throw new IllegalArgumentException("The partition index cannot be converted to an integer"); + } + } + + private static final class HeaderMappingMessagePostProcessor + implements MessagePostProcessor { + + private final Message integrationMessage; + private final JmsHeaderMapper headerMapper; + private final Integer partition; + private final boolean mapHeaders; + private int dbversion = 23; + + private HeaderMappingMessagePostProcessor( + Message integrationMessage, + JmsHeaderMapper headerMapper, + Integer pNum, + boolean mapHeaders + ) { + this.integrationMessage = integrationMessage; + this.headerMapper = headerMapper; + this.partition = pNum; + this.mapHeaders = mapHeaders; + } + + public void setDBVersion(int dbversion) { + this.dbversion = dbversion; + } + + public jakarta.jms.Message postProcessMessage( + jakarta.jms.Message jmsMessage + ) throws JMSException { + if (this.mapHeaders) { + this.headerMapper.fromHeaders( + this.integrationMessage.getHeaders(), + jmsMessage + ); + } + + // set partition property if not null + if (this.partition != null) { + if (this.dbversion == 19) + jmsMessage.setJMSCorrelationID("" + this.partition); + else + jmsMessage.setLongProperty("AQINTERNAL_PARTITION", this.partition * 2L); + } else { + // choose 0 by default + if (this.dbversion != 19) + jmsMessage.setLongProperty("AQINTERNAL_PARTITION", 0L); + } + + return jmsMessage; + } + } + + /* + TODO: This has to be re factored, there is an open issue https://github.com/spring-cloud/spring-cloud-stream/issues/607 + that requires some love first + */ + private boolean running; + + @Override + public synchronized void start() { + running = true; + } + + @Override + public synchronized void stop() { + running = false; + } + + @Override + public synchronized boolean isRunning() { + return running; } - } - - /* - TODO: This has to be re factored, there is an open issue https://github.com/spring-cloud/spring-cloud-stream/issues/607 - that requires some love first - */ - private boolean running; - - @Override - public synchronized void start() { - running = true; - } - - @Override - public synchronized void stop() { - running = false; - } - - @Override - public synchronized boolean isRunning() { - return running; - } } diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/RepublishMessageRecoverer.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/RepublishMessageRecoverer.java index 7a5c1f1e..69ba7f00 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/RepublishMessageRecoverer.java +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/RepublishMessageRecoverer.java @@ -1,9 +1,9 @@ /* -** TxEventQ Support for Spring Cloud Stream -** Copyright (c) 2023, 2024 Oracle and/or its affiliates. -** -** This file has been modified by Oracle Corporation. -*/ + ** TxEventQ Support for Spring Cloud Stream + ** Copyright (c) 2023, 2024 Oracle and/or its affiliates. + ** + ** This file has been modified by Oracle Corporation. + */ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -24,9 +24,10 @@ package com.oracle.database.spring.cloud.stream.binder.utils; +import java.util.Map; + import jakarta.jms.JMSException; import jakarta.jms.Message; -import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.integration.jms.JmsHeaderMapper; @@ -38,55 +39,55 @@ public class RepublishMessageRecoverer implements MessageRecoverer { - public static final String X_EXCEPTION_MESSAGE = "x_exception_message"; - public static final String X_ORIGINAL_QUEUE = "x_original_queue"; - public static final String X_EXCEPTION_STACKTRACE = "x_exception_stacktrace"; - - private final Log logger = LogFactory.getLog(getClass()); - - private final JmsTemplate jmsTemplate; - private final JmsHeaderMapper mapper; - - public RepublishMessageRecoverer( - JmsTemplate jmsTemplate, - JmsHeaderMapper mapper - ) { - this.jmsTemplate = jmsTemplate; - this.mapper = mapper; - } - - @Override - public void recover(Message undeliveredMessage, String dlq, Throwable cause) { - //String deadLetterQueueName = destination.getDlq(); - - MessageConverter converter = new SimpleMessageConverter(); - Object payload = null; - - try { - payload = converter.fromMessage(undeliveredMessage); - } catch (JMSException e) { - logger.error( - "The message payload could not be retrieved. It will be lost.", - e - ); - } + public static final String X_EXCEPTION_MESSAGE = "x_exception_message"; + public static final String X_ORIGINAL_QUEUE = "x_original_queue"; + public static final String X_EXCEPTION_STACKTRACE = "x_exception_stacktrace"; - final Map headers = mapper.toHeaders(undeliveredMessage); - headers.put(X_EXCEPTION_STACKTRACE, getStackTraceAsString(cause)); - headers.put( - X_EXCEPTION_MESSAGE, - cause.getCause() != null - ? cause.getCause().getMessage() - : cause.getMessage() - ); - try { - headers.put( - X_ORIGINAL_QUEUE, - undeliveredMessage.getJMSDestination().toString() - ); - } catch (JMSException e) { - logger.error("The message destination could not be retrieved", e); + private final Log logger = LogFactory.getLog(getClass()); + + private final JmsTemplate jmsTemplate; + private final JmsHeaderMapper mapper; + + public RepublishMessageRecoverer( + JmsTemplate jmsTemplate, + JmsHeaderMapper mapper + ) { + this.jmsTemplate = jmsTemplate; + this.mapper = mapper; } + + @Override + public void recover(Message undeliveredMessage, String dlq, Throwable cause) { + //String deadLetterQueueName = destination.getDlq(); + + MessageConverter converter = new SimpleMessageConverter(); + Object payload = null; + + try { + payload = converter.fromMessage(undeliveredMessage); + } catch (JMSException e) { + logger.error( + "The message payload could not be retrieved. It will be lost.", + e + ); + } + + final Map headers = mapper.toHeaders(undeliveredMessage); + headers.put(X_EXCEPTION_STACKTRACE, getStackTraceAsString(cause)); + headers.put( + X_EXCEPTION_MESSAGE, + cause.getCause() != null + ? cause.getCause().getMessage() + : cause.getMessage() + ); + try { + headers.put( + X_ORIGINAL_QUEUE, + undeliveredMessage.getJMSDestination().toString() + ); + } catch (JMSException e) { + logger.error("The message destination could not be retrieved", e); + } // TODO use in a sublcass for a later version // Map additionalHeaders = additionalHeaders( // undeliveredMessage, @@ -96,43 +97,43 @@ public void recover(Message undeliveredMessage, String dlq, Throwable cause) { // headers.putAll(additionalHeaders); // } - jmsTemplate.convertAndSend( - dlq, - payload, - new MessagePostProcessor() { - @Override - public Message postProcessMessage(Message message) throws JMSException { - mapper.fromHeaders(new MessageHeaders(headers), message); - return message; - } - } - ); - } - - /** - * Provide additional headers for the message. - * - *

Subclasses can override this method to add more headers to the - * undelivered message when it is republished to the DLQ. - * - * @param message The failed message. - * @param cause The cause. - * @return A {@link Map} of additional headers to add. - */ - protected Map additionalHeaders( - Message message, - Throwable cause - ) { - return null; - } - - private String getStackTraceAsString(Throwable cause) { + jmsTemplate.convertAndSend( + dlq, + payload, + new MessagePostProcessor() { + @Override + public Message postProcessMessage(Message message) throws JMSException { + mapper.fromHeaders(new MessageHeaders(headers), message); + return message; + } + } + ); + } + + /** + * Provide additional headers for the message. + * + *

Subclasses can override this method to add more headers to the + * undelivered message when it is republished to the DLQ. + * + * @param message The failed message. + * @param cause The cause. + * @return A {@link Map} of additional headers to add. + */ + protected Map additionalHeaders( + Message message, + Throwable cause + ) { + return null; + } + + private String getStackTraceAsString(Throwable cause) { /* StringWriter stringWriter = new StringWriter(); PrintWriter printWriter = new PrintWriter(stringWriter, true); cause.printStackTrace(printWriter); return stringWriter.getBuffer().toString(); */ - return cause.getMessage(); - } + return cause.getMessage(); + } } diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/SpecCompliantJmsHeaderMapper.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/SpecCompliantJmsHeaderMapper.java index 552cf1c5..5e8f7aeb 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/SpecCompliantJmsHeaderMapper.java +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/SpecCompliantJmsHeaderMapper.java @@ -1,9 +1,9 @@ /* -** TxEventQ Support for Spring Cloud Stream -** Copyright (c) 2023, 2025 Oracle and/or its affiliates. -** -** This file has been modified by Oracle Corporation. -*/ + ** TxEventQ Support for Spring Cloud Stream + ** Copyright (c) 2023, 2025 Oracle and/or its affiliates. + ** + ** This file has been modified by Oracle Corporation. + */ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -24,8 +24,6 @@ package com.oracle.database.spring.cloud.stream.binder.utils; -import jakarta.jms.Message; - import java.io.Serializable; import java.sql.Connection; import java.util.ArrayList; @@ -35,6 +33,7 @@ import java.util.Map; import java.util.UUID; +import jakarta.jms.Message; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.integration.jms.DefaultJmsHeaderMapper; @@ -43,71 +42,71 @@ public class SpecCompliantJmsHeaderMapper extends DefaultJmsHeaderMapper { - private static final Logger logger = LoggerFactory.getLogger( - SpecCompliantJmsHeaderMapper.class - ); - - private static final List> SUPPORTED_PROPERTY_TYPES = - Arrays.asList(Boolean.class, Byte.class, Double.class, Float.class, Integer.class, Long.class, Short.class, - String.class, byte[].class, UUID.class); - - private static final List DEFAULT_TO_STRING_CLASSES = - Arrays.asList( - "org.springframework.util.MimeType", - "org.springframework.http.MediaType" - ); - - public void addDefaultToStringClass(String className) { - SpecCompliantJmsHeaderMapper.DEFAULT_TO_STRING_CLASSES.add(className); - } - - public List getDefaultToStringClasses() { - return new ArrayList<>(SpecCompliantJmsHeaderMapper.DEFAULT_TO_STRING_CLASSES); - } - - private Connection conn; - - public void setConnection(Connection c) { - this.conn = c; - } - - @Override - public void fromHeaders(MessageHeaders headers, Message jmsMessage) { - Map compliantHeaders = new HashMap<>(headers.size()); - for (Map.Entry entry : headers.entrySet()) { - if (entry.getKey().contains("-")) { - String key = entry.getKey().replace("-", "_"); - logger.trace("Rewriting header name '{}' to conform to JMS spec", key); - compliantHeaders.put(key, entry.getValue()); - } else { - compliantHeaders.put(entry.getKey(), entry.getValue()); - } + private static final Logger logger = LoggerFactory.getLogger( + SpecCompliantJmsHeaderMapper.class + ); + + private static final List> SUPPORTED_PROPERTY_TYPES = + Arrays.asList(Boolean.class, Byte.class, Double.class, Float.class, Integer.class, Long.class, Short.class, + String.class, byte[].class, UUID.class); + + private static final List DEFAULT_TO_STRING_CLASSES = + Arrays.asList( + "org.springframework.util.MimeType", + "org.springframework.http.MediaType" + ); + + public void addDefaultToStringClass(String className) { + SpecCompliantJmsHeaderMapper.DEFAULT_TO_STRING_CLASSES.add(className); } - // for each header, if its value belongs to toString - // classes, convert to String - for (Map.Entry entry : compliantHeaders.entrySet()){ - Object value = entry.getValue(); - if(SpecCompliantJmsHeaderMapper.DEFAULT_TO_STRING_CLASSES.contains(value.getClass().getName())) { - compliantHeaders.put(entry.getKey(), value.toString()); - } else if(!SUPPORTED_PROPERTY_TYPES.contains(value.getClass())) { - if(value instanceof Serializable) { - logger.info("Serializing {} header object", value); - compliantHeaders.put(entry.getKey(), SerializationUtils.serialize(value)); - } else { - logger.info("Storing String representation for header: {}", entry.getKey()); - compliantHeaders.put(entry.getKey(), value.toString()); - } - } + public List getDefaultToStringClasses() { + return new ArrayList<>(SpecCompliantJmsHeaderMapper.DEFAULT_TO_STRING_CLASSES); } - super.fromHeaders(new MessageHeaders(compliantHeaders), jmsMessage); - } + private Connection conn; - @Override - public Map toHeaders(Message jmsMessage) { - Map headers = super.toHeaders(jmsMessage); - headers.put(TxEventQBinderHeaderConstants.MESSAGE_CONNECTION, this.conn); - return headers; - } + public void setConnection(Connection c) { + this.conn = c; + } + + @Override + public void fromHeaders(MessageHeaders headers, Message jmsMessage) { + Map compliantHeaders = new HashMap<>(headers.size()); + for (Map.Entry entry : headers.entrySet()) { + if (entry.getKey().contains("-")) { + String key = entry.getKey().replace("-", "_"); + logger.trace("Rewriting header name '{}' to conform to JMS spec", key); + compliantHeaders.put(key, entry.getValue()); + } else { + compliantHeaders.put(entry.getKey(), entry.getValue()); + } + } + + // for each header, if its value belongs to toString + // classes, convert to String + for (Map.Entry entry : compliantHeaders.entrySet()) { + Object value = entry.getValue(); + if (SpecCompliantJmsHeaderMapper.DEFAULT_TO_STRING_CLASSES.contains(value.getClass().getName())) { + compliantHeaders.put(entry.getKey(), value.toString()); + } else if (!SUPPORTED_PROPERTY_TYPES.contains(value.getClass())) { + if (value instanceof Serializable) { + logger.info("Serializing {} header object", value); + compliantHeaders.put(entry.getKey(), SerializationUtils.serialize(value)); + } else { + logger.info("Storing String representation for header: {}", entry.getKey()); + compliantHeaders.put(entry.getKey(), value.toString()); + } + } + } + + super.fromHeaders(new MessageHeaders(compliantHeaders), jmsMessage); + } + + @Override + public Map toHeaders(Message jmsMessage) { + Map headers = super.toHeaders(jmsMessage); + headers.put(TxEventQBinderHeaderConstants.MESSAGE_CONNECTION, this.conn); + return headers; + } } diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/TEQBatchMessageListener.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/TEQBatchMessageListener.java index a1494f62..79a6bac9 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/TEQBatchMessageListener.java +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/TEQBatchMessageListener.java @@ -1,9 +1,9 @@ /* -** TxEventQ Support for Spring Cloud Stream -** Copyright (c) 2023, 2024 Oracle and/or its affiliates. -** -** This file has been modified by Oracle Corporation. -*/ + ** TxEventQ Support for Spring Cloud Stream + ** Copyright (c) 2023, 2024 Oracle and/or its affiliates. + ** + ** This file has been modified by Oracle Corporation. + */ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -28,6 +28,10 @@ import java.util.List; import java.util.Map; +import com.oracle.database.spring.cloud.stream.binder.serialize.CustomSerializationMessageConverter; +import jakarta.jms.BytesMessage; +import jakarta.jms.JMSException; +import jakarta.jms.Session; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.integration.core.MessagingTemplate; @@ -47,287 +51,278 @@ import org.springframework.retry.RecoveryCallback; import org.springframework.retry.support.RetryTemplate; -import com.oracle.database.spring.cloud.stream.binder.serialize.CustomSerializationMessageConverter; +public class TEQBatchMessageListener extends ChannelPublishingJmsMessageListener { + private final GatewayDelegate gatewayDelegate = new GatewayDelegate(); -import jakarta.jms.BytesMessage; -import jakarta.jms.JMSException; -import jakarta.jms.Session; + private boolean expectReply; -public class TEQBatchMessageListener extends ChannelPublishingJmsMessageListener { - private final GatewayDelegate gatewayDelegate = new GatewayDelegate(); - - private boolean expectReply; - - private MessageConverter messageConverter = new SimpleMessageConverter(); - - private boolean extractRequestPayload = true; - - private JmsHeaderMapper headerMapper = new DefaultJmsHeaderMapper(); - - private BeanFactory beanFactory; - - private MessageBuilderFactory messageBuilderFactory = new DefaultMessageBuilderFactory(); - - private static final String TEQ_BATCHED_HEADERS = "teq_batched_headers"; - - private RetryTemplate retryTemplate; - - private RecoveryCallback recoverer; - - private String RETRY_CONTEXT_MESSAGE_ATTRIBUTE = "message"; - - private String deSerializerClassName = null; - - public String getDeSerializerClassName() { - return deSerializerClassName; - } - - public void setDeSerializerClassName(String deSerializerClassName) { - this.deSerializerClassName = deSerializerClassName; - } - - @Override - public void setExpectReply(boolean expectReply) { - this.expectReply = expectReply; - } - - @Override - public void setComponentName(String componentName) { - this.gatewayDelegate.setComponentName(componentName); - } - - @Override - public void setRequestChannel(MessageChannel requestChannel) { - this.gatewayDelegate.setRequestChannel(requestChannel); - } - - @Override - public void setRequestChannelName(String requestChannelName) { - this.gatewayDelegate.setRequestChannelName(requestChannelName); - } - - public void setRetryTemplate(RetryTemplate retryTemplate) { - this.retryTemplate = retryTemplate; - } - - public void setRecoverer(RecoveryCallback recoverer) { - this.recoverer = recoverer; - } - - @Override - public void setReplyChannel(MessageChannel replyChannel) { - this.gatewayDelegate.setReplyChannel(replyChannel); - } - - @Override - public void setReplyChannelName(String replyChannelName) { - this.gatewayDelegate.setReplyChannelName(replyChannelName); - } - - @Override - public void setErrorChannel(MessageChannel errorChannel) { - this.gatewayDelegate.setErrorChannel(errorChannel); - } - - @Override - public void setErrorChannelName(String errorChannelName) { - this.gatewayDelegate.setErrorChannelName(errorChannelName); - } - - @Override - public void setRequestTimeout(long requestTimeout) { - this.gatewayDelegate.setRequestTimeout(requestTimeout); - } - - @Override - public void setReplyTimeout(long replyTimeout) { - this.gatewayDelegate.setReplyTimeout(replyTimeout); - } - - @Override - public void setErrorOnTimeout(boolean errorOnTimeout) { - this.gatewayDelegate.setErrorOnTimeout(errorOnTimeout); - } - - @Override - public void setShouldTrack(boolean shouldTrack) { - this.gatewayDelegate.setShouldTrack(shouldTrack); - } - - @Override - public String getComponentName() { - return this.gatewayDelegate.getComponentName(); - } - - @Override - public String getComponentType() { - return this.gatewayDelegate.getComponentType(); - } - - @Override - public void setMessageConverter(MessageConverter messageConverter) { - this.messageConverter = messageConverter; - } - - @Override - public void setHeaderMapper(JmsHeaderMapper headerMapper) { - this.headerMapper = headerMapper; - } - - @Override - public void setExtractRequestPayload(boolean extractRequestPayload) { - this.extractRequestPayload = extractRequestPayload; - } - - - @Override - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - this.beanFactory = beanFactory; - } - - @Override - public void onMessage(jakarta.jms.Message jmsMessage, Session session) throws JMSException { - throw new IllegalArgumentException("Single consumer message listener should not be called for batched listener!!"); - } - - public void onMessage(List jmsMessages, Session session) throws JMSException { - this.retryTemplate - .execute(retryContext -> { - try { - - if(deSerializerClassName != null) { - // get class name and parameterized type name - CustomSerializationMessageConverter customConverter = new CustomSerializationMessageConverter(); - customConverter.setDeserializer(deSerializerClassName); - TEQBatchMessageListener.this.setMessageConverter( - customConverter - ); - } - - retryContext.setAttribute( - RETRY_CONTEXT_MESSAGE_ATTRIBUTE, - jmsMessages - ); - - TEQBatchMessageListener.this.onMessageHelper( - jmsMessages, - session + private MessageConverter messageConverter = new SimpleMessageConverter(); + + private boolean extractRequestPayload = true; + + private JmsHeaderMapper headerMapper = new DefaultJmsHeaderMapper(); + + private BeanFactory beanFactory; + + private MessageBuilderFactory messageBuilderFactory = new DefaultMessageBuilderFactory(); + + private static final String TEQ_BATCHED_HEADERS = "teq_batched_headers"; + + private RetryTemplate retryTemplate; + + private RecoveryCallback recoverer; + + private String RETRY_CONTEXT_MESSAGE_ATTRIBUTE = "message"; + + private String deSerializerClassName = null; + + public String getDeSerializerClassName() { + return deSerializerClassName; + } + + public void setDeSerializerClassName(String deSerializerClassName) { + this.deSerializerClassName = deSerializerClassName; + } + + @Override + public void setExpectReply(boolean expectReply) { + this.expectReply = expectReply; + } + + @Override + public void setComponentName(String componentName) { + this.gatewayDelegate.setComponentName(componentName); + } + + @Override + public void setRequestChannel(MessageChannel requestChannel) { + this.gatewayDelegate.setRequestChannel(requestChannel); + } + + @Override + public void setRequestChannelName(String requestChannelName) { + this.gatewayDelegate.setRequestChannelName(requestChannelName); + } + + public void setRetryTemplate(RetryTemplate retryTemplate) { + this.retryTemplate = retryTemplate; + } + + public void setRecoverer(RecoveryCallback recoverer) { + this.recoverer = recoverer; + } + + @Override + public void setReplyChannel(MessageChannel replyChannel) { + this.gatewayDelegate.setReplyChannel(replyChannel); + } + + @Override + public void setReplyChannelName(String replyChannelName) { + this.gatewayDelegate.setReplyChannelName(replyChannelName); + } + + @Override + public void setErrorChannel(MessageChannel errorChannel) { + this.gatewayDelegate.setErrorChannel(errorChannel); + } + + @Override + public void setErrorChannelName(String errorChannelName) { + this.gatewayDelegate.setErrorChannelName(errorChannelName); + } + + @Override + public void setRequestTimeout(long requestTimeout) { + this.gatewayDelegate.setRequestTimeout(requestTimeout); + } + + @Override + public void setReplyTimeout(long replyTimeout) { + this.gatewayDelegate.setReplyTimeout(replyTimeout); + } + + @Override + public void setErrorOnTimeout(boolean errorOnTimeout) { + this.gatewayDelegate.setErrorOnTimeout(errorOnTimeout); + } + + @Override + public void setShouldTrack(boolean shouldTrack) { + this.gatewayDelegate.setShouldTrack(shouldTrack); + } + + @Override + public String getComponentName() { + return this.gatewayDelegate.getComponentName(); + } + + @Override + public String getComponentType() { + return this.gatewayDelegate.getComponentType(); + } + + @Override + public void setMessageConverter(MessageConverter messageConverter) { + this.messageConverter = messageConverter; + } + + @Override + public void setHeaderMapper(JmsHeaderMapper headerMapper) { + this.headerMapper = headerMapper; + } + + @Override + public void setExtractRequestPayload(boolean extractRequestPayload) { + this.extractRequestPayload = extractRequestPayload; + } + + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.beanFactory = beanFactory; + } + + @Override + public void onMessage(jakarta.jms.Message jmsMessage, Session session) throws JMSException { + throw new IllegalArgumentException("Single consumer message listener should not be called for batched listener!!"); + } + + public void onMessage(List jmsMessages, Session session) throws JMSException { + this.retryTemplate + .execute(retryContext -> { + try { + + if (deSerializerClassName != null) { + // get class name and parameterized type name + CustomSerializationMessageConverter customConverter = new CustomSerializationMessageConverter(); + customConverter.setDeserializer(deSerializerClassName); + TEQBatchMessageListener.this.setMessageConverter( + customConverter + ); + } + + retryContext.setAttribute( + RETRY_CONTEXT_MESSAGE_ATTRIBUTE, + jmsMessages + ); + + TEQBatchMessageListener.this.onMessageHelper( + jmsMessages, + session + ); + } catch (JMSException e) { + logger.error(e, "Failed to send message"); + for (jakarta.jms.Message jmsMessage : jmsMessages) + TEQBatchMessageListener.this.resetMessageIfRequired(jmsMessage); + throw new RuntimeException(e); + } catch (Exception e) { + for (jakarta.jms.Message jmsMessage : jmsMessages) + TEQBatchMessageListener.this.resetMessageIfRequired(jmsMessage); + throw e; + } + return null; + }, + this.recoverer ); - } catch (JMSException e) { - logger.error(e, "Failed to send message"); - for(jakarta.jms.Message jmsMessage: jmsMessages) - TEQBatchMessageListener.this.resetMessageIfRequired(jmsMessage); - throw new RuntimeException(e); - } catch (Exception e) { - for(jakarta.jms.Message jmsMessage: jmsMessages) - TEQBatchMessageListener.this.resetMessageIfRequired(jmsMessage); + } + + protected void resetMessageIfRequired(jakarta.jms.Message jmsMessage) + throws JMSException { + if (jmsMessage instanceof BytesMessage) { + BytesMessage message = (BytesMessage) jmsMessage; + message.reset(); + } + } + + public void onMessageHelper(List jmsMessages, Session session) throws JMSException { + Message requestMessage = null; + try { + // result will store the list of appropriately converted message + final List result = new ArrayList<>(); + List> individual_headers = new ArrayList<>(); + if (this.extractRequestPayload) { + for (jakarta.jms.Message jmsMessage : jmsMessages) { + Object payload = this.messageConverter.fromMessage(jmsMessage); + result.add(payload); + Map headers = this.headerMapper.toHeaders(jmsMessage); + individual_headers.add(headers); + this.logger.debug(() -> "converted JMS Message [" + jmsMessage + "] to integration Message payload [" + + payload + "]"); + } + } else { + for (jakarta.jms.Message jmsMessage : jmsMessages) + result.add((Object) jmsMessage); + } + + requestMessage = this.messageBuilderFactory + .withPayload(result) + .setHeader(TEQ_BATCHED_HEADERS, individual_headers) + .build(); + + } catch (RuntimeException e) { + MessageChannel errorChannel = this.gatewayDelegate.getErrorChannel(); + if (errorChannel == null) { throw e; - } - return null; - }, - this.recoverer - ); - } - - protected void resetMessageIfRequired(jakarta.jms.Message jmsMessage) - throws JMSException { - if (jmsMessage instanceof BytesMessage) { - BytesMessage message = (BytesMessage) jmsMessage; - message.reset(); - } - } - - public void onMessageHelper(List jmsMessages, Session session) throws JMSException { - Message requestMessage = null; - try { - // result will store the list of appropriately converted message - final List result = new ArrayList<>(); - List> individual_headers = new ArrayList<>(); - if (this.extractRequestPayload) { - for(jakarta.jms.Message jmsMessage: jmsMessages) { - Object payload = this.messageConverter.fromMessage(jmsMessage); - result.add(payload); - Map headers = this.headerMapper.toHeaders(jmsMessage); - individual_headers.add(headers); - this.logger.debug(() -> "converted JMS Message [" + jmsMessage + "] to integration Message payload [" - + payload + "]"); - } - } - else { - for(jakarta.jms.Message jmsMessage: jmsMessages) - result.add((Object)jmsMessage); - } - - requestMessage = this.messageBuilderFactory - .withPayload(result) - .setHeader(TEQ_BATCHED_HEADERS, individual_headers) - .build(); - - } - catch (RuntimeException e) { - MessageChannel errorChannel = this.gatewayDelegate.getErrorChannel(); - if (errorChannel == null) { - throw e; - } - this.gatewayDelegate.getMessagingTemplate() - .send(errorChannel, - this.gatewayDelegate.buildErrorMessage( - new MessagingException("Inbound conversion failed for: " + jmsMessages, e))); - return; - } - - this.gatewayDelegate.send(requestMessage); - } - - @Override - public void afterPropertiesSet() { - if (this.beanFactory != null) { - this.gatewayDelegate.setBeanFactory(this.beanFactory); - } - this.gatewayDelegate.afterPropertiesSet(); - this.messageBuilderFactory = IntegrationUtils.getMessageBuilderFactory(this.beanFactory); - } - - protected void start() { - this.gatewayDelegate.start(); - } - - protected void stop() { - this.gatewayDelegate.stop(); - } - - private class GatewayDelegate extends MessagingGatewaySupport { - - GatewayDelegate() { - } - - @Override - protected void send(Object request) { // NOSONAR - not useless, increases visibility - super.send(request); - } - - @Override - protected Message sendAndReceiveMessage(Object request) { // NOSONAR - not useless, increases visibility - return super.sendAndReceiveMessage(request); - } - - protected ErrorMessage buildErrorMessage(Throwable throwable) { - return super.buildErrorMessage(null, throwable); - } - - protected MessagingTemplate getMessagingTemplate() { - return this.messagingTemplate; - } - - @Override - public String getComponentType() { - if (TEQBatchMessageListener.this.expectReply) { - return "jms:inbound-gateway"; - } - else { - return "jms:message-driven-channel-adapter"; - } - } - - } + } + this.gatewayDelegate.getMessagingTemplate() + .send(errorChannel, + this.gatewayDelegate.buildErrorMessage( + new MessagingException("Inbound conversion failed for: " + jmsMessages, e))); + return; + } + + this.gatewayDelegate.send(requestMessage); + } + + @Override + public void afterPropertiesSet() { + if (this.beanFactory != null) { + this.gatewayDelegate.setBeanFactory(this.beanFactory); + } + this.gatewayDelegate.afterPropertiesSet(); + this.messageBuilderFactory = IntegrationUtils.getMessageBuilderFactory(this.beanFactory); + } + + protected void start() { + this.gatewayDelegate.start(); + } + + protected void stop() { + this.gatewayDelegate.stop(); + } + + private class GatewayDelegate extends MessagingGatewaySupport { + + GatewayDelegate() { + } + + @Override + protected void send(Object request) { // NOSONAR - not useless, increases visibility + super.send(request); + } + + @Override + protected Message sendAndReceiveMessage(Object request) { // NOSONAR - not useless, increases visibility + return super.sendAndReceiveMessage(request); + } + + protected ErrorMessage buildErrorMessage(Throwable throwable) { + return super.buildErrorMessage(null, throwable); + } + + protected MessagingTemplate getMessagingTemplate() { + return this.messagingTemplate; + } + + @Override + public String getComponentType() { + if (TEQBatchMessageListener.this.expectReply) { + return "jms:inbound-gateway"; + } else { + return "jms:message-driven-channel-adapter"; + } + } + + } } \ No newline at end of file diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/TEQBatchMessageListenerContainer.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/TEQBatchMessageListenerContainer.java index edea463c..acfffc3f 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/TEQBatchMessageListenerContainer.java +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/TEQBatchMessageListenerContainer.java @@ -1,9 +1,9 @@ /* -** TxEventQ Support for Spring Cloud Stream -** Copyright (c) 2023, 2024 Oracle and/or its affiliates. -** -** This file has been modified by Oracle Corporation. -*/ + ** TxEventQ Support for Spring Cloud Stream + ** Copyright (c) 2023, 2024 Oracle and/or its affiliates. + ** + ** This file has been modified by Oracle Corporation. + */ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -27,6 +27,13 @@ import java.util.ArrayList; import java.util.List; +import jakarta.jms.Connection; +import jakarta.jms.Destination; +import jakarta.jms.JMSException; +import jakarta.jms.Message; +import jakarta.jms.MessageConsumer; +import jakarta.jms.Session; +import oracle.jakarta.jms.AQjmsConsumer; import org.springframework.jms.connection.ConnectionFactoryUtils; import org.springframework.jms.connection.JmsResourceHolder; import org.springframework.jms.connection.SingleConnectionFactory; @@ -36,334 +43,315 @@ import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.TransactionSynchronizationManager; +public class TEQBatchMessageListenerContainer extends DefaultMessageListenerContainer { + private final MessageListenerContainerResourceFactory transactionalResourceFactory = + new MessageListenerContainerResourceFactory(); -import jakarta.jms.Connection; -import jakarta.jms.Destination; -import jakarta.jms.JMSException; -import jakarta.jms.Message; -import jakarta.jms.MessageConsumer; -import jakarta.jms.Session; -import oracle.jakarta.jms.AQjmsConsumer; + /** + * Instance variable to consume from a specific partition + */ + private int partition = -1; -public class TEQBatchMessageListenerContainer extends DefaultMessageListenerContainer { - private final MessageListenerContainerResourceFactory transactionalResourceFactory = - new MessageListenerContainerResourceFactory(); - - /** - * Instance variable to consume from a specific partition - */ - private int partition = -1; - - /** - * Instance variable to consume messages in a batch - */ - private int batchSize = 10; - - /** - * Getters and setters for partition - */ - public int getPartition() { - return this.partition; - } - - public void setPartition(int partition) { - this.partition = partition; - } - - public int getBatchSize() { - return this.batchSize; - } - - public void setBatchSize(int bSize) { - this.batchSize = bSize; - } - - /** - * Create a JMS MessageConsumer for the given Session and Destination. - *

This implementation uses JMS 1.1 API. - * Also sets the corresponding partition on AQjmsConsumer - * @param session the JMS Session to create a MessageConsumer for - * @param destination the JMS Destination to create a MessageConsumer for - * @return the new JMS MessageConsumer - * @throws jakarta.jms.JMSException if thrown by JMS API methods - */ - @Override - protected MessageConsumer createConsumer(Session session, Destination destination) throws JMSException { - MessageConsumer consumer = super.createConsumer(session, destination); - if(this.partition != -1) - ((AQjmsConsumer)consumer).setPartition(this.partition); - return consumer; - } - - protected List receiveBatch(MessageConsumer consumer, long timeout) throws JMSException { - List msgs = new ArrayList(); - if (timeout > 0) { - Message[] messages = ((AQjmsConsumer) consumer).bulkReceive(this.batchSize, timeout); - if(messages == null) return null; - for(Message msg: messages) { - msgs.add(msg); - } - } - else if (timeout < 0) { - Message[] messages = ((AQjmsConsumer) consumer).bulkReceiveNoWait(this.batchSize); - if(messages == null) return null; - for(Message msg: messages) { - msgs.add(msg); - } - } - else { - Message[] messages = ((AQjmsConsumer) consumer).bulkReceive(this.batchSize); - if(messages == null) return null; - for(Message msg: messages) { - msgs.add(msg); - } - } - return msgs; - } - - // use -> to receive in batch - /** - * Actually execute the listener for a message received from the given consumer, - * fetching all requires resources and invoking the listener. - * @param session the JMS Session to work on - * @param consumer the MessageConsumer to work on - * @param status the TransactionStatus (may be {@code null}) - * @return whether a message has been received - * @throws JMSException if thrown by JMS methods - * @see #doExecuteListener(jakarta.jms.Session, jakarta.jms.Message) - */ - @Override - protected boolean doReceiveAndExecute(Object invoker, @Nullable Session session, - @Nullable MessageConsumer consumer, @Nullable TransactionStatus status) throws JMSException { - - Connection conToClose = null; - Session sessionToClose = null; - MessageConsumer consumerToClose = null; - try { - Session sessionToUse = session; - boolean transactional = false; - if (sessionToUse == null) { - sessionToUse = ConnectionFactoryUtils.doGetTransactionalSession( - obtainConnectionFactory(), this.transactionalResourceFactory, true); - transactional = (sessionToUse != null); - } - if (sessionToUse == null) { - Connection conToUse; - if (sharedConnectionEnabled()) { - conToUse = getSharedConnection(); - } - else { - conToUse = createConnection(); - conToClose = conToUse; - conToUse.start(); - } - sessionToUse = createSession(conToUse); - sessionToClose = sessionToUse; - } - MessageConsumer consumerToUse = consumer; - if (consumerToUse == null) { - consumerToUse = createListenerConsumer(sessionToUse); - consumerToClose = consumerToUse; - } - List messages = receiveBatch(consumer, getReceiveTimeout()); - if (messages != null) { - if (logger.isDebugEnabled()) { - logger.debug("Received message of type [" + messages.getClass() + "] from consumer [" + - consumerToUse + "] of " + (transactional ? "transactional " : "") + "session [" + - sessionToUse + "]"); - } - messageReceived(invoker, sessionToUse); - boolean exposeResource = (!transactional && isExposeListenerSession() && - !TransactionSynchronizationManager.hasResource(obtainConnectionFactory())); - if (exposeResource) { - TransactionSynchronizationManager.bindResource( - obtainConnectionFactory(), new LocallyExposedJmsResourceHolder(sessionToUse)); - } - try { - doExecuteListener(sessionToUse, messages); - } - catch (Throwable ex) { - if (status != null) { - if (logger.isDebugEnabled()) { - logger.debug("Rolling back transaction because of listener exception thrown: " + ex); - } - status.setRollbackOnly(); - } - handleListenerException(ex); - // Rethrow JMSException to indicate an infrastructure problem - // that may have to trigger recovery... - if (ex instanceof JMSException jmsException) { - throw jmsException; - } - } - finally { - if (exposeResource) { - TransactionSynchronizationManager.unbindResource(obtainConnectionFactory()); - } - } - // Indicate that a message has been received. - return true; - } - else { - if (logger.isTraceEnabled()) { - logger.trace("Consumer [" + consumerToUse + "] of " + (transactional ? "transactional " : "") + - "session [" + sessionToUse + "] did not receive a message"); - } - noMessageReceived(invoker, sessionToUse); - // Nevertheless call commit, in order to reset the transaction timeout (if any). - if (shouldCommitAfterNoMessageReceived(sessionToUse)) { - super.commitIfNecessary(sessionToUse, null); - } - // Indicate that no message has been received. - return false; - } - } - finally { - JmsUtils.closeMessageConsumer(consumerToClose); - JmsUtils.closeSession(sessionToClose); - ConnectionFactoryUtils.releaseConnection(conToClose, getConnectionFactory(), true); - } - } - - protected void doExecuteListener(Session session, List messages) throws JMSException { - if (!isAcceptMessagesWhileStopping() && !isRunning()) { - if (logger.isWarnEnabled()) { - logger.warn("Rejecting received messages because of the listener container " + - "having been stopped in the meantime: " + messages); - } - rollbackIfNecessary(session); - throw new MessageRejectedWhileStoppingException(); - } - - try { - invokeListener(session, messages); - } - catch (JMSException | RuntimeException | Error ex) { - rollbackOnExceptionIfNecessary(session, ex); - throw ex; - } - commitIfNecessary(session, messages); - } - - protected void commitIfNecessary(Session session, @Nullable List messages) throws JMSException { - // Commit session or acknowledge message. - if (session.getTransacted()) { - // Commit necessary - but avoid commit call within a JTA transaction. - if (isSessionLocallyTransacted(session)) { - // Transacted session created by this container -> commit. - JmsUtils.commitIfNecessary(session); - } - } - else if (messages != null && isClientAcknowledge(session)) { - // acknowledge last message of the group only - messages.get(messages.size()-1).acknowledge(); - } - } - - @Override - protected boolean isSessionLocallyTransacted(Session session) { - if (!isSessionTransacted()) { - return false; - } - JmsResourceHolder resourceHolder = - (JmsResourceHolder) TransactionSynchronizationManager.getResource(obtainConnectionFactory()); - return (resourceHolder == null || resourceHolder instanceof LocallyExposedJmsResourceHolder || - !resourceHolder.containsSession(session)); - } - - protected void invokeListener(Session session, List messages) throws JMSException { - Object listener = getMessageListener(); - - if (listener instanceof TEQBatchMessageListener teqBatchListener) { - this.doInvokeListener((TEQBatchMessageListener)teqBatchListener, session, messages); - } - else if (listener != null) { - throw new IllegalArgumentException( - "Only TEQBatchMessageListener supported: " + listener); - } - else { - throw new IllegalStateException("No message listener specified - see property 'messageListener'"); - } - } - - protected void doInvokeListener(TEQBatchMessageListener listener, Session session, List messages) - throws JMSException { - - Connection conToClose = null; - Session sessionToClose = null; - try { - Session sessionToUse = session; - if (!isExposeListenerSession()) { - // We need to expose a separate Session. - conToClose = createConnection(); - sessionToClose = createSession(conToClose); - sessionToUse = sessionToClose; - } - // Actually invoke the message listener... - listener.setHeaderMapper(new SpecCompliantJmsHeaderMapper()); - listener.onMessage(messages, sessionToUse); - // Clean up specially exposed Session, if any. - if (sessionToUse != session) { - if (sessionToUse.getTransacted() && isSessionLocallyTransacted(sessionToUse)) { - // Transacted session created by this container -> commit. - JmsUtils.commitIfNecessary(sessionToUse); - } - } - } - finally { - JmsUtils.closeSession(sessionToClose); - JmsUtils.closeConnection(conToClose); - } - } - - /** - * ResourceFactory implementation that delegates to this listener container's protected callback methods. - */ - private class MessageListenerContainerResourceFactory implements ConnectionFactoryUtils.ResourceFactory { - - @Override - @Nullable - public Connection getConnection(JmsResourceHolder holder) { - return TEQBatchMessageListenerContainer.this.getConnection(holder); - } - - @Override - @Nullable - public Session getSession(JmsResourceHolder holder) { - return TEQBatchMessageListenerContainer.this.getSession(holder); - } - - @Override - public Connection createConnection() throws JMSException { - if (TEQBatchMessageListenerContainer.this.sharedConnectionEnabled()) { - Connection sharedCon = TEQBatchMessageListenerContainer.this.getSharedConnection(); - return new SingleConnectionFactory(sharedCon).createConnection(); - } - else { - return TEQBatchMessageListenerContainer.this.createConnection(); - } - } - - @Override - public Session createSession(Connection con) throws JMSException { - return TEQBatchMessageListenerContainer.this.createSession(con); - } - - @Override - public boolean isSynchedLocalTransactionAllowed() { - return TEQBatchMessageListenerContainer.this.isSessionTransacted(); - } - } - - @SuppressWarnings("serial") - private static class MessageRejectedWhileStoppingException extends RuntimeException { - } + /** + * Instance variable to consume messages in a batch + */ + private int batchSize = 10; + + /** + * Getters and setters for partition + */ + public int getPartition() { + return this.partition; + } + + public void setPartition(int partition) { + this.partition = partition; + } + + public int getBatchSize() { + return this.batchSize; + } + + public void setBatchSize(int bSize) { + this.batchSize = bSize; + } + + /** + * Create a JMS MessageConsumer for the given Session and Destination. + *

This implementation uses JMS 1.1 API. + * Also sets the corresponding partition on AQjmsConsumer + * + * @param session the JMS Session to create a MessageConsumer for + * @param destination the JMS Destination to create a MessageConsumer for + * @return the new JMS MessageConsumer + * @throws jakarta.jms.JMSException if thrown by JMS API methods + */ + @Override + protected MessageConsumer createConsumer(Session session, Destination destination) throws JMSException { + MessageConsumer consumer = super.createConsumer(session, destination); + if (this.partition != -1) + ((AQjmsConsumer) consumer).setPartition(this.partition); + return consumer; + } + + protected List receiveBatch(MessageConsumer consumer, long timeout) throws JMSException { + List msgs = new ArrayList(); + if (timeout > 0) { + Message[] messages = ((AQjmsConsumer) consumer).bulkReceive(this.batchSize, timeout); + if (messages == null) return null; + for (Message msg : messages) { + msgs.add(msg); + } + } else if (timeout < 0) { + Message[] messages = ((AQjmsConsumer) consumer).bulkReceiveNoWait(this.batchSize); + if (messages == null) return null; + for (Message msg : messages) { + msgs.add(msg); + } + } else { + Message[] messages = ((AQjmsConsumer) consumer).bulkReceive(this.batchSize); + if (messages == null) return null; + for (Message msg : messages) { + msgs.add(msg); + } + } + return msgs; + } + + // use -> to receive in batch + + /** + * Actually execute the listener for a message received from the given consumer, + * fetching all requires resources and invoking the listener. + * + * @param session the JMS Session to work on + * @param consumer the MessageConsumer to work on + * @param status the TransactionStatus (may be {@code null}) + * @return whether a message has been received + * @throws JMSException if thrown by JMS methods + * @see #doExecuteListener(jakarta.jms.Session, jakarta.jms.Message) + */ + @Override + protected boolean doReceiveAndExecute(Object invoker, @Nullable Session session, + @Nullable MessageConsumer consumer, @Nullable TransactionStatus status) throws JMSException { + + Connection conToClose = null; + Session sessionToClose = null; + MessageConsumer consumerToClose = null; + try { + Session sessionToUse = session; + boolean transactional = false; + if (sessionToUse == null) { + sessionToUse = ConnectionFactoryUtils.doGetTransactionalSession( + obtainConnectionFactory(), this.transactionalResourceFactory, true); + transactional = (sessionToUse != null); + } + if (sessionToUse == null) { + Connection conToUse; + if (sharedConnectionEnabled()) { + conToUse = getSharedConnection(); + } else { + conToUse = createConnection(); + conToClose = conToUse; + conToUse.start(); + } + sessionToUse = createSession(conToUse); + sessionToClose = sessionToUse; + } + MessageConsumer consumerToUse = consumer; + if (consumerToUse == null) { + consumerToUse = createListenerConsumer(sessionToUse); + consumerToClose = consumerToUse; + } + List messages = receiveBatch(consumer, getReceiveTimeout()); + if (messages != null) { + if (logger.isDebugEnabled()) { + logger.debug("Received message of type [" + messages.getClass() + "] from consumer [" + + consumerToUse + "] of " + (transactional ? "transactional " : "") + "session [" + + sessionToUse + "]"); + } + messageReceived(invoker, sessionToUse); + boolean exposeResource = (!transactional && isExposeListenerSession() && + !TransactionSynchronizationManager.hasResource(obtainConnectionFactory())); + if (exposeResource) { + TransactionSynchronizationManager.bindResource( + obtainConnectionFactory(), new LocallyExposedJmsResourceHolder(sessionToUse)); + } + try { + doExecuteListener(sessionToUse, messages); + } catch (Throwable ex) { + if (status != null) { + if (logger.isDebugEnabled()) { + logger.debug("Rolling back transaction because of listener exception thrown: " + ex); + } + status.setRollbackOnly(); + } + handleListenerException(ex); + // Rethrow JMSException to indicate an infrastructure problem + // that may have to trigger recovery... + if (ex instanceof JMSException jmsException) { + throw jmsException; + } + } finally { + if (exposeResource) { + TransactionSynchronizationManager.unbindResource(obtainConnectionFactory()); + } + } + // Indicate that a message has been received. + return true; + } else { + if (logger.isTraceEnabled()) { + logger.trace("Consumer [" + consumerToUse + "] of " + (transactional ? "transactional " : "") + + "session [" + sessionToUse + "] did not receive a message"); + } + noMessageReceived(invoker, sessionToUse); + // Nevertheless call commit, in order to reset the transaction timeout (if any). + if (shouldCommitAfterNoMessageReceived(sessionToUse)) { + super.commitIfNecessary(sessionToUse, null); + } + // Indicate that no message has been received. + return false; + } + } finally { + JmsUtils.closeMessageConsumer(consumerToClose); + JmsUtils.closeSession(sessionToClose); + ConnectionFactoryUtils.releaseConnection(conToClose, getConnectionFactory(), true); + } + } + + protected void doExecuteListener(Session session, List messages) throws JMSException { + if (!isAcceptMessagesWhileStopping() && !isRunning()) { + if (logger.isWarnEnabled()) { + logger.warn("Rejecting received messages because of the listener container " + + "having been stopped in the meantime: " + messages); + } + rollbackIfNecessary(session); + throw new MessageRejectedWhileStoppingException(); + } + + try { + invokeListener(session, messages); + } catch (JMSException | RuntimeException | Error ex) { + rollbackOnExceptionIfNecessary(session, ex); + throw ex; + } + commitIfNecessary(session, messages); + } + + protected void commitIfNecessary(Session session, @Nullable List messages) throws JMSException { + // Commit session or acknowledge message. + if (session.getTransacted()) { + // Commit necessary - but avoid commit call within a JTA transaction. + if (isSessionLocallyTransacted(session)) { + // Transacted session created by this container -> commit. + JmsUtils.commitIfNecessary(session); + } + } else if (messages != null && isClientAcknowledge(session)) { + // acknowledge last message of the group only + messages.get(messages.size() - 1).acknowledge(); + } + } + + @Override + protected boolean isSessionLocallyTransacted(Session session) { + if (!isSessionTransacted()) { + return false; + } + JmsResourceHolder resourceHolder = + (JmsResourceHolder) TransactionSynchronizationManager.getResource(obtainConnectionFactory()); + return (resourceHolder == null || resourceHolder instanceof LocallyExposedJmsResourceHolder || + !resourceHolder.containsSession(session)); + } + + protected void invokeListener(Session session, List messages) throws JMSException { + Object listener = getMessageListener(); + + if (listener instanceof TEQBatchMessageListener teqBatchListener) { + this.doInvokeListener((TEQBatchMessageListener) teqBatchListener, session, messages); + } else if (listener != null) { + throw new IllegalArgumentException( + "Only TEQBatchMessageListener supported: " + listener); + } else { + throw new IllegalStateException("No message listener specified - see property 'messageListener'"); + } + } + + protected void doInvokeListener(TEQBatchMessageListener listener, Session session, List messages) + throws JMSException { + + Connection conToClose = null; + Session sessionToClose = null; + try { + Session sessionToUse = session; + if (!isExposeListenerSession()) { + // We need to expose a separate Session. + conToClose = createConnection(); + sessionToClose = createSession(conToClose); + sessionToUse = sessionToClose; + } + // Actually invoke the message listener... + listener.setHeaderMapper(new SpecCompliantJmsHeaderMapper()); + listener.onMessage(messages, sessionToUse); + // Clean up specially exposed Session, if any. + if (sessionToUse != session) { + if (sessionToUse.getTransacted() && isSessionLocallyTransacted(sessionToUse)) { + // Transacted session created by this container -> commit. + JmsUtils.commitIfNecessary(sessionToUse); + } + } + } finally { + JmsUtils.closeSession(sessionToClose); + JmsUtils.closeConnection(conToClose); + } + } + + /** + * ResourceFactory implementation that delegates to this listener container's protected callback methods. + */ + private class MessageListenerContainerResourceFactory implements ConnectionFactoryUtils.ResourceFactory { + + @Override + @Nullable + public Connection getConnection(JmsResourceHolder holder) { + return TEQBatchMessageListenerContainer.this.getConnection(holder); + } + + @Override + @Nullable + public Session getSession(JmsResourceHolder holder) { + return TEQBatchMessageListenerContainer.this.getSession(holder); + } + + @Override + public Connection createConnection() throws JMSException { + if (TEQBatchMessageListenerContainer.this.sharedConnectionEnabled()) { + Connection sharedCon = TEQBatchMessageListenerContainer.this.getSharedConnection(); + return new SingleConnectionFactory(sharedCon).createConnection(); + } else { + return TEQBatchMessageListenerContainer.this.createConnection(); + } + } + + @Override + public Session createSession(Connection con) throws JMSException { + return TEQBatchMessageListenerContainer.this.createSession(con); + } + + @Override + public boolean isSynchedLocalTransactionAllowed() { + return TEQBatchMessageListenerContainer.this.isSessionTransacted(); + } + } + + @SuppressWarnings("serial") + private static class MessageRejectedWhileStoppingException extends RuntimeException { + } } class LocallyExposedJmsResourceHolder extends JmsResourceHolder { - - public LocallyExposedJmsResourceHolder(Session session) { - super(session); - } + + public LocallyExposedJmsResourceHolder(Session session) { + super(session); + } } diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/TEQMessageListenerContainer.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/TEQMessageListenerContainer.java index 73e4e7f1..f183539c 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/TEQMessageListenerContainer.java +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/TEQMessageListenerContainer.java @@ -1,9 +1,9 @@ /* -** TxEventQ Support for Spring Cloud Stream -** Copyright (c) 2023, 2024 Oracle and/or its affiliates. -** -** This file has been modified by Oracle Corporation. -*/ + ** TxEventQ Support for Spring Cloud Stream + ** Copyright (c) 2023, 2024 Oracle and/or its affiliates. + ** + ** This file has been modified by Oracle Corporation. + */ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -24,46 +24,46 @@ package com.oracle.database.spring.cloud.stream.binder.utils; -import org.springframework.jms.listener.DefaultMessageListenerContainer; -import oracle.jakarta.jms.AQjmsConsumer; - import jakarta.jms.Destination; import jakarta.jms.JMSException; import jakarta.jms.MessageConsumer; import jakarta.jms.Session; +import oracle.jakarta.jms.AQjmsConsumer; +import org.springframework.jms.listener.DefaultMessageListenerContainer; public class TEQMessageListenerContainer extends DefaultMessageListenerContainer { - - /** - * Instance variable to consume from a specific partition - */ - private int partition = -1; - - /** - * Getters and setters for partition - */ - public int getPartition() { - return this.partition; - } - - public void setPartition(int partition) { - this.partition = partition; - } - - /** - * Create a JMS MessageConsumer for the given Session and Destination. - *

This implementation uses JMS 1.1 API. - * Also sets the corresponding partition on AQjmsConsumer - * @param session the JMS Session to create a MessageConsumer for - * @param destination the JMS Destination to create a MessageConsumer for - * @return the new JMS MessageConsumer - * @throws jakarta.jms.JMSException if thrown by JMS API methods - */ - @Override - protected MessageConsumer createConsumer(Session session, Destination destination) throws JMSException { - MessageConsumer consumer = super.createConsumer(session, destination); - if(this.partition != -1) - ((AQjmsConsumer)consumer).setPartition(this.partition); - return consumer; - } + + /** + * Instance variable to consume from a specific partition + */ + private int partition = -1; + + /** + * Getters and setters for partition + */ + public int getPartition() { + return this.partition; + } + + public void setPartition(int partition) { + this.partition = partition; + } + + /** + * Create a JMS MessageConsumer for the given Session and Destination. + *

This implementation uses JMS 1.1 API. + * Also sets the corresponding partition on AQjmsConsumer + * + * @param session the JMS Session to create a MessageConsumer for + * @param destination the JMS Destination to create a MessageConsumer for + * @return the new JMS MessageConsumer + * @throws jakarta.jms.JMSException if thrown by JMS API methods + */ + @Override + protected MessageConsumer createConsumer(Session session, Destination destination) throws JMSException { + MessageConsumer consumer = super.createConsumer(session, destination); + if (this.partition != -1) + ((AQjmsConsumer) consumer).setPartition(this.partition); + return consumer; + } } diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/TxEventQBinderHeaderConstants.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/TxEventQBinderHeaderConstants.java index 73c156ef..442a36b9 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/TxEventQBinderHeaderConstants.java +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/TxEventQBinderHeaderConstants.java @@ -1,9 +1,10 @@ package com.oracle.database.spring.cloud.stream.binder.utils; public class TxEventQBinderHeaderConstants { - public static final String MESSAGE_CONNECTION = "oracle.jdbc.internal.connection"; - public static final String CONNECTION_CONSUMER = "oracle.jdbc.internal.callback"; - public static final String MESSAGE_CONTEXT = "oracle.jdbc.internal.message_context"; + public static final String MESSAGE_CONNECTION = "oracle.jdbc.internal.connection"; + public static final String CONNECTION_CONSUMER = "oracle.jdbc.internal.callback"; + public static final String MESSAGE_CONTEXT = "oracle.jdbc.internal.message_context"; - private TxEventQBinderHeaderConstants() {} + private TxEventQBinderHeaderConstants() { + } } diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/TxEventQMessageBuilder.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/TxEventQMessageBuilder.java index d9970c23..5494d8a6 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/TxEventQMessageBuilder.java +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/TxEventQMessageBuilder.java @@ -22,236 +22,235 @@ public final class TxEventQMessageBuilder extends AbstractIntegrationMessageBuilder { - private static final Log LOGGER = LogFactory.getLog(TxEventQMessageBuilder.class); - - private final T payload; - - private final IntegrationMessageHeaderAccessor headerAccessor; - - @Nullable - private final Message originalMessage; - - private volatile boolean modified; - - private String[] readOnlyHeaders; - - private TxEventQMessageBuilder(T payload, @Nullable Message originalMessage) { - Assert.notNull(payload, "payload must not be null"); - this.payload = payload; - this.originalMessage = originalMessage; - this.headerAccessor = new IntegrationMessageHeaderAccessor(originalMessage); - if (originalMessage != null) { - this.modified = (!this.payload.equals(originalMessage.getPayload())); - } - } - - @Override - public T getPayload() { - return this.payload; - } - - @Override - public Map getHeaders() { - return this.headerAccessor.toMap(); - } - - @Nullable - @Override - public V getHeader(String key, Class type) { - return this.headerAccessor.getHeader(key, type); - } - - public static TxEventQMessageBuilder fromMessage(Message message) { - Assert.notNull(message, "message must not be null"); - return new TxEventQMessageBuilder<>(message.getPayload(), message); - } - - public static TxEventQMessageBuilder withPayload(T payload) { - return new TxEventQMessageBuilder<>(payload, null); - } - - @Override - public TxEventQMessageBuilder setHeader(String headerName, @Nullable Object headerValue) { - this.headerAccessor.setHeader(headerName, headerValue); - return this; - } - - @Override - public TxEventQMessageBuilder setHeaderIfAbsent(String headerName, Object headerValue) { - this.headerAccessor.setHeaderIfAbsent(headerName, headerValue); - return this; - } - - @Override - public TxEventQMessageBuilder removeHeaders(String... headerPatterns) { - this.headerAccessor.removeHeaders(headerPatterns); - return this; - } - - @Override - public TxEventQMessageBuilder removeHeader(String headerName) { - if (!this.headerAccessor.isReadOnly(headerName)) { - this.headerAccessor.removeHeader(headerName); - } - else if (LOGGER.isInfoEnabled()) { - LOGGER.info("The header [" + headerName + "] is ignored for removal because it is is readOnly."); - } - return this; - } - - @Override - public TxEventQMessageBuilder copyHeaders(@Nullable Map headersToCopy) { - this.headerAccessor.copyHeaders(headersToCopy); - return this; - } - - @Override - public TxEventQMessageBuilder copyHeadersIfAbsent(@Nullable Map headersToCopy) { - if (headersToCopy != null) { - for (Map.Entry entry : headersToCopy.entrySet()) { - String headerName = entry.getKey(); - if (!this.headerAccessor.isReadOnly(headerName)) { - this.headerAccessor.setHeaderIfAbsent(headerName, entry.getValue()); - } - } - } - return this; - } - - @SuppressWarnings("unchecked") - @Override - @Nullable - protected List> getSequenceDetails() { - return (List>) this.headerAccessor.getHeader(IntegrationMessageHeaderAccessor.SEQUENCE_DETAILS); - } - - @Override - @Nullable - protected Object getCorrelationId() { - return this.headerAccessor.getCorrelationId(); - } - - @Override - protected Object getSequenceNumber() { - return this.headerAccessor.getSequenceNumber(); - } - - @Override - protected Object getSequenceSize() { - return this.headerAccessor.getSequenceSize(); - } - - @Override - public TxEventQMessageBuilder pushSequenceDetails(Object correlationId, int sequenceNumber, int sequenceSize) { - super.pushSequenceDetails(correlationId, sequenceNumber, sequenceSize); - return this; - } - - @Override - public TxEventQMessageBuilder popSequenceDetails() { - super.popSequenceDetails(); - return this; - } - - @Override - public TxEventQMessageBuilder setExpirationDate(@Nullable Long expirationDate) { - super.setExpirationDate(expirationDate); - return this; - } - - @Override - public TxEventQMessageBuilder setExpirationDate(@Nullable Date expirationDate) { - super.setExpirationDate(expirationDate); - return this; - } - - @Override - public TxEventQMessageBuilder setCorrelationId(Object correlationId) { - super.setCorrelationId(correlationId); - return this; - } - - @Override - public TxEventQMessageBuilder setReplyChannel(MessageChannel replyChannel) { - super.setReplyChannel(replyChannel); - return this; - } - - @Override - public TxEventQMessageBuilder setReplyChannelName(String replyChannelName) { - super.setReplyChannelName(replyChannelName); - return this; - } - - @Override - public TxEventQMessageBuilder setErrorChannel(MessageChannel errorChannel) { - super.setErrorChannel(errorChannel); - return this; - } - - @Override - public TxEventQMessageBuilder setErrorChannelName(String errorChannelName) { - super.setErrorChannelName(errorChannelName); - return this; - } - - @Override - public TxEventQMessageBuilder setSequenceNumber(Integer sequenceNumber) { - super.setSequenceNumber(sequenceNumber); - return this; - } - - @Override - public TxEventQMessageBuilder setSequenceSize(Integer sequenceSize) { - super.setSequenceSize(sequenceSize); - return this; - } - - @Override - public TxEventQMessageBuilder setPriority(Integer priority) { - super.setPriority(priority); - return this; - } - - public TxEventQMessageBuilder setConnectionCallback(Consumer callback) { - this.setHeader(TxEventQBinderHeaderConstants.CONNECTION_CONSUMER, callback); - return this; - } - - public TxEventQMessageBuilder setConnectionCallbackContext(Consumer callback, Message message) { - this.setHeader(TxEventQBinderHeaderConstants.CONNECTION_CONSUMER, callback); - this.setHeader(TxEventQBinderHeaderConstants.MESSAGE_CONTEXT, TxEventQUtils.getDBConnection(message)); - return this; - } - - public TxEventQMessageBuilder readOnlyHeaders(String... readOnlyHeaders) { - this.readOnlyHeaders = readOnlyHeaders != null ? Arrays.copyOf(readOnlyHeaders, readOnlyHeaders.length) : null; - this.headerAccessor.setReadOnlyHeaders(readOnlyHeaders); - return this; - } - - @Override - @SuppressWarnings("unchecked") - public Message build() { - if (!this.modified && !this.headerAccessor.isModified() && this.originalMessage != null - && !containsReadOnly(this.originalMessage.getHeaders())) { - return this.originalMessage; - } - if (this.payload instanceof Throwable) { - return (Message) new ErrorMessage((Throwable) this.payload, this.headerAccessor.toMap()); - } - return new GenericMessage<>(this.payload, this.headerAccessor.toMap()); - } - - private boolean containsReadOnly(MessageHeaders headers) { - if (!ObjectUtils.isEmpty(this.readOnlyHeaders)) { - for (String readOnly : this.readOnlyHeaders) { - if (headers.containsKey(readOnly)) { - return true; - } - } - } - return false; - } + private static final Log LOGGER = LogFactory.getLog(TxEventQMessageBuilder.class); + + private final T payload; + + private final IntegrationMessageHeaderAccessor headerAccessor; + + @Nullable + private final Message originalMessage; + + private volatile boolean modified; + + private String[] readOnlyHeaders; + + private TxEventQMessageBuilder(T payload, @Nullable Message originalMessage) { + Assert.notNull(payload, "payload must not be null"); + this.payload = payload; + this.originalMessage = originalMessage; + this.headerAccessor = new IntegrationMessageHeaderAccessor(originalMessage); + if (originalMessage != null) { + this.modified = (!this.payload.equals(originalMessage.getPayload())); + } + } + + @Override + public T getPayload() { + return this.payload; + } + + @Override + public Map getHeaders() { + return this.headerAccessor.toMap(); + } + + @Nullable + @Override + public V getHeader(String key, Class type) { + return this.headerAccessor.getHeader(key, type); + } + + public static TxEventQMessageBuilder fromMessage(Message message) { + Assert.notNull(message, "message must not be null"); + return new TxEventQMessageBuilder<>(message.getPayload(), message); + } + + public static TxEventQMessageBuilder withPayload(T payload) { + return new TxEventQMessageBuilder<>(payload, null); + } + + @Override + public TxEventQMessageBuilder setHeader(String headerName, @Nullable Object headerValue) { + this.headerAccessor.setHeader(headerName, headerValue); + return this; + } + + @Override + public TxEventQMessageBuilder setHeaderIfAbsent(String headerName, Object headerValue) { + this.headerAccessor.setHeaderIfAbsent(headerName, headerValue); + return this; + } + + @Override + public TxEventQMessageBuilder removeHeaders(String... headerPatterns) { + this.headerAccessor.removeHeaders(headerPatterns); + return this; + } + + @Override + public TxEventQMessageBuilder removeHeader(String headerName) { + if (!this.headerAccessor.isReadOnly(headerName)) { + this.headerAccessor.removeHeader(headerName); + } else if (LOGGER.isInfoEnabled()) { + LOGGER.info("The header [" + headerName + "] is ignored for removal because it is is readOnly."); + } + return this; + } + + @Override + public TxEventQMessageBuilder copyHeaders(@Nullable Map headersToCopy) { + this.headerAccessor.copyHeaders(headersToCopy); + return this; + } + + @Override + public TxEventQMessageBuilder copyHeadersIfAbsent(@Nullable Map headersToCopy) { + if (headersToCopy != null) { + for (Map.Entry entry : headersToCopy.entrySet()) { + String headerName = entry.getKey(); + if (!this.headerAccessor.isReadOnly(headerName)) { + this.headerAccessor.setHeaderIfAbsent(headerName, entry.getValue()); + } + } + } + return this; + } + + @SuppressWarnings("unchecked") + @Override + @Nullable + protected List> getSequenceDetails() { + return (List>) this.headerAccessor.getHeader(IntegrationMessageHeaderAccessor.SEQUENCE_DETAILS); + } + + @Override + @Nullable + protected Object getCorrelationId() { + return this.headerAccessor.getCorrelationId(); + } + + @Override + protected Object getSequenceNumber() { + return this.headerAccessor.getSequenceNumber(); + } + + @Override + protected Object getSequenceSize() { + return this.headerAccessor.getSequenceSize(); + } + + @Override + public TxEventQMessageBuilder pushSequenceDetails(Object correlationId, int sequenceNumber, int sequenceSize) { + super.pushSequenceDetails(correlationId, sequenceNumber, sequenceSize); + return this; + } + + @Override + public TxEventQMessageBuilder popSequenceDetails() { + super.popSequenceDetails(); + return this; + } + + @Override + public TxEventQMessageBuilder setExpirationDate(@Nullable Long expirationDate) { + super.setExpirationDate(expirationDate); + return this; + } + + @Override + public TxEventQMessageBuilder setExpirationDate(@Nullable Date expirationDate) { + super.setExpirationDate(expirationDate); + return this; + } + + @Override + public TxEventQMessageBuilder setCorrelationId(Object correlationId) { + super.setCorrelationId(correlationId); + return this; + } + + @Override + public TxEventQMessageBuilder setReplyChannel(MessageChannel replyChannel) { + super.setReplyChannel(replyChannel); + return this; + } + + @Override + public TxEventQMessageBuilder setReplyChannelName(String replyChannelName) { + super.setReplyChannelName(replyChannelName); + return this; + } + + @Override + public TxEventQMessageBuilder setErrorChannel(MessageChannel errorChannel) { + super.setErrorChannel(errorChannel); + return this; + } + + @Override + public TxEventQMessageBuilder setErrorChannelName(String errorChannelName) { + super.setErrorChannelName(errorChannelName); + return this; + } + + @Override + public TxEventQMessageBuilder setSequenceNumber(Integer sequenceNumber) { + super.setSequenceNumber(sequenceNumber); + return this; + } + + @Override + public TxEventQMessageBuilder setSequenceSize(Integer sequenceSize) { + super.setSequenceSize(sequenceSize); + return this; + } + + @Override + public TxEventQMessageBuilder setPriority(Integer priority) { + super.setPriority(priority); + return this; + } + + public TxEventQMessageBuilder setConnectionCallback(Consumer callback) { + this.setHeader(TxEventQBinderHeaderConstants.CONNECTION_CONSUMER, callback); + return this; + } + + public TxEventQMessageBuilder setConnectionCallbackContext(Consumer callback, Message message) { + this.setHeader(TxEventQBinderHeaderConstants.CONNECTION_CONSUMER, callback); + this.setHeader(TxEventQBinderHeaderConstants.MESSAGE_CONTEXT, TxEventQUtils.getDBConnection(message)); + return this; + } + + public TxEventQMessageBuilder readOnlyHeaders(String... readOnlyHeaders) { + this.readOnlyHeaders = readOnlyHeaders != null ? Arrays.copyOf(readOnlyHeaders, readOnlyHeaders.length) : null; + this.headerAccessor.setReadOnlyHeaders(readOnlyHeaders); + return this; + } + + @Override + @SuppressWarnings("unchecked") + public Message build() { + if (!this.modified && !this.headerAccessor.isModified() && this.originalMessage != null + && !containsReadOnly(this.originalMessage.getHeaders())) { + return this.originalMessage; + } + if (this.payload instanceof Throwable) { + return (Message) new ErrorMessage((Throwable) this.payload, this.headerAccessor.toMap()); + } + return new GenericMessage<>(this.payload, this.headerAccessor.toMap()); + } + + private boolean containsReadOnly(MessageHeaders headers) { + if (!ObjectUtils.isEmpty(this.readOnlyHeaders)) { + for (String readOnly : this.readOnlyHeaders) { + if (headers.containsKey(readOnly)) { + return true; + } + } + } + return false; + } } diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/TxEventQUtils.java b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/TxEventQUtils.java index 3f7eb311..37c353af 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/TxEventQUtils.java +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/java/com/oracle/database/spring/cloud/stream/binder/utils/TxEventQUtils.java @@ -6,29 +6,30 @@ import org.springframework.messaging.Message; public class TxEventQUtils { - /* Private constructor to avoid creating object of class TxEventQUtils */ - private TxEventQUtils() { } + /* Private constructor to avoid creating object of class TxEventQUtils */ + private TxEventQUtils() { + } - /* Static Utility Methods */ - public static Connection getDBConnection(Message message) { - return (Connection) message - .getHeaders() - .getOrDefault(TxEventQBinderHeaderConstants.MESSAGE_CONNECTION, null); - } + /* Static Utility Methods */ + public static Connection getDBConnection(Message message) { + return (Connection) message + .getHeaders() + .getOrDefault(TxEventQBinderHeaderConstants.MESSAGE_CONNECTION, null); + } - public static Message setConnectionCallbackContext(Message message, - Consumer callback, - Message oldMessage) { - return TxEventQMessageBuilder - .fromMessage(message) - .setConnectionCallbackContext(callback, oldMessage) - .build(); - } + public static Message setConnectionCallbackContext(Message message, + Consumer callback, + Message oldMessage) { + return TxEventQMessageBuilder + .fromMessage(message) + .setConnectionCallbackContext(callback, oldMessage) + .build(); + } - public static Message setConnectionCallback(Message message, Consumer callback) { - return TxEventQMessageBuilder - .fromMessage(message) - .setConnectionCallback(callback) - .build(); - } + public static Message setConnectionCallback(Message message, Consumer callback) { + return TxEventQMessageBuilder + .fromMessage(message) + .setConnectionCallback(callback) + .build(); + } } From 0835cddacbccf5e889bffb25882cb67e8104f76f Mon Sep 17 00:00:00 2001 From: Anders Swanson Date: Thu, 20 Mar 2025 12:10:07 -0700 Subject: [PATCH 4/4] ONS Signed-off-by: Anders Swanson --- .../src/main/resources/META-INF/spring.binders | 5 ----- database/starters/oracle-spring-boot-starter-ucp/pom.xml | 4 ++++ database/starters/pom.xml | 5 +++++ 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/resources/META-INF/spring.binders b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/resources/META-INF/spring.binders index 5aa62aa7..7211a669 100644 --- a/database/spring-cloud-stream-binder-oracle-txeventq/src/main/resources/META-INF/spring.binders +++ b/database/spring-cloud-stream-binder-oracle-txeventq/src/main/resources/META-INF/spring.binders @@ -1,7 +1,2 @@ -<<<<<<< HEAD -txeventqjms:\ -com.oracle.cstream.config.TxEventQJmsConfiguration -======= txeventqjms=\ com.oracle.database.spring.cloud.stream.binder.config.TxEventQJmsConfiguration ->>>>>>> 2ae791e79f499734bb809fc7c193110f1669a51e diff --git a/database/starters/oracle-spring-boot-starter-ucp/pom.xml b/database/starters/oracle-spring-boot-starter-ucp/pom.xml index 345ba989..da39711f 100644 --- a/database/starters/oracle-spring-boot-starter-ucp/pom.xml +++ b/database/starters/oracle-spring-boot-starter-ucp/pom.xml @@ -62,6 +62,10 @@ ojdbc11 compile + + com.oracle.database.ha + ons + com.oracle.database.jdbc ucp diff --git a/database/starters/pom.xml b/database/starters/pom.xml index 0d7e0911..d8334358 100644 --- a/database/starters/pom.xml +++ b/database/starters/pom.xml @@ -161,6 +161,11 @@ ojdbc11 ${oracle.version} + + com.oracle.database.ha + ons + ${oracle.version} + com.oracle.database.jdbc ucp