Merge pull request #2013 from zkzlx/rocketmq

[major update]many features enhanced & many bugs fixed
pull/2102/head 2.2.5-RocketMQ-RC1
TheoneFx 4 years ago committed by GitHub
commit 9d27402646
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -90,6 +90,7 @@
<curator.version>4.0.1</curator.version>
<!-- Apache RocketMQ -->
<rocketmq.version>4.6.1</rocketmq.version>
<rocketmq.starter.version>2.0.2</rocketmq.starter.version>
<!-- Maven Plugin Versions -->
@ -179,10 +180,20 @@
</exclusions>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.apache.rocketmq</groupId>-->
<!-- <artifactId>rocketmq-spring-boot-starter</artifactId>-->
<!-- <version>${rocketmq.starter.version}</version>-->
<!-- </dependency>-->
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>${rocketmq.starter.version}</version>
<artifactId>rocketmq-client</artifactId>
<version>${rocketmq.version}</version>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-acl</artifactId>
<version>${rocketmq.version}</version>
</dependency>
</dependencies>
</dependencyManagement>

@ -3,20 +3,20 @@ spring.cloud.stream.rocketmq.binder.name-server=127.0.0.1:9876
spring.cloud.stream.bindings.input1.destination=test-topic
spring.cloud.stream.bindings.input1.content-type=text/plain
spring.cloud.stream.bindings.input1.group=test-group1
spring.cloud.stream.rocketmq.bindings.input1.consumer.orderly=true
spring.cloud.stream.rocketmq.bindings.input1.consumer.push.orderly=true
spring.cloud.stream.bindings.input2.destination=test-topic
spring.cloud.stream.bindings.input2.content-type=text/plain
spring.cloud.stream.bindings.input2.group=test-group2
spring.cloud.stream.rocketmq.bindings.input2.consumer.orderly=false
spring.cloud.stream.rocketmq.bindings.input2.consumer.tags=tagStr
spring.cloud.stream.rocketmq.bindings.input2.consumer.push.orderly=false
spring.cloud.stream.rocketmq.bindings.input2.consumer.subscription=tagStr
spring.cloud.stream.bindings.input2.consumer.concurrency=20
spring.cloud.stream.bindings.input2.consumer.maxAttempts=1
spring.cloud.stream.bindings.input3.destination=test-topic
spring.cloud.stream.bindings.input3.content-type=application/json
spring.cloud.stream.bindings.input3.group=test-group3
spring.cloud.stream.rocketmq.bindings.input3.consumer.tags=tagObj
spring.cloud.stream.rocketmq.bindings.input3.consumer.subscription=tagObj
spring.cloud.stream.bindings.input3.consumer.concurrency=20
spring.cloud.stream.bindings.input4.destination=TransactionTopic

@ -20,8 +20,8 @@ import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.alibaba.cloud.examples.RocketMQProduceApplication.MySource;
import com.alibaba.cloud.stream.binder.rocketmq.contants.RocketMQConst;
import org.apache.rocketmq.common.message.MessageConst;
import org.apache.rocketmq.spring.support.RocketMQHeaders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
@ -62,7 +62,7 @@ public class SenderService {
MessageBuilder builder = MessageBuilder.withPayload(msg)
.setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_JSON);
builder.setHeader("test", String.valueOf(num));
builder.setHeader(RocketMQHeaders.TAGS, "binder");
builder.setHeader(RocketMQConst.USER_TRANSACTIONAL_ARGS, "binder");
Message message = builder.build();
source.output2().send(message);
}

@ -16,43 +16,39 @@
package com.alibaba.cloud.examples;
import org.apache.rocketmq.spring.annotation.RocketMQTransactionListener;
import org.apache.rocketmq.spring.core.RocketMQLocalTransactionListener;
import org.apache.rocketmq.spring.core.RocketMQLocalTransactionState;
import org.apache.rocketmq.client.producer.LocalTransactionState;
import org.apache.rocketmq.client.producer.TransactionListener;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.message.MessageExt;
import org.springframework.messaging.Message;
import org.springframework.stereotype.Component;
/**
* @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
*/
@RocketMQTransactionListener(txProducerGroup = "myTxProducerGroup", corePoolSize = 5,
maximumPoolSize = 10)
public class TransactionListenerImpl implements RocketMQLocalTransactionListener {
@Component("myTransactionListener")
public class TransactionListenerImpl implements TransactionListener {
@Override
public RocketMQLocalTransactionState executeLocalTransaction(Message msg,
Object arg) {
Object num = msg.getHeaders().get("test");
public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
Object num = msg.getProperty("test");
if ("1".equals(num)) {
System.out.println(
"executer: " + new String((byte[]) msg.getPayload()) + " unknown");
return RocketMQLocalTransactionState.UNKNOWN;
System.out.println("executer: " + new String(msg.getBody()) + " unknown");
return LocalTransactionState.UNKNOW;
}
else if ("2".equals(num)) {
System.out.println(
"executer: " + new String((byte[]) msg.getPayload()) + " rollback");
return RocketMQLocalTransactionState.ROLLBACK;
System.out.println("executer: " + new String(msg.getBody()) + " rollback");
return LocalTransactionState.ROLLBACK_MESSAGE;
}
System.out.println(
"executer: " + new String((byte[]) msg.getPayload()) + " commit");
return RocketMQLocalTransactionState.COMMIT;
System.out.println("executer: " + new String(msg.getBody()) + " commit");
return LocalTransactionState.COMMIT_MESSAGE;
}
@Override
public RocketMQLocalTransactionState checkLocalTransaction(Message msg) {
System.out.println("check: " + new String((byte[]) msg.getPayload()));
return RocketMQLocalTransactionState.COMMIT;
public LocalTransactionState checkLocalTransaction(MessageExt msg) {
System.out.println("check: " + new String(msg.getBody()));
return LocalTransactionState.COMMIT_MESSAGE;
}
}

@ -11,6 +11,7 @@ spring.cloud.stream.bindings.output2.destination=TransactionTopic
spring.cloud.stream.bindings.output2.content-type=application/json
spring.cloud.stream.rocketmq.bindings.output2.producer.transactional=true
spring.cloud.stream.rocketmq.bindings.output2.producer.group=myTxProducerGroup
spring.cloud.stream.rocketmq.bindings.output2.producer.transactionListener=myTransactionListener
spring.cloud.stream.bindings.output3.destination=pull-topic
spring.cloud.stream.bindings.output3.content-type=text/plain

@ -50,9 +50,12 @@
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<artifactId>rocketmq-client</artifactId>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-acl</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>

@ -1,51 +0,0 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.stream.binder.rocketmq;
import static org.apache.rocketmq.spring.support.RocketMQHeaders.PREFIX;
/**
* @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
* @author <a href="mailto:jiashuai.xie01@gmail.com">Xiejiashuai</a>
*/
public final class RocketMQBinderConstants {
/**
* Header key for RocketMQ Transactional Args.
*/
public static final String ROCKET_TRANSACTIONAL_ARG = "TRANSACTIONAL_ARG";
/**
* Default NameServer value.
*/
public static final String DEFAULT_NAME_SERVER = "127.0.0.1:9876";
/**
* Default group for SCS RocketMQ Binder.
*/
public static final String DEFAULT_GROUP = PREFIX + "binder_default_group_name";
/**
* RocketMQ re-consume times.
*/
public static final String ROCKETMQ_RECONSUME_TIMES = PREFIX + "RECONSUME_TIMES";
private RocketMQBinderConstants() {
throw new AssertionError("Must not instantiate constant utility class");
}
}

@ -1,89 +0,0 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.stream.binder.rocketmq;
import java.util.Arrays;
import java.util.List;
import com.alibaba.cloud.stream.binder.rocketmq.properties.RocketMQBinderConfigurationProperties;
import org.apache.rocketmq.spring.autoconfigure.RocketMQProperties;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
/**
* @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
*/
public final class RocketMQBinderUtils {
private RocketMQBinderUtils() {
}
public static RocketMQBinderConfigurationProperties mergeProperties(
RocketMQBinderConfigurationProperties rocketBinderConfigurationProperties,
RocketMQProperties rocketMQProperties) {
RocketMQBinderConfigurationProperties result = new RocketMQBinderConfigurationProperties();
if (StringUtils.isEmpty(rocketMQProperties.getNameServer())) {
result.setNameServer(rocketBinderConfigurationProperties.getNameServer());
}
else {
result.setNameServer(
Arrays.asList(rocketMQProperties.getNameServer().split(";")));
}
if (rocketMQProperties.getProducer() == null
|| StringUtils.isEmpty(rocketMQProperties.getProducer().getAccessKey())) {
result.setAccessKey(rocketBinderConfigurationProperties.getAccessKey());
}
else {
result.setAccessKey(rocketMQProperties.getProducer().getAccessKey());
}
if (rocketMQProperties.getProducer() == null
|| StringUtils.isEmpty(rocketMQProperties.getProducer().getSecretKey())) {
result.setSecretKey(rocketBinderConfigurationProperties.getSecretKey());
}
else {
result.setSecretKey(rocketMQProperties.getProducer().getSecretKey());
}
if (rocketMQProperties.getProducer() == null || StringUtils
.isEmpty(rocketMQProperties.getProducer().getCustomizedTraceTopic())) {
result.setCustomizedTraceTopic(
rocketBinderConfigurationProperties.getCustomizedTraceTopic());
}
else {
result.setCustomizedTraceTopic(
rocketMQProperties.getProducer().getCustomizedTraceTopic());
}
if (rocketMQProperties.getProducer() != null
&& rocketMQProperties.getProducer().isEnableMsgTrace()) {
result.setEnableMsgTrace(Boolean.TRUE);
}
else {
result.setEnableMsgTrace(
rocketBinderConfigurationProperties.isEnableMsgTrace());
}
return result;
}
public static String getNameServerStr(List<String> nameServerList) {
if (CollectionUtils.isEmpty(nameServerList)) {
return null;
}
return String.join(";", nameServerList);
}
}

@ -16,34 +16,18 @@
package com.alibaba.cloud.stream.binder.rocketmq;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import com.alibaba.cloud.stream.binder.rocketmq.consuming.RocketMQListenerBindingContainer;
import com.alibaba.cloud.stream.binder.rocketmq.integration.RocketMQInboundChannelAdapter;
import com.alibaba.cloud.stream.binder.rocketmq.integration.RocketMQMessageHandler;
import com.alibaba.cloud.stream.binder.rocketmq.integration.RocketMQMessageSource;
import com.alibaba.cloud.stream.binder.rocketmq.metrics.InstrumentationManager;
import com.alibaba.cloud.stream.binder.rocketmq.custom.RocketMQBeanContainerCache;
import com.alibaba.cloud.stream.binder.rocketmq.extend.ErrorAcknowledgeHandler;
import com.alibaba.cloud.stream.binder.rocketmq.integration.inbound.RocketMQInboundChannelAdapter;
import com.alibaba.cloud.stream.binder.rocketmq.integration.inbound.pull.DefaultErrorAcknowledgeHandler;
import com.alibaba.cloud.stream.binder.rocketmq.integration.inbound.pull.RocketMQMessageSource;
import com.alibaba.cloud.stream.binder.rocketmq.integration.outbound.RocketMQProducerMessageHandler;
import com.alibaba.cloud.stream.binder.rocketmq.properties.RocketMQBinderConfigurationProperties;
import com.alibaba.cloud.stream.binder.rocketmq.properties.RocketMQConsumerProperties;
import com.alibaba.cloud.stream.binder.rocketmq.properties.RocketMQExtendedBindingProperties;
import com.alibaba.cloud.stream.binder.rocketmq.properties.RocketMQProducerProperties;
import com.alibaba.cloud.stream.binder.rocketmq.provisioning.RocketMQTopicProvisioner;
import com.alibaba.cloud.stream.binder.rocketmq.provisioning.selector.PartitionMessageQueueSelector;
import com.alibaba.cloud.stream.binder.rocketmq.support.JacksonRocketMQHeaderMapper;
import com.alibaba.cloud.stream.binder.rocketmq.support.RocketMQHeaderMapper;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.rocketmq.acl.common.AclClientRPCHook;
import org.apache.rocketmq.acl.common.SessionCredentials;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.common.UtilAll;
import org.apache.rocketmq.remoting.RPCHook;
import org.apache.rocketmq.spring.autoconfigure.RocketMQProperties;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.apache.rocketmq.spring.support.RocketMQUtil;
import com.alibaba.cloud.stream.binder.rocketmq.utils.RocketMQUtils;
import org.springframework.cloud.stream.binder.AbstractMessageChannelBinder;
import org.springframework.cloud.stream.binder.BinderSpecificPropertiesProvider;
@ -55,15 +39,19 @@ import org.springframework.cloud.stream.provisioning.ConsumerDestination;
import org.springframework.cloud.stream.provisioning.ProducerDestination;
import org.springframework.integration.StaticMessageHeaderAccessor;
import org.springframework.integration.acks.AcknowledgmentCallback;
import org.springframework.integration.acks.AcknowledgmentCallback.Status;
import org.springframework.integration.channel.AbstractMessageChannel;
import org.springframework.integration.core.MessageProducer;
import org.springframework.integration.support.DefaultErrorMessageStrategy;
import org.springframework.integration.support.ErrorMessageStrategy;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessagingException;
import org.springframework.util.StringUtils;
/**
* A {@link org.springframework.cloud.stream.binder.Binder} that uses RocketMQ as the
* underlying middleware.
*
* @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
*/
public class RocketMQMessageChannelBinder extends
@ -71,120 +59,45 @@ public class RocketMQMessageChannelBinder extends
implements
ExtendedPropertiesBinder<MessageChannel, RocketMQConsumerProperties, RocketMQProducerProperties> {
private RocketMQExtendedBindingProperties extendedBindingProperties = new RocketMQExtendedBindingProperties();
private final RocketMQBinderConfigurationProperties rocketBinderConfigurationProperties;
private final RocketMQProperties rocketMQProperties;
private final InstrumentationManager instrumentationManager;
private final RocketMQExtendedBindingProperties extendedBindingProperties;
private Map<String, String> topicInUse = new HashMap<>();
private final RocketMQBinderConfigurationProperties binderConfigurationProperties;
public RocketMQMessageChannelBinder(RocketMQTopicProvisioner provisioningProvider,
public RocketMQMessageChannelBinder(
RocketMQBinderConfigurationProperties binderConfigurationProperties,
RocketMQExtendedBindingProperties extendedBindingProperties,
RocketMQBinderConfigurationProperties rocketBinderConfigurationProperties,
RocketMQProperties rocketMQProperties,
InstrumentationManager instrumentationManager) {
super(null, provisioningProvider);
RocketMQTopicProvisioner provisioningProvider) {
super(new String[0], provisioningProvider);
this.extendedBindingProperties = extendedBindingProperties;
this.rocketBinderConfigurationProperties = rocketBinderConfigurationProperties;
this.rocketMQProperties = rocketMQProperties;
this.instrumentationManager = instrumentationManager;
this.binderConfigurationProperties = binderConfigurationProperties;
}
@Override
protected MessageHandler createProducerMessageHandler(ProducerDestination destination,
ExtendedProducerProperties<RocketMQProducerProperties> producerProperties,
ExtendedProducerProperties<RocketMQProducerProperties> extendedProducerProperties,
MessageChannel channel, MessageChannel errorChannel) throws Exception {
if (producerProperties.getExtension().getEnabled()) {
// if producerGroup is empty, using destination
String extendedProducerGroup = producerProperties.getExtension().getGroup();
String producerGroup = StringUtils.isEmpty(extendedProducerGroup)
? destination.getName() : extendedProducerGroup;
RocketMQBinderConfigurationProperties mergedProperties = RocketMQBinderUtils
.mergeProperties(rocketBinderConfigurationProperties,
rocketMQProperties);
RocketMQTemplate rocketMQTemplate;
if (producerProperties.getExtension().getTransactional()) {
Map<String, RocketMQTemplate> rocketMQTemplates = getBeanFactory()
.getBeansOfType(RocketMQTemplate.class);
if (rocketMQTemplates.size() == 0) {
throw new IllegalStateException(
"there is no RocketMQTemplate in Spring BeanFactory");
}
else if (rocketMQTemplates.size() > 1) {
throw new IllegalStateException(
"there is more than 1 RocketMQTemplates in Spring BeanFactory");
}
rocketMQTemplate = rocketMQTemplates.values().iterator().next();
}
else {
rocketMQTemplate = new RocketMQTemplate();
rocketMQTemplate.setObjectMapper(this.getApplicationContext()
.getBeansOfType(ObjectMapper.class).values().iterator().next());
DefaultMQProducer producer;
String ak = mergedProperties.getAccessKey();
String sk = mergedProperties.getSecretKey();
if (!StringUtils.isEmpty(ak) && !StringUtils.isEmpty(sk)) {
RPCHook rpcHook = new AclClientRPCHook(
new SessionCredentials(ak, sk));
producer = new DefaultMQProducer(producerGroup, rpcHook,
mergedProperties.isEnableMsgTrace(),
mergedProperties.getCustomizedTraceTopic());
producer.setVipChannelEnabled(false);
producer.setInstanceName(RocketMQUtil.getInstanceName(rpcHook,
destination.getName() + "|" + UtilAll.getPid()));
}
else {
producer = new DefaultMQProducer(producerGroup);
producer.setVipChannelEnabled(
producerProperties.getExtension().getVipChannelEnabled());
}
producer.setNamesrvAddr(RocketMQBinderUtils
.getNameServerStr(mergedProperties.getNameServer()));
producer.setSendMsgTimeout(
producerProperties.getExtension().getSendMessageTimeout());
producer.setRetryTimesWhenSendFailed(
producerProperties.getExtension().getRetryTimesWhenSendFailed());
producer.setRetryTimesWhenSendAsyncFailed(producerProperties
.getExtension().getRetryTimesWhenSendAsyncFailed());
producer.setCompressMsgBodyOverHowmuch(producerProperties.getExtension()
.getCompressMessageBodyThreshold());
producer.setRetryAnotherBrokerWhenNotStoreOK(
producerProperties.getExtension().isRetryNextServer());
producer.setMaxMessageSize(
producerProperties.getExtension().getMaxMessageSize());
rocketMQTemplate.setProducer(producer);
if (producerProperties.isPartitioned()) {
rocketMQTemplate
.setMessageQueueSelector(new PartitionMessageQueueSelector());
}
}
RocketMQMessageHandler messageHandler = new RocketMQMessageHandler(
rocketMQTemplate, destination.getName(), producerGroup,
producerProperties.getExtension().getTransactional(),
instrumentationManager, producerProperties,
((AbstractMessageChannel) channel).getInterceptors().stream().filter(
channelInterceptor -> channelInterceptor instanceof MessageConverterConfigurer.PartitioningInterceptor)
.map(channelInterceptor -> ((MessageConverterConfigurer.PartitioningInterceptor) channelInterceptor))
.findFirst().orElse(null));
messageHandler.setBeanFactory(this.getApplicationContext().getBeanFactory());
messageHandler.setSync(producerProperties.getExtension().getSync());
messageHandler.setHeaderMapper(createHeaderMapper(producerProperties));
if (errorChannel != null) {
messageHandler.setSendFailureChannel(errorChannel);
}
return messageHandler;
}
else {
if (!extendedProducerProperties.getExtension().getEnabled()) {
throw new RuntimeException("Binding for channel " + destination.getName()
+ " has been disabled, message can't be delivered");
}
RocketMQProducerProperties mqProducerProperties = RocketMQUtils
.mergeRocketMQProperties(binderConfigurationProperties,
extendedProducerProperties.getExtension());
RocketMQProducerMessageHandler messageHandler = new RocketMQProducerMessageHandler(
destination, extendedProducerProperties, mqProducerProperties);
messageHandler.setApplicationContext(this.getApplicationContext());
if (errorChannel != null) {
messageHandler.setSendFailureChannel(errorChannel);
}
MessageConverterConfigurer.PartitioningInterceptor partitioningInterceptor = ((AbstractMessageChannel) channel)
.getInterceptors().stream()
.filter(channelInterceptor -> channelInterceptor instanceof MessageConverterConfigurer.PartitioningInterceptor)
.map(channelInterceptor -> ((MessageConverterConfigurer.PartitioningInterceptor) channelInterceptor))
.findFirst().orElse(null);
messageHandler.setPartitioningInterceptor(partitioningInterceptor);
messageHandler.setBeanFactory(this.getApplicationContext().getBeanFactory());
messageHandler.setErrorMessageStrategy(this.getErrorMessageStrategy());
return messageHandler;
}
@Override
@ -198,56 +111,43 @@ public class RocketMQMessageChannelBinder extends
@Override
protected MessageProducer createConsumerEndpoint(ConsumerDestination destination,
String group,
ExtendedConsumerProperties<RocketMQConsumerProperties> consumerProperties)
ExtendedConsumerProperties<RocketMQConsumerProperties> extendedConsumerProperties)
throws Exception {
if (group == null || "".equals(group)) {
// todo support anymous consumer
if (StringUtils.isEmpty(group)) {
throw new RuntimeException(
"'group must be configured for channel " + destination.getName());
}
RocketMQUtils.mergeRocketMQProperties(binderConfigurationProperties,
extendedConsumerProperties.getExtension());
extendedConsumerProperties.getExtension().setGroup(group);
RocketMQListenerBindingContainer listenerContainer = new RocketMQListenerBindingContainer(
consumerProperties, rocketBinderConfigurationProperties, this);
listenerContainer.setConsumerGroup(group);
listenerContainer.setTopic(destination.getName());
listenerContainer.setConsumeThreadMax(consumerProperties.getConcurrency());
listenerContainer.setSuspendCurrentQueueTimeMillis(
consumerProperties.getExtension().getSuspendCurrentQueueTimeMillis());
listenerContainer.setDelayLevelWhenNextConsume(
consumerProperties.getExtension().getDelayLevelWhenNextConsume());
listenerContainer
.setNameServer(rocketBinderConfigurationProperties.getNameServer());
listenerContainer.setHeaderMapper(createHeaderMapper(consumerProperties));
RocketMQInboundChannelAdapter rocketInboundChannelAdapter = new RocketMQInboundChannelAdapter(
listenerContainer, consumerProperties, instrumentationManager);
topicInUse.put(destination.getName(), group);
RocketMQInboundChannelAdapter inboundChannelAdapter = new RocketMQInboundChannelAdapter(
destination.getName(), extendedConsumerProperties);
ErrorInfrastructure errorInfrastructure = registerErrorInfrastructure(destination,
group, consumerProperties);
if (consumerProperties.getMaxAttempts() > 1) {
rocketInboundChannelAdapter
.setRetryTemplate(buildRetryTemplate(consumerProperties));
rocketInboundChannelAdapter
.setRecoveryCallback(errorInfrastructure.getRecoverer());
group, extendedConsumerProperties);
if (extendedConsumerProperties.getMaxAttempts() > 1) {
inboundChannelAdapter
.setRetryTemplate(buildRetryTemplate(extendedConsumerProperties));
inboundChannelAdapter.setRecoveryCallback(errorInfrastructure.getRecoverer());
}
else {
rocketInboundChannelAdapter
.setErrorChannel(errorInfrastructure.getErrorChannel());
inboundChannelAdapter.setErrorChannel(errorInfrastructure.getErrorChannel());
}
return rocketInboundChannelAdapter;
return inboundChannelAdapter;
}
@Override
protected PolledConsumerResources createPolledConsumerResources(String name,
String group, ConsumerDestination destination,
ExtendedConsumerProperties<RocketMQConsumerProperties> consumerProperties) {
RocketMQMessageSource rocketMQMessageSource = new RocketMQMessageSource(
rocketBinderConfigurationProperties, consumerProperties, name, group);
return new PolledConsumerResources(rocketMQMessageSource,
registerErrorInfrastructure(destination, group, consumerProperties,
true));
ExtendedConsumerProperties<RocketMQConsumerProperties> extendedConsumerProperties) {
RocketMQUtils.mergeRocketMQProperties(binderConfigurationProperties,
extendedConsumerProperties.getExtension());
extendedConsumerProperties.getExtension().setGroup(group);
RocketMQMessageSource messageSource = new RocketMQMessageSource(name,
extendedConsumerProperties);
return new PolledConsumerResources(messageSource, registerErrorInfrastructure(
destination, group, extendedConsumerProperties, true));
}
@Override
@ -261,67 +161,47 @@ public class RocketMQMessageChannelBinder extends
((MessagingException) message.getPayload())
.getFailedMessage());
if (ack != null) {
if (properties.getExtension().shouldRequeue()) {
ack.acknowledge(Status.REQUEUE);
}
else {
ack.acknowledge(Status.REJECT);
}
ErrorAcknowledgeHandler handler = RocketMQBeanContainerCache.getBean(
properties.getExtension().getPull().getErrAcknowledge(),
ErrorAcknowledgeHandler.class,
new DefaultErrorAcknowledgeHandler());
ack.acknowledge(
handler.handler(((MessagingException) message.getPayload())
.getFailedMessage()));
}
}
};
}
/**
* Binders can return an {@link ErrorMessageStrategy} for building error messages;
* binder implementations typically might add extra headers to the error message.
* @return the implementation - may be null.
*/
@Override
public RocketMQConsumerProperties getExtendedConsumerProperties(String channelName) {
return extendedBindingProperties.getExtendedConsumerProperties(channelName);
protected ErrorMessageStrategy getErrorMessageStrategy() {
// It can be extended to custom if necessary.
return new DefaultErrorMessageStrategy();
}
@Override
public RocketMQProducerProperties getExtendedProducerProperties(String channelName) {
return extendedBindingProperties.getExtendedProducerProperties(channelName);
public RocketMQConsumerProperties getExtendedConsumerProperties(String channelName) {
return this.extendedBindingProperties.getExtendedConsumerProperties(channelName);
}
public Map<String, String> getTopicInUse() {
return topicInUse;
@Override
public RocketMQProducerProperties getExtendedProducerProperties(String channelName) {
return this.extendedBindingProperties.getExtendedProducerProperties(channelName);
}
@Override
public String getDefaultsPrefix() {
return extendedBindingProperties.getDefaultsPrefix();
return this.extendedBindingProperties.getDefaultsPrefix();
}
@Override
public Class<? extends BinderSpecificPropertiesProvider> getExtendedPropertiesEntryClass() {
return extendedBindingProperties.getExtendedPropertiesEntryClass();
}
public void setExtendedBindingProperties(
RocketMQExtendedBindingProperties extendedBindingProperties) {
this.extendedBindingProperties = extendedBindingProperties;
}
private RocketMQHeaderMapper createHeaderMapper(
final ExtendedConsumerProperties<RocketMQConsumerProperties> extendedConsumerProperties) {
Set<String> trustedPackages = extendedConsumerProperties.getExtension()
.getTrustedPackages();
return createHeaderMapper(trustedPackages);
}
private RocketMQHeaderMapper createHeaderMapper(
final ExtendedProducerProperties<RocketMQProducerProperties> producerProperties) {
return createHeaderMapper(Collections.emptyList());
}
private RocketMQHeaderMapper createHeaderMapper(Collection<String> trustedPackages) {
ObjectMapper objectMapper = this.getApplicationContext()
.getBeansOfType(ObjectMapper.class).values().iterator().next();
JacksonRocketMQHeaderMapper headerMapper = new JacksonRocketMQHeaderMapper(
objectMapper);
if (!StringUtils.isEmpty(trustedPackages)) {
headerMapper.addTrustedPackages(trustedPackages);
}
return headerMapper;
return this.extendedBindingProperties.getExtendedPropertiesEntryClass();
}
}

@ -19,7 +19,6 @@ package com.alibaba.cloud.stream.binder.rocketmq.actuator;
import com.alibaba.cloud.stream.binder.rocketmq.metrics.Instrumentation;
import com.alibaba.cloud.stream.binder.rocketmq.metrics.InstrumentationManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
import org.springframework.boot.actuate.health.Health;
@ -29,23 +28,20 @@ import org.springframework.boot.actuate.health.Health;
*/
public class RocketMQBinderHealthIndicator extends AbstractHealthIndicator {
@Autowired
private InstrumentationManager instrumentationManager;
@Override
protected void doHealthCheck(Health.Builder builder) throws Exception {
if (instrumentationManager.getHealthInstrumentations().stream()
if (InstrumentationManager.getHealthInstrumentations().stream()
.allMatch(Instrumentation::isUp)) {
builder.up();
return;
}
if (instrumentationManager.getHealthInstrumentations().stream()
if (InstrumentationManager.getHealthInstrumentations().stream()
.allMatch(Instrumentation::isOutOfService)) {
builder.outOfService();
return;
}
builder.down();
instrumentationManager.getHealthInstrumentations().stream()
InstrumentationManager.getHealthInstrumentations().stream()
.filter(instrumentation -> !instrumentation.isStarted())
.forEach(instrumentation1 -> builder
.withException(instrumentation1.getStartException()));

@ -0,0 +1,45 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.stream.binder.rocketmq.autoconfigurate;
import java.util.HashMap;
import java.util.Map;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.cloud.stream.config.BindingHandlerAdvise.MappingsProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ExtendedBindingHandlerMappingsProviderConfiguration {
@Bean
public MappingsProvider rocketExtendedPropertiesDefaultMappingsProvider() {
return () -> {
Map<ConfigurationPropertyName, ConfigurationPropertyName> mappings = new HashMap<>();
mappings.put(
ConfigurationPropertyName.of("spring.cloud.stream.rocketmq.bindings"),
ConfigurationPropertyName.of("spring.cloud.stream.rocketmq.default"));
mappings.put(
ConfigurationPropertyName.of("spring.cloud.stream.rocketmq.streams"),
ConfigurationPropertyName
.of("spring.cloud.stream.rocketmq.streams.default"));
return mappings;
};
}
}

@ -0,0 +1,83 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.stream.binder.rocketmq.autoconfigurate;
import com.alibaba.cloud.stream.binder.rocketmq.RocketMQMessageChannelBinder;
import com.alibaba.cloud.stream.binder.rocketmq.actuator.RocketMQBinderHealthIndicator;
import com.alibaba.cloud.stream.binder.rocketmq.convert.RocketMQMessageConverter;
import com.alibaba.cloud.stream.binder.rocketmq.custom.RocketMQConfigBeanPostProcessor;
import com.alibaba.cloud.stream.binder.rocketmq.properties.RocketMQBinderConfigurationProperties;
import com.alibaba.cloud.stream.binder.rocketmq.properties.RocketMQExtendedBindingProperties;
import com.alibaba.cloud.stream.binder.rocketmq.provisioning.RocketMQTopicProvisioner;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.converter.CompositeMessageConverter;
/**
* issue:https://github.com/alibaba/spring-cloud-alibaba/issues/1681 .
*
* @author Timur Valiev
* @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
*/
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties({ RocketMQExtendedBindingProperties.class,
RocketMQBinderConfigurationProperties.class })
public class RocketMQBinderAutoConfiguration {
@Autowired
private RocketMQExtendedBindingProperties extendedBindingProperties;
@Autowired
private RocketMQBinderConfigurationProperties rocketBinderConfigurationProperties;
@Bean
public RocketMQConfigBeanPostProcessor rocketMQConfigBeanPostProcessor() {
return new RocketMQConfigBeanPostProcessor();
}
@Bean(RocketMQMessageConverter.DEFAULT_NAME)
@ConditionalOnMissingBean(name = { RocketMQMessageConverter.DEFAULT_NAME })
public CompositeMessageConverter rocketMQMessageConverter() {
return new RocketMQMessageConverter().getMessageConverter();
}
@Bean
@ConditionalOnEnabledHealthIndicator("rocketmq")
@ConditionalOnClass(name = "org.springframework.boot.actuate.health.HealthIndicator")
public RocketMQBinderHealthIndicator rocketMQBinderHealthIndicator() {
return new RocketMQBinderHealthIndicator();
}
@Bean
public RocketMQTopicProvisioner rocketMQTopicProvisioner() {
return new RocketMQTopicProvisioner();
}
@Bean
public RocketMQMessageChannelBinder rocketMQMessageChannelBinder(
RocketMQTopicProvisioner provisioningProvider) {
return new RocketMQMessageChannelBinder(rocketBinderConfigurationProperties,
extendedBindingProperties, provisioningProvider);
}
}

@ -1,81 +0,0 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.stream.binder.rocketmq.config;
import com.alibaba.cloud.stream.binder.rocketmq.RocketMQMessageChannelBinder;
import com.alibaba.cloud.stream.binder.rocketmq.metrics.InstrumentationManager;
import com.alibaba.cloud.stream.binder.rocketmq.properties.RocketMQBinderConfigurationProperties;
import com.alibaba.cloud.stream.binder.rocketmq.properties.RocketMQExtendedBindingProperties;
import com.alibaba.cloud.stream.binder.rocketmq.provisioning.RocketMQTopicProvisioner;
import org.apache.rocketmq.spring.autoconfigure.RocketMQAutoConfiguration;
import org.apache.rocketmq.spring.autoconfigure.RocketMQProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/**
* @author Timur Valiev
* @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
*/
@Configuration(proxyBeanMethods = false)
@Import({ RocketMQAutoConfiguration.class,
RocketMQBinderHealthIndicatorAutoConfiguration.class })
@EnableConfigurationProperties({ RocketMQBinderConfigurationProperties.class,
RocketMQExtendedBindingProperties.class })
public class RocketMQBinderAutoConfiguration {
private final RocketMQExtendedBindingProperties extendedBindingProperties;
private final RocketMQBinderConfigurationProperties rocketBinderConfigurationProperties;
@Autowired(required = false)
private RocketMQProperties rocketMQProperties = new RocketMQProperties();
@Autowired
public RocketMQBinderAutoConfiguration(
RocketMQExtendedBindingProperties extendedBindingProperties,
RocketMQBinderConfigurationProperties rocketBinderConfigurationProperties) {
this.extendedBindingProperties = extendedBindingProperties;
this.rocketBinderConfigurationProperties = rocketBinderConfigurationProperties;
}
@Bean
public RocketMQTopicProvisioner provisioningProvider() {
return new RocketMQTopicProvisioner();
}
@Bean
public RocketMQMessageChannelBinder rocketMessageChannelBinder(
RocketMQTopicProvisioner provisioningProvider,
InstrumentationManager instrumentationManager) {
RocketMQMessageChannelBinder binder = new RocketMQMessageChannelBinder(
provisioningProvider, extendedBindingProperties,
rocketBinderConfigurationProperties, rocketMQProperties,
instrumentationManager);
binder.setExtendedBindingProperties(extendedBindingProperties);
return binder;
}
@Bean
public InstrumentationManager instrumentationManager() {
return new InstrumentationManager();
}
}

@ -1,40 +0,0 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.stream.binder.rocketmq.config;
import com.alibaba.cloud.stream.binder.rocketmq.actuator.RocketMQBinderHealthIndicator;
import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Endpoint.class)
public class RocketMQBinderHealthIndicatorAutoConfiguration {
@Bean
@ConditionalOnEnabledHealthIndicator("rocketmq")
public RocketMQBinderHealthIndicator rocketBinderHealthIndicator() {
return new RocketMQBinderHealthIndicator();
}
}

@ -1,102 +0,0 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.stream.binder.rocketmq.config;
import com.alibaba.cloud.stream.binder.rocketmq.RocketMQBinderConstants;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.rocketmq.acl.common.AclClientRPCHook;
import org.apache.rocketmq.acl.common.SessionCredentials;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.spring.autoconfigure.RocketMQAutoConfiguration;
import org.apache.rocketmq.spring.config.RocketMQConfigUtils;
import org.apache.rocketmq.spring.config.RocketMQTransactionAnnotationProcessor;
import org.apache.rocketmq.spring.config.TransactionHandlerRegistry;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.util.StringUtils;
/**
* @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
*/
@Configuration(proxyBeanMethods = false)
@AutoConfigureAfter(RocketMQAutoConfiguration.class)
@ConditionalOnMissingBean(DefaultMQProducer.class)
public class RocketMQComponent4BinderAutoConfiguration {
private final Environment environment;
public RocketMQComponent4BinderAutoConfiguration(Environment environment) {
this.environment = environment;
}
@Bean
@ConditionalOnMissingBean(DefaultMQProducer.class)
public DefaultMQProducer defaultMQProducer() {
DefaultMQProducer producer;
String configNameServer = environment.resolveRequiredPlaceholders(
"${spring.cloud.stream.rocketmq.binder.name-server:${rocketmq.producer.name-server:}}");
String ak = environment.resolveRequiredPlaceholders(
"${spring.cloud.stream.rocketmq.binder.access-key:${rocketmq.producer.access-key:}}");
String sk = environment.resolveRequiredPlaceholders(
"${spring.cloud.stream.rocketmq.binder.secret-key:${rocketmq.producer.secret-key:}}");
if (!StringUtils.isEmpty(ak) && !StringUtils.isEmpty(sk)) {
producer = new DefaultMQProducer(RocketMQBinderConstants.DEFAULT_GROUP,
new AclClientRPCHook(new SessionCredentials(ak, sk)));
producer.setVipChannelEnabled(false);
}
else {
producer = new DefaultMQProducer(RocketMQBinderConstants.DEFAULT_GROUP);
}
if (StringUtils.isEmpty(configNameServer)) {
configNameServer = RocketMQBinderConstants.DEFAULT_NAME_SERVER;
}
producer.setNamesrvAddr(configNameServer);
return producer;
}
@Bean(destroyMethod = "destroy")
@ConditionalOnMissingBean
public RocketMQTemplate rocketMQTemplate(DefaultMQProducer mqProducer,
ObjectMapper objectMapper) {
RocketMQTemplate rocketMQTemplate = new RocketMQTemplate();
rocketMQTemplate.setProducer(mqProducer);
rocketMQTemplate.setObjectMapper(objectMapper);
return rocketMQTemplate;
}
@Bean
@ConditionalOnBean(RocketMQTemplate.class)
@ConditionalOnMissingBean(TransactionHandlerRegistry.class)
public TransactionHandlerRegistry transactionHandlerRegistry(
RocketMQTemplate template) {
return new TransactionHandlerRegistry(template);
}
@Bean(name = RocketMQConfigUtils.ROCKETMQ_TRANSACTION_ANNOTATION_PROCESSOR_BEAN_NAME)
@ConditionalOnBean(TransactionHandlerRegistry.class)
public static RocketMQTransactionAnnotationProcessor transactionAnnotationProcessor(
TransactionHandlerRegistry transactionHandlerRegistry) {
return new RocketMQTransactionAnnotationProcessor(transactionHandlerRegistry);
}
}

@ -1,465 +0,0 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.stream.binder.rocketmq.consuming;
import java.util.List;
import java.util.Objects;
import com.alibaba.cloud.stream.binder.rocketmq.RocketMQBinderUtils;
import com.alibaba.cloud.stream.binder.rocketmq.RocketMQMessageChannelBinder;
import com.alibaba.cloud.stream.binder.rocketmq.properties.RocketMQBinderConfigurationProperties;
import com.alibaba.cloud.stream.binder.rocketmq.properties.RocketMQConsumerProperties;
import com.alibaba.cloud.stream.binder.rocketmq.support.RocketMQHeaderMapper;
import org.apache.rocketmq.acl.common.AclClientRPCHook;
import org.apache.rocketmq.acl.common.SessionCredentials;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.MessageSelector;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.client.consumer.listener.MessageListenerOrderly;
import org.apache.rocketmq.client.consumer.rebalance.AllocateMessageQueueAveragely;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.common.UtilAll;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.remoting.RPCHook;
import org.apache.rocketmq.spring.annotation.ConsumeMode;
import org.apache.rocketmq.spring.annotation.MessageModel;
import org.apache.rocketmq.spring.annotation.SelectorType;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.apache.rocketmq.spring.core.RocketMQPushConsumerLifecycleListener;
import org.apache.rocketmq.spring.support.RocketMQListenerContainer;
import org.apache.rocketmq.spring.support.RocketMQUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.cloud.stream.binder.ExtendedConsumerProperties;
import org.springframework.context.SmartLifecycle;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.Message;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import static com.alibaba.cloud.stream.binder.rocketmq.RocketMQBinderConstants.ROCKETMQ_RECONSUME_TIMES;
/**
* A class that Listen on rocketmq message.
* <p>
* this class will delegate {@link RocketMQListener} to handle message
*
* @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
* @author <a href="mailto:jiashuai.xie01@gmail.com">Xiejiashuai</a>
* @see RocketMQListener
*/
public class RocketMQListenerBindingContainer
implements InitializingBean, RocketMQListenerContainer, SmartLifecycle {
private final static Logger log = LoggerFactory
.getLogger(RocketMQListenerBindingContainer.class);
private long suspendCurrentQueueTimeMillis = 1000;
/**
* Message consume retry strategy<br>
* -1,no retry,put into DLQ directly<br>
* 0,broker control retry frequency<br>
* >0,client control retry frequency.
*/
private int delayLevelWhenNextConsume = 0;
private List<String> nameServer;
private String consumerGroup;
private String topic;
private int consumeThreadMax = 64;
private String charset = "UTF-8";
private RocketMQListener rocketMQListener;
private RocketMQHeaderMapper headerMapper;
private DefaultMQPushConsumer consumer;
private boolean running;
private final ExtendedConsumerProperties<RocketMQConsumerProperties> rocketMQConsumerProperties;
private final RocketMQMessageChannelBinder rocketMQMessageChannelBinder;
private final RocketMQBinderConfigurationProperties rocketBinderConfigurationProperties;
// The following properties came from RocketMQConsumerProperties.
private ConsumeMode consumeMode;
private SelectorType selectorType;
private String selectorExpression;
private MessageModel messageModel;
public RocketMQListenerBindingContainer(
ExtendedConsumerProperties<RocketMQConsumerProperties> rocketMQConsumerProperties,
RocketMQBinderConfigurationProperties rocketBinderConfigurationProperties,
RocketMQMessageChannelBinder rocketMQMessageChannelBinder) {
this.rocketMQConsumerProperties = rocketMQConsumerProperties;
this.rocketBinderConfigurationProperties = rocketBinderConfigurationProperties;
this.rocketMQMessageChannelBinder = rocketMQMessageChannelBinder;
this.consumeMode = rocketMQConsumerProperties.getExtension().getOrderly()
? ConsumeMode.ORDERLY : ConsumeMode.CONCURRENTLY;
if (StringUtils.isEmpty(rocketMQConsumerProperties.getExtension().getSql())) {
this.selectorType = SelectorType.TAG;
this.selectorExpression = rocketMQConsumerProperties.getExtension().getTags();
}
else {
this.selectorType = SelectorType.SQL92;
this.selectorExpression = rocketMQConsumerProperties.getExtension().getSql();
}
this.messageModel = rocketMQConsumerProperties.getExtension().getBroadcasting()
? MessageModel.BROADCASTING : MessageModel.CLUSTERING;
}
@Override
public void setupMessageListener(RocketMQListener<?> rocketMQListener) {
this.rocketMQListener = rocketMQListener;
}
@Override
public void destroy() throws Exception {
this.setRunning(false);
if (Objects.nonNull(consumer)) {
consumer.shutdown();
}
log.info("container destroyed, {}", this.toString());
}
@Override
public void afterPropertiesSet() throws Exception {
initRocketMQPushConsumer();
}
@Override
public boolean isAutoStartup() {
return true;
}
@Override
public void stop(Runnable callback) {
stop();
callback.run();
}
@Override
public void start() {
if (this.isRunning()) {
throw new IllegalStateException(
"container already running. " + this.toString());
}
try {
consumer.start();
}
catch (MQClientException e) {
throw new IllegalStateException("Failed to start RocketMQ push consumer", e);
}
this.setRunning(true);
log.info("running container: {}", this.toString());
}
@Override
public void stop() {
if (this.isRunning()) {
if (Objects.nonNull(consumer)) {
consumer.shutdown();
}
setRunning(false);
}
}
@Override
public boolean isRunning() {
return running;
}
private void setRunning(boolean running) {
this.running = running;
}
@Override
public int getPhase() {
return Integer.MAX_VALUE;
}
private void initRocketMQPushConsumer() throws MQClientException {
Assert.notNull(rocketMQListener, "Property 'rocketMQListener' is required");
Assert.notNull(consumerGroup, "Property 'consumerGroup' is required");
Assert.notNull(nameServer, "Property 'nameServer' is required");
Assert.notNull(topic, "Property 'topic' is required");
String ak = rocketBinderConfigurationProperties.getAccessKey();
String sk = rocketBinderConfigurationProperties.getSecretKey();
if (!StringUtils.isEmpty(ak) && !StringUtils.isEmpty(sk)) {
RPCHook rpcHook = new AclClientRPCHook(new SessionCredentials(ak, sk));
consumer = new DefaultMQPushConsumer(consumerGroup, rpcHook,
new AllocateMessageQueueAveragely(),
rocketBinderConfigurationProperties.isEnableMsgTrace(),
rocketBinderConfigurationProperties.getCustomizedTraceTopic());
consumer.setInstanceName(RocketMQUtil.getInstanceName(rpcHook,
topic + "|" + UtilAll.getPid()));
consumer.setVipChannelEnabled(false);
}
else {
consumer = new DefaultMQPushConsumer(consumerGroup,
rocketBinderConfigurationProperties.isEnableMsgTrace(),
rocketBinderConfigurationProperties.getCustomizedTraceTopic());
}
consumer.setNamesrvAddr(RocketMQBinderUtils.getNameServerStr(nameServer));
consumer.setConsumeThreadMax(rocketMQConsumerProperties.getConcurrency());
consumer.setConsumeThreadMin(rocketMQConsumerProperties.getConcurrency());
switch (messageModel) {
case BROADCASTING:
consumer.setMessageModel(
org.apache.rocketmq.common.protocol.heartbeat.MessageModel.BROADCASTING);
break;
case CLUSTERING:
consumer.setMessageModel(
org.apache.rocketmq.common.protocol.heartbeat.MessageModel.CLUSTERING);
break;
default:
throw new IllegalArgumentException("Property 'messageModel' was wrong.");
}
switch (selectorType) {
case TAG:
consumer.subscribe(topic, selectorExpression);
break;
case SQL92:
consumer.subscribe(topic, MessageSelector.bySql(selectorExpression));
break;
default:
throw new IllegalArgumentException("Property 'selectorType' was wrong.");
}
switch (consumeMode) {
case ORDERLY:
consumer.setMessageListener(new DefaultMessageListenerOrderly());
break;
case CONCURRENTLY:
consumer.setMessageListener(new DefaultMessageListenerConcurrently());
break;
default:
throw new IllegalArgumentException("Property 'consumeMode' was wrong.");
}
if (rocketMQListener instanceof RocketMQPushConsumerLifecycleListener) {
((RocketMQPushConsumerLifecycleListener) rocketMQListener)
.prepareStart(consumer);
}
}
@Override
public String toString() {
return "RocketMQListenerBindingContainer{" + "consumerGroup='" + consumerGroup
+ '\'' + ", nameServer='" + nameServer + '\'' + ", topic='" + topic + '\''
+ ", consumeMode=" + consumeMode + ", selectorType=" + selectorType
+ ", selectorExpression='" + selectorExpression + '\'' + ", messageModel="
+ messageModel + '}';
}
public long getSuspendCurrentQueueTimeMillis() {
return suspendCurrentQueueTimeMillis;
}
public void setSuspendCurrentQueueTimeMillis(long suspendCurrentQueueTimeMillis) {
this.suspendCurrentQueueTimeMillis = suspendCurrentQueueTimeMillis;
}
public int getDelayLevelWhenNextConsume() {
return delayLevelWhenNextConsume;
}
public void setDelayLevelWhenNextConsume(int delayLevelWhenNextConsume) {
this.delayLevelWhenNextConsume = delayLevelWhenNextConsume;
}
public List<String> getNameServer() {
return nameServer;
}
public void setNameServer(List<String> nameServer) {
this.nameServer = nameServer;
}
public String getConsumerGroup() {
return consumerGroup;
}
public void setConsumerGroup(String consumerGroup) {
this.consumerGroup = consumerGroup;
}
public String getTopic() {
return topic;
}
public void setTopic(String topic) {
this.topic = topic;
}
public int getConsumeThreadMax() {
return consumeThreadMax;
}
public void setConsumeThreadMax(int consumeThreadMax) {
this.consumeThreadMax = consumeThreadMax;
}
public String getCharset() {
return charset;
}
public void setCharset(String charset) {
this.charset = charset;
}
public RocketMQListener getRocketMQListener() {
return rocketMQListener;
}
public void setRocketMQListener(RocketMQListener rocketMQListener) {
this.rocketMQListener = rocketMQListener;
}
public DefaultMQPushConsumer getConsumer() {
return consumer;
}
public void setConsumer(DefaultMQPushConsumer consumer) {
this.consumer = consumer;
}
public ExtendedConsumerProperties<RocketMQConsumerProperties> getRocketMQConsumerProperties() {
return rocketMQConsumerProperties;
}
public ConsumeMode getConsumeMode() {
return consumeMode;
}
public SelectorType getSelectorType() {
return selectorType;
}
public String getSelectorExpression() {
return selectorExpression;
}
public MessageModel getMessageModel() {
return messageModel;
}
public RocketMQHeaderMapper getHeaderMapper() {
return headerMapper;
}
public void setHeaderMapper(RocketMQHeaderMapper headerMapper) {
this.headerMapper = headerMapper;
}
/**
* Convert rocketmq {@link MessageExt} to Spring {@link Message}.
* @param messageExt the rocketmq message
* @return the converted Spring {@link Message}
*/
@SuppressWarnings("unchecked")
private Message convertToSpringMessage(MessageExt messageExt) {
// add reconsume-times header to messageExt
int reconsumeTimes = messageExt.getReconsumeTimes();
messageExt.putUserProperty(ROCKETMQ_RECONSUME_TIMES,
String.valueOf(reconsumeTimes));
Message message = RocketMQUtil.convertToSpringMessage(messageExt);
return MessageBuilder.fromMessage(message)
.copyHeaders(headerMapper.toHeaders(messageExt.getProperties())).build();
}
public class DefaultMessageListenerConcurrently
implements MessageListenerConcurrently {
@SuppressWarnings({ "unchecked", "Duplicates" })
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
ConsumeConcurrentlyContext context) {
for (MessageExt messageExt : msgs) {
log.debug("received msg: {}", messageExt);
try {
long now = System.currentTimeMillis();
rocketMQListener.onMessage(convertToSpringMessage(messageExt));
long costTime = System.currentTimeMillis() - now;
log.debug("consume {} message key:[{}] cost: {} ms",
messageExt.getMsgId(), messageExt.getKeys(), costTime);
}
catch (Exception e) {
log.warn("consume message failed. messageExt:{}", messageExt, e);
context.setDelayLevelWhenNextConsume(delayLevelWhenNextConsume);
return ConsumeConcurrentlyStatus.RECONSUME_LATER;
}
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
}
public class DefaultMessageListenerOrderly implements MessageListenerOrderly {
@SuppressWarnings({ "unchecked", "Duplicates" })
@Override
public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs,
ConsumeOrderlyContext context) {
for (MessageExt messageExt : msgs) {
log.debug("received msg: {}", messageExt);
try {
long now = System.currentTimeMillis();
rocketMQListener.onMessage(convertToSpringMessage(messageExt));
long costTime = System.currentTimeMillis() - now;
log.info("consume {} message key:[{}] cost: {} ms",
messageExt.getMsgId(), messageExt.getKeys(), costTime);
}
catch (Exception e) {
log.warn("consume message failed. messageExt:{}", messageExt, e);
context.setSuspendCurrentQueueTimeMillis(
suspendCurrentQueueTimeMillis);
return ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT;
}
}
return ConsumeOrderlyStatus.SUCCESS;
}
}
}

@ -1,62 +0,0 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.stream.binder.rocketmq.consuming;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import org.apache.rocketmq.common.message.MessageQueue;
/**
* @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
*/
public class RocketMQMessageQueueChooser {
private volatile int queueIndex = 0;
private volatile List<MessageQueue> messageQueues;
public MessageQueue choose() {
return messageQueues.get(queueIndex);
}
public int requeue() {
if (queueIndex - 1 < 0) {
this.queueIndex = messageQueues.size() - 1;
}
else {
this.queueIndex = this.queueIndex - 1;
}
return this.queueIndex;
}
public void increment() {
this.queueIndex = (this.queueIndex + 1) % messageQueues.size();
}
public void reset(Set<MessageQueue> queueSet) {
this.messageQueues = null;
this.messageQueues = new ArrayList<>(queueSet);
this.queueIndex = 0;
}
public List<MessageQueue> getMessageQueues() {
return messageQueues;
}
}

@ -0,0 +1,100 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.stream.binder.rocketmq.contants;
import org.apache.rocketmq.common.message.MessageConst;
/**
* @author zkzlx
*/
public class RocketMQConst extends MessageConst {
/**
* Default NameServer value.
*/
public static final String DEFAULT_NAME_SERVER = "127.0.0.1:9876";
/**
* Default group for SCS RocketMQ Binder.
*/
public static final String DEFAULT_GROUP = "binder_default_group_name";
/**
* user args for SCS RocketMQ Binder.
*/
public static final String USER_TRANSACTIONAL_ARGS = "TRANSACTIONAL_ARGS";
/**
* It is mainly provided for conversion between rocketMq-message and Spring-message,
* and parameters are passed through HEADERS.
*/
public static class Headers {
/**
* keys for SCS RocketMQ Headers.
*/
public static final String KEYS = MessageConst.PROPERTY_KEYS;
/**
* tags for SCS RocketMQ Headers.
*/
public static final String TAGS = MessageConst.PROPERTY_TAGS;
/**
* topic for SCS RocketMQ Headers.
*/
public static final String TOPIC = "MQ_TOPIC";
/**
* The ID of the message.
*/
public static final String MESSAGE_ID = "MQ_MESSAGE_ID";
/**
* The timestamp that the message producer invokes the message sending API.
*/
public static final String BORN_TIMESTAMP = "MQ_BORN_TIMESTAMP";
/**
* The IP and port number of the message producer.
*/
public static final String BORN_HOST = "MQ_BORN_HOST";
/**
* Message flag, MQ is not processed and is available for use by applications.
*/
public static final String FLAG = "MQ_FLAG";
/**
* Message consumption queue ID.
*/
public static final String QUEUE_ID = "MQ_QUEUE_ID";
/**
* Message system Flag, such as whether or not to compress, whether or not to
* transactional messages.
*/
public static final String SYS_FLAG = "MQ_SYS_FLAG";
/**
* The transaction ID of the transaction message.
*/
public static final String TRANSACTION_ID = "MQ_TRANSACTION_ID";
}
}

@ -0,0 +1,90 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.stream.binder.rocketmq.convert;
import java.util.ArrayList;
import java.util.List;
import org.springframework.messaging.converter.ByteArrayMessageConverter;
import org.springframework.messaging.converter.CompositeMessageConverter;
import org.springframework.messaging.converter.MappingJackson2MessageConverter;
import org.springframework.messaging.converter.MessageConverter;
import org.springframework.messaging.converter.StringMessageConverter;
import org.springframework.util.ClassUtils;
/**
* The default message converter of rocketMq,its bean name is {@link #DEFAULT_NAME} .
*
* @author zkzlx
*/
public class RocketMQMessageConverter {
/**
* rocketMQMessageConverter.
*/
public static final String DEFAULT_NAME = "rocketMQMessageConverter";
private static final boolean JACKSON_PRESENT;
private static final boolean FASTJSON_PRESENT;
static {
ClassLoader classLoader = RocketMQMessageConverter.class.getClassLoader();
JACKSON_PRESENT = ClassUtils
.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader)
&& ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator",
classLoader);
FASTJSON_PRESENT = ClassUtils.isPresent("com.alibaba.fastjson.JSON", classLoader)
&& ClassUtils.isPresent(
"com.alibaba.fastjson.support.config.FastJsonConfig",
classLoader);
}
private CompositeMessageConverter messageConverter;
public RocketMQMessageConverter() {
List<MessageConverter> messageConverters = new ArrayList<>();
ByteArrayMessageConverter byteArrayMessageConverter = new ByteArrayMessageConverter();
byteArrayMessageConverter.setContentTypeResolver(null);
messageConverters.add(byteArrayMessageConverter);
messageConverters.add(new StringMessageConverter());
if (JACKSON_PRESENT) {
messageConverters.add(new MappingJackson2MessageConverter());
}
if (FASTJSON_PRESENT) {
try {
messageConverters.add((MessageConverter) ClassUtils.forName(
"com.alibaba.fastjson.support.spring.messaging.MappingFastJsonMessageConverter",
ClassUtils.getDefaultClassLoader()).newInstance());
}
catch (ClassNotFoundException | IllegalAccessException
| InstantiationException ignored) {
// ignore this exception
}
}
messageConverter = new CompositeMessageConverter(messageConverters);
}
public CompositeMessageConverter getMessageConverter() {
return messageConverter;
}
public void setMessageConverter(CompositeMessageConverter messageConverter) {
this.messageConverter = messageConverter;
}
}

@ -0,0 +1,78 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.stream.binder.rocketmq.custom;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import com.alibaba.cloud.stream.binder.rocketmq.extend.ErrorAcknowledgeHandler;
import org.apache.rocketmq.client.consumer.AllocateMessageQueueStrategy;
import org.apache.rocketmq.client.consumer.listener.MessageListener;
import org.apache.rocketmq.client.hook.CheckForbiddenHook;
import org.apache.rocketmq.client.hook.SendMessageHook;
import org.apache.rocketmq.client.producer.MessageQueueSelector;
import org.apache.rocketmq.client.producer.SendCallback;
import org.apache.rocketmq.client.producer.TransactionListener;
import org.springframework.messaging.converter.CompositeMessageConverter;
import org.springframework.util.StringUtils;
/**
* Gets the beans configured in the configuration file.
*
* @author junboXiang
*/
public final class RocketMQBeanContainerCache {
private RocketMQBeanContainerCache() {
}
private static final Class<?>[] CLASSES = new Class[] {
CompositeMessageConverter.class, AllocateMessageQueueStrategy.class,
MessageQueueSelector.class, MessageListener.class, TransactionListener.class,
SendCallback.class, CheckForbiddenHook.class, SendMessageHook.class,
ErrorAcknowledgeHandler.class };
private static final Map<String, Object> BEANS_CACHE = new ConcurrentHashMap<>();
static void putBean(String beanName, Object beanObj) {
BEANS_CACHE.put(beanName, beanObj);
}
static Class<?>[] getClassAry() {
return CLASSES;
}
public static <T> T getBean(String beanName, Class<T> clazz) {
return getBean(beanName, clazz, null);
}
public static <T> T getBean(String beanName, Class<T> clazz, T defaultObj) {
if (StringUtils.isEmpty(beanName)) {
return defaultObj;
}
Object obj = BEANS_CACHE.get(beanName);
if (null == obj) {
return defaultObj;
}
if (clazz.isAssignableFrom(obj.getClass())) {
return (T) obj;
}
return defaultObj;
}
}

@ -0,0 +1,43 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.stream.binder.rocketmq.custom;
import java.util.stream.Stream;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
/**
* find RocketMQ bean by annotations.
*
* @author junboXiang
*
*/
public class RocketMQConfigBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
Stream.of(RocketMQBeanContainerCache.getClassAry()).forEach(clazz -> {
if (clazz.isAssignableFrom(bean.getClass())) {
RocketMQBeanContainerCache.putBean(beanName, bean);
}
});
return bean;
}
}

@ -0,0 +1,35 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.stream.binder.rocketmq.extend;
import org.springframework.integration.acks.AcknowledgmentCallback.Status;
import org.springframework.messaging.Message;
/**
* @author zkzlx
*/
public interface ErrorAcknowledgeHandler {
/**
* Ack state handling, including receive, reject, and retry, when a consumption
* exception occurs.
* @param message message
* @return see {@link Status}
*/
Status handler(Message<?> message);
}

@ -1,176 +0,0 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.stream.binder.rocketmq.integration;
import com.alibaba.cloud.stream.binder.rocketmq.consuming.RocketMQListenerBindingContainer;
import com.alibaba.cloud.stream.binder.rocketmq.metrics.Instrumentation;
import com.alibaba.cloud.stream.binder.rocketmq.metrics.InstrumentationManager;
import com.alibaba.cloud.stream.binder.rocketmq.properties.RocketMQConsumerProperties;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.stream.binder.ExtendedConsumerProperties;
import org.springframework.integration.endpoint.MessageProducerSupport;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessagingException;
import org.springframework.retry.RecoveryCallback;
import org.springframework.retry.RetryCallback;
import org.springframework.retry.RetryContext;
import org.springframework.retry.RetryListener;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.util.Assert;
/**
* @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
*/
public class RocketMQInboundChannelAdapter extends MessageProducerSupport {
private static final Logger log = LoggerFactory
.getLogger(RocketMQInboundChannelAdapter.class);
private RetryTemplate retryTemplate;
private RecoveryCallback<? extends Object> recoveryCallback;
private RocketMQListenerBindingContainer rocketMQListenerContainer;
private final ExtendedConsumerProperties<RocketMQConsumerProperties> consumerProperties;
private final InstrumentationManager instrumentationManager;
public RocketMQInboundChannelAdapter(
RocketMQListenerBindingContainer rocketMQListenerContainer,
ExtendedConsumerProperties<RocketMQConsumerProperties> consumerProperties,
InstrumentationManager instrumentationManager) {
this.rocketMQListenerContainer = rocketMQListenerContainer;
this.consumerProperties = consumerProperties;
this.instrumentationManager = instrumentationManager;
}
@Override
protected void onInit() {
if (consumerProperties == null
|| !consumerProperties.getExtension().getEnabled()) {
return;
}
super.onInit();
if (this.retryTemplate != null) {
Assert.state(getErrorChannel() == null,
"Cannot have an 'errorChannel' property when a 'RetryTemplate' is "
+ "provided; use an 'ErrorMessageSendingRecoverer' in the 'recoveryCallback' property to "
+ "send an error message when retries are exhausted");
}
BindingRocketMQListener listener = new BindingRocketMQListener();
rocketMQListenerContainer.setRocketMQListener(listener);
if (retryTemplate != null) {
this.retryTemplate.registerListener(listener);
}
try {
rocketMQListenerContainer.afterPropertiesSet();
}
catch (Exception e) {
log.error("rocketMQListenerContainer init error: " + e.getMessage(), e);
throw new IllegalArgumentException(
"rocketMQListenerContainer init error: " + e.getMessage(), e);
}
instrumentationManager.addHealthInstrumentation(
new Instrumentation(rocketMQListenerContainer.getTopic()
+ rocketMQListenerContainer.getConsumerGroup()));
}
@Override
protected void doStart() {
if (consumerProperties == null
|| !consumerProperties.getExtension().getEnabled()) {
return;
}
try {
rocketMQListenerContainer.start();
instrumentationManager
.getHealthInstrumentation(rocketMQListenerContainer.getTopic()
+ rocketMQListenerContainer.getConsumerGroup())
.markStartedSuccessfully();
}
catch (Exception e) {
instrumentationManager
.getHealthInstrumentation(rocketMQListenerContainer.getTopic()
+ rocketMQListenerContainer.getConsumerGroup())
.markStartFailed(e);
log.error("RocketMQTemplate startup failed, Caused by " + e.getMessage());
throw new MessagingException(MessageBuilder.withPayload(
"RocketMQTemplate startup failed, Caused by " + e.getMessage())
.build(), e);
}
}
@Override
protected void doStop() {
rocketMQListenerContainer.stop();
}
public void setRetryTemplate(RetryTemplate retryTemplate) {
this.retryTemplate = retryTemplate;
}
public void setRecoveryCallback(RecoveryCallback<? extends Object> recoveryCallback) {
this.recoveryCallback = recoveryCallback;
}
protected class BindingRocketMQListener
implements RocketMQListener<Message>, RetryListener {
@Override
public void onMessage(Message message) {
boolean enableRetry = RocketMQInboundChannelAdapter.this.retryTemplate != null;
if (enableRetry) {
RocketMQInboundChannelAdapter.this.retryTemplate.execute(context -> {
RocketMQInboundChannelAdapter.this.sendMessage(message);
return null;
}, (RecoveryCallback<Object>) RocketMQInboundChannelAdapter.this.recoveryCallback);
}
else {
RocketMQInboundChannelAdapter.this.sendMessage(message);
}
}
@Override
public <T, E extends Throwable> boolean open(RetryContext context,
RetryCallback<T, E> callback) {
return true;
}
@Override
public <T, E extends Throwable> void close(RetryContext context,
RetryCallback<T, E> callback, Throwable throwable) {
}
@Override
public <T, E extends Throwable> void onError(RetryContext context,
RetryCallback<T, E> callback, Throwable throwable) {
}
}
}

@ -1,302 +0,0 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.stream.binder.rocketmq.integration;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import com.alibaba.cloud.stream.binder.rocketmq.RocketMQBinderConstants;
import com.alibaba.cloud.stream.binder.rocketmq.metrics.Instrumentation;
import com.alibaba.cloud.stream.binder.rocketmq.metrics.InstrumentationManager;
import com.alibaba.cloud.stream.binder.rocketmq.properties.RocketMQProducerProperties;
import com.alibaba.cloud.stream.binder.rocketmq.support.RocketMQHeaderMapper;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.SendCallback;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.client.producer.SendStatus;
import org.apache.rocketmq.common.message.MessageConst;
import org.apache.rocketmq.common.message.MessageQueue;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.apache.rocketmq.spring.support.RocketMQHeaders;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.stream.binder.BinderHeaders;
import org.springframework.cloud.stream.binder.ExtendedProducerProperties;
import org.springframework.cloud.stream.binding.MessageConverterConfigurer;
import org.springframework.context.Lifecycle;
import org.springframework.integration.handler.AbstractMessageHandler;
import org.springframework.integration.support.DefaultErrorMessageStrategy;
import org.springframework.integration.support.ErrorMessageStrategy;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessagingException;
import org.springframework.messaging.support.ErrorMessage;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
*/
public class RocketMQMessageHandler extends AbstractMessageHandler implements Lifecycle {
private final static Logger log = LoggerFactory
.getLogger(RocketMQMessageHandler.class);
private ErrorMessageStrategy errorMessageStrategy = new DefaultErrorMessageStrategy();
private MessageChannel sendFailureChannel;
private final RocketMQTemplate rocketMQTemplate;
private RocketMQHeaderMapper headerMapper;
private final Boolean transactional;
private final String destination;
private final String groupName;
private final InstrumentationManager instrumentationManager;
private boolean sync = false;
private volatile boolean running = false;
private ExtendedProducerProperties<RocketMQProducerProperties> producerProperties;
private MessageConverterConfigurer.PartitioningInterceptor partitioningInterceptor;
public RocketMQMessageHandler(RocketMQTemplate rocketMQTemplate, String destination,
String groupName, Boolean transactional,
InstrumentationManager instrumentationManager,
ExtendedProducerProperties<RocketMQProducerProperties> producerProperties,
MessageConverterConfigurer.PartitioningInterceptor partitioningInterceptor) {
this.rocketMQTemplate = rocketMQTemplate;
this.destination = destination;
this.groupName = groupName;
this.transactional = transactional;
this.instrumentationManager = instrumentationManager;
this.producerProperties = producerProperties;
this.partitioningInterceptor = partitioningInterceptor;
}
@Override
public void start() {
if (!transactional) {
instrumentationManager
.addHealthInstrumentation(new Instrumentation(destination));
try {
rocketMQTemplate.afterPropertiesSet();
instrumentationManager.getHealthInstrumentation(destination)
.markStartedSuccessfully();
}
catch (Exception e) {
instrumentationManager.getHealthInstrumentation(destination)
.markStartFailed(e);
log.error("RocketMQTemplate startup failed, Caused by " + e.getMessage());
throw new MessagingException(MessageBuilder.withPayload(
"RocketMQTemplate startup failed, Caused by " + e.getMessage())
.build(), e);
}
}
if (producerProperties.isPartitioned()) {
try {
List<MessageQueue> messageQueues = rocketMQTemplate.getProducer()
.fetchPublishMessageQueues(destination);
if (producerProperties.getPartitionCount() != messageQueues.size()) {
logger.info(String.format(
"The partition count of topic '%s' will change from '%s' to '%s'",
destination, producerProperties.getPartitionCount(),
messageQueues.size()));
producerProperties.setPartitionCount(messageQueues.size());
partitioningInterceptor
.setPartitionCount(producerProperties.getPartitionCount());
}
}
catch (MQClientException e) {
logger.error("fetch publish message queues fail", e);
}
}
running = true;
}
@Override
public void stop() {
if (!transactional) {
rocketMQTemplate.destroy();
}
running = false;
}
@Override
public boolean isRunning() {
return running;
}
@Override
protected void handleMessageInternal(
org.springframework.messaging.Message<?> message) {
try {
// issue 737 fix
Map<String, String> jsonHeaders = headerMapper
.fromHeaders(message.getHeaders());
message = org.springframework.messaging.support.MessageBuilder
.fromMessage(message).copyHeaders(jsonHeaders).build();
final StringBuilder topicWithTags = new StringBuilder(destination);
String tags = Optional
.ofNullable(message.getHeaders().get(RocketMQHeaders.TAGS)).orElse("")
.toString();
if (!StringUtils.isEmpty(tags)) {
topicWithTags.append(":").append(tags);
}
SendResult sendRes = null;
if (transactional) {
sendRes = rocketMQTemplate.sendMessageInTransaction(groupName,
topicWithTags.toString(), message, message.getHeaders()
.get(RocketMQBinderConstants.ROCKET_TRANSACTIONAL_ARG));
log.debug("transactional send to topic " + topicWithTags + " " + sendRes);
}
else {
int delayLevel = 0;
try {
Object delayLevelObj = message.getHeaders()
.getOrDefault(MessageConst.PROPERTY_DELAY_TIME_LEVEL, 0);
if (delayLevelObj instanceof Number) {
delayLevel = ((Number) delayLevelObj).intValue();
}
else if (delayLevelObj instanceof String) {
delayLevel = Integer.parseInt((String) delayLevelObj);
}
}
catch (Exception e) {
// ignore
}
boolean needSelectQueue = message.getHeaders()
.containsKey(BinderHeaders.PARTITION_HEADER);
if (sync) {
if (needSelectQueue) {
sendRes = rocketMQTemplate.syncSendOrderly(
topicWithTags.toString(), message, "",
rocketMQTemplate.getProducer().getSendMsgTimeout());
}
else {
sendRes = rocketMQTemplate.syncSend(topicWithTags.toString(),
message,
rocketMQTemplate.getProducer().getSendMsgTimeout(),
delayLevel);
}
log.debug("sync send to topic " + topicWithTags + " " + sendRes);
}
else {
Message<?> finalMessage = message;
SendCallback sendCallback = new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
log.debug("async send to topic " + topicWithTags + " "
+ sendResult);
}
@Override
public void onException(Throwable e) {
log.error("RocketMQ Message hasn't been sent. Caused by "
+ e.getMessage());
if (getSendFailureChannel() != null) {
getSendFailureChannel().send(
RocketMQMessageHandler.this.errorMessageStrategy
.buildErrorMessage(new MessagingException(
finalMessage, e), null));
}
}
};
if (needSelectQueue) {
rocketMQTemplate.asyncSendOrderly(topicWithTags.toString(),
message, "", sendCallback,
rocketMQTemplate.getProducer().getSendMsgTimeout());
}
else {
rocketMQTemplate.asyncSend(topicWithTags.toString(), message,
sendCallback);
}
}
}
if (sendRes != null && !sendRes.getSendStatus().equals(SendStatus.SEND_OK)) {
if (getSendFailureChannel() != null) {
this.getSendFailureChannel().send(message);
}
else {
throw new MessagingException(message,
new MQClientException("message hasn't been sent", null));
}
}
}
catch (Exception e) {
log.error("RocketMQ Message hasn't been sent. Caused by " + e.getMessage());
if (getSendFailureChannel() != null) {
getSendFailureChannel().send(this.errorMessageStrategy
.buildErrorMessage(new MessagingException(message, e), null));
}
else {
throw new MessagingException(message, e);
}
}
}
/**
* Set the failure channel. After a send failure, an {@link ErrorMessage} will be sent
* to this channel with a payload of a {@link MessagingException} with the failed
* message and cause.
* @param sendFailureChannel the failure channel.
* @since 0.2.2
*/
public void setSendFailureChannel(MessageChannel sendFailureChannel) {
this.sendFailureChannel = sendFailureChannel;
}
/**
* Set the error message strategy implementation to use when sending error messages
* after send failures. Cannot be null.
* @param errorMessageStrategy the implementation.
* @since 0.2.2
*/
public void setErrorMessageStrategy(ErrorMessageStrategy errorMessageStrategy) {
Assert.notNull(errorMessageStrategy, "'errorMessageStrategy' cannot be null");
this.errorMessageStrategy = errorMessageStrategy;
}
public MessageChannel getSendFailureChannel() {
return sendFailureChannel;
}
public void setSync(boolean sync) {
this.sync = sync;
}
public RocketMQHeaderMapper getHeaderMapper() {
return headerMapper;
}
public void setHeaderMapper(RocketMQHeaderMapper headerMapper) {
this.headerMapper = headerMapper;
}
}

@ -1,382 +0,0 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.stream.binder.rocketmq.integration;
import java.util.List;
import java.util.Set;
import com.alibaba.cloud.stream.binder.rocketmq.RocketMQBinderUtils;
import com.alibaba.cloud.stream.binder.rocketmq.consuming.RocketMQMessageQueueChooser;
import com.alibaba.cloud.stream.binder.rocketmq.properties.RocketMQBinderConfigurationProperties;
import com.alibaba.cloud.stream.binder.rocketmq.properties.RocketMQConsumerProperties;
import org.apache.rocketmq.client.consumer.DefaultMQPullConsumer;
import org.apache.rocketmq.client.consumer.MessageQueueListener;
import org.apache.rocketmq.client.consumer.MessageSelector;
import org.apache.rocketmq.client.consumer.PullResult;
import org.apache.rocketmq.client.consumer.PullStatus;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.common.message.MessageQueue;
import org.apache.rocketmq.common.protocol.heartbeat.MessageModel;
import org.apache.rocketmq.spring.support.RocketMQUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.cloud.stream.binder.ExtendedConsumerProperties;
import org.springframework.context.Lifecycle;
import org.springframework.integration.IntegrationMessageHeaderAccessor;
import org.springframework.integration.acks.AcknowledgmentCallback;
import org.springframework.integration.acks.AcknowledgmentCallbackFactory;
import org.springframework.integration.endpoint.AbstractMessageSource;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.Message;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
*/
public class RocketMQMessageSource extends AbstractMessageSource<Object>
implements DisposableBean, Lifecycle {
private final static Logger log = LoggerFactory
.getLogger(RocketMQMessageSource.class);
private final RocketMQCallbackFactory ackCallbackFactory;
private final RocketMQBinderConfigurationProperties rocketMQBinderConfigurationProperties;
private final ExtendedConsumerProperties<RocketMQConsumerProperties> rocketMQConsumerProperties;
private final String topic;
private final String group;
private final Object consumerMonitor = new Object();
private DefaultMQPullConsumer consumer;
private boolean running;
private MessageSelector messageSelector;
private RocketMQMessageQueueChooser messageQueueChooser = new RocketMQMessageQueueChooser();
public RocketMQMessageSource(
RocketMQBinderConfigurationProperties rocketMQBinderConfigurationProperties,
ExtendedConsumerProperties<RocketMQConsumerProperties> rocketMQConsumerProperties,
String topic, String group) {
this(new RocketMQCallbackFactory(), rocketMQBinderConfigurationProperties,
rocketMQConsumerProperties, topic, group);
}
public RocketMQMessageSource(RocketMQCallbackFactory ackCallbackFactory,
RocketMQBinderConfigurationProperties rocketMQBinderConfigurationProperties,
ExtendedConsumerProperties<RocketMQConsumerProperties> rocketMQConsumerProperties,
String topic, String group) {
this.ackCallbackFactory = ackCallbackFactory;
this.rocketMQBinderConfigurationProperties = rocketMQBinderConfigurationProperties;
this.rocketMQConsumerProperties = rocketMQConsumerProperties;
this.topic = topic;
this.group = group;
}
@Override
public synchronized void start() {
if (this.isRunning()) {
throw new IllegalStateException(
"pull consumer already running. " + this.toString());
}
try {
consumer = new DefaultMQPullConsumer(group);
consumer.setNamesrvAddr(RocketMQBinderUtils.getNameServerStr(
rocketMQBinderConfigurationProperties.getNameServer()));
consumer.setConsumerPullTimeoutMillis(
rocketMQConsumerProperties.getExtension().getPullTimeout());
consumer.setMessageModel(MessageModel.CLUSTERING);
String tags = rocketMQConsumerProperties.getExtension().getTags();
String sql = rocketMQConsumerProperties.getExtension().getSql();
if (!StringUtils.isEmpty(tags) && !StringUtils.isEmpty(sql)) {
messageSelector = MessageSelector.byTag(tags);
}
else if (!StringUtils.isEmpty(tags)) {
messageSelector = MessageSelector.byTag(tags);
}
else if (!StringUtils.isEmpty(sql)) {
messageSelector = MessageSelector.bySql(sql);
}
consumer.registerMessageQueueListener(topic, new MessageQueueListener() {
@Override
public void messageQueueChanged(String topic, Set<MessageQueue> mqAll,
Set<MessageQueue> mqDivided) {
log.info(
"messageQueueChanged, topic='{}', mqAll=`{}`, mqDivided=`{}`",
topic, mqAll, mqDivided);
switch (consumer.getMessageModel()) {
case BROADCASTING:
RocketMQMessageSource.this.resetMessageQueues(mqAll);
break;
case CLUSTERING:
RocketMQMessageSource.this.resetMessageQueues(mqDivided);
break;
default:
break;
}
}
});
consumer.start();
}
catch (MQClientException e) {
log.error("DefaultMQPullConsumer startup error: " + e.getMessage(), e);
}
this.setRunning(true);
}
@Override
public synchronized void stop() {
if (this.isRunning()) {
this.setRunning(false);
consumer.shutdown();
}
}
@Override
public synchronized boolean isRunning() {
return running;
}
@Override
protected synchronized Object doReceive() {
if (messageQueueChooser.getMessageQueues() == null
|| messageQueueChooser.getMessageQueues().size() == 0) {
return null;
}
try {
int count = 0;
while (count < messageQueueChooser.getMessageQueues().size()) {
MessageQueue messageQueue;
synchronized (this.consumerMonitor) {
messageQueue = messageQueueChooser.choose();
messageQueueChooser.increment();
}
long offset = consumer.fetchConsumeOffset(messageQueue,
rocketMQConsumerProperties.getExtension().isFromStore());
log.debug("topic='{}', group='{}', messageQueue='{}', offset now='{}'",
this.topic, this.group, messageQueue, offset);
PullResult pullResult;
if (messageSelector != null) {
pullResult = consumer.pull(messageQueue, messageSelector, offset, 1);
}
else {
pullResult = consumer.pull(messageQueue, (String) null, offset, 1);
}
if (pullResult.getPullStatus() == PullStatus.FOUND) {
List<MessageExt> messageExtList = pullResult.getMsgFoundList();
Message message = RocketMQUtil
.convertToSpringMessage(messageExtList.get(0));
AcknowledgmentCallback ackCallback = this.ackCallbackFactory
.createCallback(new RocketMQAckInfo(messageQueue, pullResult,
consumer, offset));
Message messageResult = MessageBuilder.fromMessage(message).setHeader(
IntegrationMessageHeaderAccessor.ACKNOWLEDGMENT_CALLBACK,
ackCallback).build();
return messageResult;
}
else {
log.debug("messageQueue='{}' PullResult='{}' with topic `{}`",
messageQueueChooser.getMessageQueues(),
pullResult.getPullStatus(), topic);
}
count++;
}
}
catch (Exception e) {
log.error("Consumer pull error: " + e.getMessage(), e);
}
return null;
}
@Override
public String getComponentType() {
return "rocketmq:message-source";
}
public synchronized void setRunning(boolean running) {
this.running = running;
}
public synchronized void resetMessageQueues(Set<MessageQueue> queueSet) {
log.info("resetMessageQueues, topic='{}', messageQueue=`{}`", topic, queueSet);
synchronized (this.consumerMonitor) {
this.messageQueueChooser.reset(queueSet);
}
}
public static class RocketMQCallbackFactory
implements AcknowledgmentCallbackFactory<RocketMQAckInfo> {
@Override
public AcknowledgmentCallback createCallback(RocketMQAckInfo info) {
return new RocketMQAckCallback(info);
}
}
public static class RocketMQAckCallback implements AcknowledgmentCallback {
private final RocketMQAckInfo ackInfo;
private boolean acknowledged;
private boolean autoAckEnabled = true;
public RocketMQAckCallback(RocketMQAckInfo ackInfo) {
this.ackInfo = ackInfo;
}
protected void setAcknowledged(boolean acknowledged) {
this.acknowledged = acknowledged;
}
@Override
public boolean isAcknowledged() {
return this.acknowledged;
}
@Override
public void noAutoAck() {
this.autoAckEnabled = false;
}
@Override
public boolean isAutoAck() {
return this.autoAckEnabled;
}
@Override
public void acknowledge(Status status) {
Assert.notNull(status, "'status' cannot be null");
if (this.acknowledged) {
throw new IllegalStateException("Already acknowledged");
}
log.debug("acknowledge(" + status.name() + ") for " + this);
synchronized (this.ackInfo.getConsumerMonitor()) {
try {
switch (status) {
case ACCEPT:
case REJECT:
ackInfo.getConsumer().updateConsumeOffset(
ackInfo.getMessageQueue(),
ackInfo.getPullResult().getNextBeginOffset());
log.debug("messageQueue='{}' offset update to `{}`",
ackInfo.getMessageQueue(), String.valueOf(
ackInfo.getPullResult().getNextBeginOffset()));
break;
case REQUEUE:
// decrease index and update offset of messageQueue of ackInfo
int oldIndex = ackInfo.getMessageQueueChooser().requeue();
ackInfo.getConsumer().updateConsumeOffset(
ackInfo.getMessageQueue(), ackInfo.getOldOffset());
log.debug(
"messageQueue='{}' offset requeue to index:`{}`, oldOffset:'{}'",
ackInfo.getMessageQueue(), oldIndex,
ackInfo.getOldOffset());
break;
default:
break;
}
}
catch (MQClientException e) {
log.error("acknowledge error: " + e.getErrorMessage(), e);
}
finally {
this.acknowledged = true;
}
}
}
@Override
public String toString() {
return "RocketMQAckCallback{" + "ackInfo=" + ackInfo + ", acknowledged="
+ acknowledged + ", autoAckEnabled=" + autoAckEnabled + '}';
}
}
public class RocketMQAckInfo {
private final MessageQueue messageQueue;
private final PullResult pullResult;
private final DefaultMQPullConsumer consumer;
private final long oldOffset;
public RocketMQAckInfo(MessageQueue messageQueue, PullResult pullResult,
DefaultMQPullConsumer consumer, long oldOffset) {
this.messageQueue = messageQueue;
this.pullResult = pullResult;
this.consumer = consumer;
this.oldOffset = oldOffset;
}
public MessageQueue getMessageQueue() {
return messageQueue;
}
public PullResult getPullResult() {
return pullResult;
}
public DefaultMQPullConsumer getConsumer() {
return consumer;
}
public RocketMQMessageQueueChooser getMessageQueueChooser() {
return RocketMQMessageSource.this.messageQueueChooser;
}
public long getOldOffset() {
return oldOffset;
}
public Object getConsumerMonitor() {
return RocketMQMessageSource.this.consumerMonitor;
}
@Override
public String toString() {
return "RocketMQAckInfo{" + "messageQueue=" + messageQueue + ", pullResult="
+ pullResult + ", consumer=" + consumer + ", oldOffset=" + oldOffset
+ '}';
}
}
}

@ -0,0 +1,160 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.stream.binder.rocketmq.integration.inbound;
import com.alibaba.cloud.stream.binder.rocketmq.custom.RocketMQBeanContainerCache;
import com.alibaba.cloud.stream.binder.rocketmq.properties.RocketMQConsumerProperties;
import com.alibaba.cloud.stream.binder.rocketmq.utils.RocketMQUtils;
import org.apache.rocketmq.acl.common.AclClientRPCHook;
import org.apache.rocketmq.acl.common.SessionCredentials;
import org.apache.rocketmq.client.consumer.AllocateMessageQueueStrategy;
import org.apache.rocketmq.client.consumer.DefaultLitePullConsumer;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.rebalance.AllocateMessageQueueAveragely;
import org.apache.rocketmq.common.protocol.heartbeat.MessageModel;
import org.apache.rocketmq.remoting.RPCHook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.stream.binder.ExtendedConsumerProperties;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Extended function related to producer . eg:initial
*
* @author zkzlx
*/
public final class RocketMQConsumerFactory {
private RocketMQConsumerFactory() {
}
private final static Logger log = LoggerFactory
.getLogger(RocketMQConsumerFactory.class);
public static DefaultMQPushConsumer initPushConsumer(
ExtendedConsumerProperties<RocketMQConsumerProperties> extendedConsumerProperties) {
RocketMQConsumerProperties consumerProperties = extendedConsumerProperties
.getExtension();
Assert.notNull(consumerProperties.getGroup(),
"Property 'group' is required - consumerGroup");
Assert.notNull(consumerProperties.getNameServer(),
"Property 'nameServer' is required");
AllocateMessageQueueStrategy allocateMessageQueueStrategy = RocketMQBeanContainerCache
.getBean(consumerProperties.getAllocateMessageQueueStrategy(),
AllocateMessageQueueStrategy.class,
new AllocateMessageQueueAveragely());
RPCHook rpcHook = null;
if (!StringUtils.isEmpty(consumerProperties.getAccessKey())
&& !StringUtils.isEmpty(consumerProperties.getSecretKey())) {
rpcHook = new AclClientRPCHook(
new SessionCredentials(consumerProperties.getAccessKey(),
consumerProperties.getSecretKey()));
}
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(
consumerProperties.getGroup(), rpcHook, allocateMessageQueueStrategy,
consumerProperties.getEnableMsgTrace(),
consumerProperties.getCustomizedTraceTopic());
consumer.setVipChannelEnabled(
null == rpcHook && consumerProperties.getVipChannelEnabled());
consumer.setInstanceName(
RocketMQUtils.getInstanceName(rpcHook, consumerProperties.getGroup()));
consumer.setNamespace(consumerProperties.getNamespace());
consumer.setNamesrvAddr(consumerProperties.getNameServer());
consumer.setMessageModel(getMessageModel(consumerProperties.getMessageModel()));
consumer.setUseTLS(consumerProperties.getUseTLS());
consumer.setPullTimeDelayMillsWhenException(
consumerProperties.getPullTimeDelayMillsWhenException());
consumer.setPullBatchSize(consumerProperties.getPullBatchSize());
consumer.setConsumeFromWhere(consumerProperties.getConsumeFromWhere());
consumer.setHeartbeatBrokerInterval(
consumerProperties.getHeartbeatBrokerInterval());
consumer.setPersistConsumerOffsetInterval(
consumerProperties.getPersistConsumerOffsetInterval());
consumer.setPullInterval(consumerProperties.getPush().getPullInterval());
consumer.setConsumeThreadMin(extendedConsumerProperties.getConcurrency());
consumer.setConsumeThreadMax(extendedConsumerProperties.getConcurrency());
return consumer;
}
/**
* todo Compatible with versions less than 4.6 ?
* @param extendedConsumerProperties extendedConsumerProperties
* @return DefaultLitePullConsumer
*/
public static DefaultLitePullConsumer initPullConsumer(
ExtendedConsumerProperties<RocketMQConsumerProperties> extendedConsumerProperties) {
RocketMQConsumerProperties consumerProperties = extendedConsumerProperties
.getExtension();
Assert.notNull(consumerProperties.getGroup(),
"Property 'group' is required - consumerGroup");
Assert.notNull(consumerProperties.getNameServer(),
"Property 'nameServer' is required");
AllocateMessageQueueStrategy allocateMessageQueueStrategy = RocketMQBeanContainerCache
.getBean(consumerProperties.getAllocateMessageQueueStrategy(),
AllocateMessageQueueStrategy.class,
new AllocateMessageQueueAveragely());
RPCHook rpcHook = null;
if (!StringUtils.isEmpty(consumerProperties.getAccessKey())
&& !StringUtils.isEmpty(consumerProperties.getSecretKey())) {
rpcHook = new AclClientRPCHook(
new SessionCredentials(consumerProperties.getAccessKey(),
consumerProperties.getSecretKey()));
}
DefaultLitePullConsumer consumer = new DefaultLitePullConsumer(
consumerProperties.getNamespace(), consumerProperties.getGroup(),
rpcHook);
consumer.setVipChannelEnabled(
null == rpcHook && consumerProperties.getVipChannelEnabled());
consumer.setInstanceName(
RocketMQUtils.getInstanceName(rpcHook, consumerProperties.getGroup()));
consumer.setAllocateMessageQueueStrategy(allocateMessageQueueStrategy);
consumer.setNamesrvAddr(consumerProperties.getNameServer());
consumer.setMessageModel(getMessageModel(consumerProperties.getMessageModel()));
consumer.setUseTLS(consumerProperties.getUseTLS());
consumer.setPullTimeDelayMillsWhenException(
consumerProperties.getPullTimeDelayMillsWhenException());
consumer.setConsumerTimeoutMillisWhenSuspend(
consumerProperties.getPull().getConsumerTimeoutMillisWhenSuspend());
consumer.setPullBatchSize(consumerProperties.getPullBatchSize());
consumer.setConsumeFromWhere(consumerProperties.getConsumeFromWhere());
consumer.setHeartbeatBrokerInterval(
consumerProperties.getHeartbeatBrokerInterval());
consumer.setPersistConsumerOffsetInterval(
consumerProperties.getPersistConsumerOffsetInterval());
consumer.setPollTimeoutMillis(
consumerProperties.getPull().getPollTimeoutMillis());
consumer.setPullThreadNums(extendedConsumerProperties.getConcurrency());
// The internal queues are cached by a maximum of 1000
consumer.setPullThresholdForAll(extendedConsumerProperties.getExtension()
.getPull().getPullThresholdForAll());
return consumer;
}
private static MessageModel getMessageModel(String messageModel) {
for (MessageModel model : MessageModel.values()) {
if (model.getModeCN().equalsIgnoreCase(messageModel)) {
return model;
}
}
return MessageModel.CLUSTERING;
}
}

@ -0,0 +1,230 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.stream.binder.rocketmq.integration.inbound;
import java.util.List;
import java.util.function.Supplier;
import com.alibaba.cloud.stream.binder.rocketmq.metrics.Instrumentation;
import com.alibaba.cloud.stream.binder.rocketmq.metrics.InstrumentationManager;
import com.alibaba.cloud.stream.binder.rocketmq.properties.RocketMQConsumerProperties;
import com.alibaba.cloud.stream.binder.rocketmq.support.RocketMQMessageConverterSupport;
import com.alibaba.cloud.stream.binder.rocketmq.utils.RocketMQUtils;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.client.consumer.listener.MessageListenerOrderly;
import org.apache.rocketmq.common.message.MessageExt;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.stream.binder.ExtendedConsumerProperties;
import org.springframework.integration.context.OrderlyShutdownCapable;
import org.springframework.integration.endpoint.MessageProducerSupport;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessagingException;
import org.springframework.retry.RecoveryCallback;
import org.springframework.retry.RetryCallback;
import org.springframework.retry.RetryContext;
import org.springframework.retry.RetryListener;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
/**
* @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
*/
public class RocketMQInboundChannelAdapter extends MessageProducerSupport
implements OrderlyShutdownCapable {
private static final Logger log = LoggerFactory
.getLogger(RocketMQInboundChannelAdapter.class);
private RetryTemplate retryTemplate;
private RecoveryCallback<Object> recoveryCallback;
private DefaultMQPushConsumer pushConsumer;
private final String topic;
private final ExtendedConsumerProperties<RocketMQConsumerProperties> extendedConsumerProperties;
public RocketMQInboundChannelAdapter(String topic,
ExtendedConsumerProperties<RocketMQConsumerProperties> extendedConsumerProperties) {
this.topic = topic;
this.extendedConsumerProperties = extendedConsumerProperties;
}
@Override
protected void onInit() {
if (extendedConsumerProperties.getExtension() == null
|| !extendedConsumerProperties.getExtension().getEnabled()) {
return;
}
Instrumentation instrumentation = new Instrumentation(topic, this);
try {
super.onInit();
if (this.retryTemplate != null) {
Assert.state(getErrorChannel() == null,
"Cannot have an 'errorChannel' property when a 'RetryTemplate' is "
+ "provided; use an 'ErrorMessageSendingRecoverer' in the 'recoveryCallback' property to "
+ "send an error message when retries are exhausted");
this.retryTemplate.registerListener(new RetryListener() {
@Override
public <T, E extends Throwable> boolean open(RetryContext context,
RetryCallback<T, E> callback) {
return true;
}
@Override
public <T, E extends Throwable> void close(RetryContext context,
RetryCallback<T, E> callback, Throwable throwable) {
}
@Override
public <T, E extends Throwable> void onError(RetryContext context,
RetryCallback<T, E> callback, Throwable throwable) {
}
});
}
pushConsumer = RocketMQConsumerFactory
.initPushConsumer(extendedConsumerProperties);
// prepare register consumer message listener,the next step is to be
// compatible with a custom MessageListener.
if (extendedConsumerProperties.getExtension().getPush().getOrderly()) {
pushConsumer.registerMessageListener((MessageListenerOrderly) (msgs,
context) -> RocketMQInboundChannelAdapter.this
.consumeMessage(msgs, () -> {
context.setSuspendCurrentQueueTimeMillis(
extendedConsumerProperties.getExtension()
.getPush()
.getSuspendCurrentQueueTimeMillis());
return ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT;
}, () -> ConsumeOrderlyStatus.SUCCESS));
}
else {
pushConsumer.registerMessageListener((MessageListenerConcurrently) (msgs,
context) -> RocketMQInboundChannelAdapter.this
.consumeMessage(msgs, () -> {
context.setDelayLevelWhenNextConsume(
extendedConsumerProperties.getExtension()
.getPush()
.getDelayLevelWhenNextConsume());
return ConsumeConcurrentlyStatus.RECONSUME_LATER;
}, () -> ConsumeConcurrentlyStatus.CONSUME_SUCCESS));
}
instrumentation.markStartedSuccessfully();
}
catch (Exception e) {
instrumentation.markStartFailed(e);
log.error("DefaultMQPushConsumer init failed, Caused by " + e.getMessage());
throw new MessagingException(MessageBuilder.withPayload(
"DefaultMQPushConsumer init failed, Caused by " + e.getMessage())
.build(), e);
}
finally {
InstrumentationManager.addHealthInstrumentation(instrumentation);
}
}
/**
* The actual execution of a user-defined input consumption service method.
* @param messageExtList rocket mq message list
* @param failSupplier {@link ConsumeConcurrentlyStatus} or
* {@link ConsumeOrderlyStatus}
* @param sucSupplier {@link ConsumeConcurrentlyStatus} or
* {@link ConsumeOrderlyStatus}
* @param <R> object
* @return R
*/
private <R> R consumeMessage(List<MessageExt> messageExtList,
Supplier<R> failSupplier, Supplier<R> sucSupplier) {
if (CollectionUtils.isEmpty(messageExtList)) {
throw new MessagingException(
"DefaultMQPushConsumer consuming failed, Caused by messageExtList is empty");
}
for (MessageExt messageExt : messageExtList) {
try {
Message<?> message = RocketMQMessageConverterSupport
.convertMessage2Spring(messageExt);
if (this.retryTemplate != null) {
this.retryTemplate.execute(context -> {
this.sendMessage(message);
return message;
}, this.recoveryCallback);
}
else {
this.sendMessage(message);
}
}
catch (Exception e) {
log.warn("consume message failed. messageExt:{}", messageExt, e);
return failSupplier.get();
}
}
return sucSupplier.get();
}
@Override
protected void doStart() {
if (extendedConsumerProperties.getExtension() == null
|| !extendedConsumerProperties.getExtension().getEnabled()) {
return;
}
try {
pushConsumer.subscribe(topic, RocketMQUtils.getMessageSelector(
extendedConsumerProperties.getExtension().getSubscription()));
pushConsumer.start();
}
catch (Exception e) {
log.error("DefaultMQPushConsumer init failed, Caused by " + e.getMessage());
throw new MessagingException(MessageBuilder.withPayload(
"DefaultMQPushConsumer init failed, Caused by " + e.getMessage())
.build(), e);
}
}
@Override
protected void doStop() {
if (pushConsumer != null) {
pushConsumer.shutdown();
}
}
public void setRetryTemplate(RetryTemplate retryTemplate) {
this.retryTemplate = retryTemplate;
}
public void setRecoveryCallback(RecoveryCallback<Object> recoveryCallback) {
this.recoveryCallback = recoveryCallback;
}
@Override
public int beforeShutdown() {
this.stop();
return 0;
}
@Override
public int afterShutdown() {
return 0;
}
}

@ -0,0 +1,44 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.stream.binder.rocketmq.integration.inbound.pull;
import com.alibaba.cloud.stream.binder.rocketmq.extend.ErrorAcknowledgeHandler;
import org.springframework.integration.acks.AcknowledgmentCallback.Status;
import org.springframework.messaging.Message;
/**
* By default, if consumption fails, the corresponding MessageQueue will always be
* retried, that is, the consumption of other messages in the MessageQueue will be
* blocked.
*
* @author zkzlx
*/
public class DefaultErrorAcknowledgeHandler implements ErrorAcknowledgeHandler {
/**
* Ack state handling, including receive, reject, and retry, when a consumption
* exception occurs.
* @param message message
* @return see {@link Status}
*/
@Override
public Status handler(Message<?> message) {
return Status.REQUEUE;
}
}

@ -0,0 +1,119 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.stream.binder.rocketmq.integration.inbound.pull;
import org.apache.rocketmq.client.consumer.DefaultLitePullConsumer;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.impl.consumer.AssignedMessageQueue;
import org.apache.rocketmq.client.impl.consumer.ProcessQueue;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.common.message.MessageQueue;
import org.apache.rocketmq.common.protocol.heartbeat.MessageModel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.integration.acks.AcknowledgmentCallback;
import org.springframework.util.Assert;
/**
* A pollable {@link org.springframework.integration.core.MessageSource} for RocketMQ.
*
* @author zkzlx
*/
public class RocketMQAckCallback implements AcknowledgmentCallback {
private final static Logger log = LoggerFactory.getLogger(RocketMQAckCallback.class);
private boolean acknowledged;
private boolean autoAckEnabled = true;
private MessageExt messageExt;
private AssignedMessageQueue assignedMessageQueue;
private DefaultLitePullConsumer consumer;
private final MessageQueue messageQueue;
public RocketMQAckCallback(DefaultLitePullConsumer consumer,
AssignedMessageQueue assignedMessageQueue, MessageQueue messageQueue,
MessageExt messageExt) {
this.messageExt = messageExt;
this.consumer = consumer;
this.assignedMessageQueue = assignedMessageQueue;
this.messageQueue = messageQueue;
}
@Override
public boolean isAcknowledged() {
return this.acknowledged;
}
@Override
public void noAutoAck() {
this.autoAckEnabled = false;
}
@Override
public boolean isAutoAck() {
return this.autoAckEnabled;
}
@Override
public void acknowledge(Status status) {
Assert.notNull(status, "'status' cannot be null");
if (this.acknowledged) {
throw new IllegalStateException("Already acknowledged");
}
synchronized (messageQueue) {
try {
long offset = messageExt.getQueueOffset();
switch (status) {
case REJECT:
case ACCEPT:
long consumerOffset = assignedMessageQueue
.getConsumerOffset(messageQueue);
if (consumerOffset != -1) {
ProcessQueue processQueue = assignedMessageQueue
.getProcessQueue(messageQueue);
if (processQueue != null && !processQueue.isDropped()) {
consumer.getOffsetStore().updateOffset(messageQueue,
consumerOffset, false);
}
}
if (consumer.getMessageModel() == MessageModel.BROADCASTING) {
consumer.getOffsetStore().persist(messageQueue);
}
break;
case REQUEUE:
consumer.seek(messageQueue, offset);
break;
default:
break;
}
}
catch (MQClientException e) {
throw new IllegalStateException(e);
}
finally {
this.acknowledged = true;
}
}
}
}

@ -0,0 +1,181 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.stream.binder.rocketmq.integration.inbound.pull;
import java.lang.reflect.Field;
import java.util.Iterator;
import java.util.List;
import com.alibaba.cloud.stream.binder.rocketmq.integration.inbound.RocketMQConsumerFactory;
import com.alibaba.cloud.stream.binder.rocketmq.metrics.Instrumentation;
import com.alibaba.cloud.stream.binder.rocketmq.metrics.InstrumentationManager;
import com.alibaba.cloud.stream.binder.rocketmq.properties.RocketMQConsumerProperties;
import com.alibaba.cloud.stream.binder.rocketmq.support.RocketMQMessageConverterSupport;
import com.alibaba.cloud.stream.binder.rocketmq.utils.RocketMQUtils;
import org.apache.rocketmq.client.consumer.DefaultLitePullConsumer;
import org.apache.rocketmq.client.consumer.MessageSelector;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.impl.consumer.AssignedMessageQueue;
import org.apache.rocketmq.client.impl.consumer.DefaultLitePullConsumerImpl;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.common.message.MessageQueue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.cloud.stream.binder.ExtendedConsumerProperties;
import org.springframework.context.Lifecycle;
import org.springframework.integration.IntegrationMessageHeaderAccessor;
import org.springframework.integration.endpoint.AbstractMessageSource;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.Message;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;
/**
* @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
*/
public class RocketMQMessageSource extends AbstractMessageSource<Object>
implements DisposableBean, Lifecycle {
private final static Logger log = LoggerFactory
.getLogger(RocketMQMessageSource.class);
private DefaultLitePullConsumer consumer;
private AssignedMessageQueue assignedMessageQueue;
private volatile boolean running;
private final String topic;
private final MessageSelector messageSelector;
private final ExtendedConsumerProperties<RocketMQConsumerProperties> extendedConsumerProperties;
private volatile Iterator<MessageExt> messageExtIterator = null;
public RocketMQMessageSource(String name,
ExtendedConsumerProperties<RocketMQConsumerProperties> extendedConsumerProperties) {
this.topic = name;
this.messageSelector = RocketMQUtils.getMessageSelector(
extendedConsumerProperties.getExtension().getSubscription());
this.extendedConsumerProperties = extendedConsumerProperties;
}
@Override
public synchronized void start() {
Instrumentation instrumentation = new Instrumentation(topic, this);
try {
if (this.isRunning()) {
throw new IllegalStateException(
"pull consumer already running. " + this.toString());
}
this.consumer = RocketMQConsumerFactory
.initPullConsumer(extendedConsumerProperties);
// This parameter must be 1, otherwise doReceive cannot be handled singly.
// this.consumer.setPullBatchSize(1);
this.consumer.subscribe(topic, messageSelector);
this.consumer.setAutoCommit(false);
this.assignedMessageQueue = acquireAssignedMessageQueue(this.consumer);
this.consumer.start();
instrumentation.markStartedSuccessfully();
}
catch (MQClientException e) {
instrumentation.markStartFailed(e);
log.error("DefaultMQPullConsumer startup error: " + e.getMessage(), e);
}
finally {
InstrumentationManager.addHealthInstrumentation(instrumentation);
}
this.running = true;
}
private AssignedMessageQueue acquireAssignedMessageQueue(
DefaultLitePullConsumer consumer) {
Field field = ReflectionUtils.findField(DefaultLitePullConsumer.class,
"defaultLitePullConsumerImpl");
assert field != null;
field.setAccessible(true);
DefaultLitePullConsumerImpl defaultLitePullConsumerImpl = (DefaultLitePullConsumerImpl) ReflectionUtils
.getField(field, consumer);
field = ReflectionUtils.findField(DefaultLitePullConsumerImpl.class,
"assignedMessageQueue");
assert field != null;
field.setAccessible(true);
return (AssignedMessageQueue) ReflectionUtils.getField(field,
defaultLitePullConsumerImpl);
}
@Override
public synchronized void stop() {
if (this.isRunning() && null != consumer) {
consumer.unsubscribe(topic);
consumer.shutdown();
this.running = false;
}
}
@Override
public synchronized boolean isRunning() {
return running;
}
@Override
protected synchronized Object doReceive() {
if (messageExtIterator == null) {
List<MessageExt> messageExtList = consumer.poll();
if (CollectionUtils.isEmpty(messageExtList) || messageExtList.size() > 1) {
return null;
}
messageExtIterator = messageExtList.iterator();
}
MessageExt messageExt = messageExtIterator.next();
if (!messageExtIterator.hasNext()) {
messageExtIterator = null;
}
if (null == messageExt) {
return null;
}
MessageQueue messageQueue = null;
for (MessageQueue queue : assignedMessageQueue.getAssignedMessageQueues()) {
if (queue.getQueueId() == messageExt.getQueueId()) {
messageQueue = queue;
break;
}
}
if (messageQueue == null) {
throw new IllegalArgumentException(
"The message queue is not in assigned list");
}
Message message = RocketMQMessageConverterSupport
.convertMessage2Spring(messageExt);
return MessageBuilder.fromMessage(message)
.setHeader(IntegrationMessageHeaderAccessor.ACKNOWLEDGMENT_CALLBACK,
new RocketMQAckCallback(this.consumer, assignedMessageQueue,
messageQueue, messageExt))
.build();
}
@Override
public String getComponentType() {
return "rocketmq:message-source";
}
}

@ -0,0 +1,136 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.stream.binder.rocketmq.integration.outbound;
import java.lang.reflect.Field;
import com.alibaba.cloud.stream.binder.rocketmq.custom.RocketMQBeanContainerCache;
import com.alibaba.cloud.stream.binder.rocketmq.properties.RocketMQProducerProperties;
import com.alibaba.cloud.stream.binder.rocketmq.utils.RocketMQUtils;
import org.apache.rocketmq.acl.common.AclClientRPCHook;
import org.apache.rocketmq.acl.common.SessionCredentials;
import org.apache.rocketmq.client.hook.CheckForbiddenHook;
import org.apache.rocketmq.client.hook.SendMessageHook;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.TransactionMQProducer;
import org.apache.rocketmq.client.trace.AsyncTraceDispatcher;
import org.apache.rocketmq.client.trace.TraceDispatcher;
import org.apache.rocketmq.client.trace.hook.SendMessageTraceHookImpl;
import org.apache.rocketmq.common.UtilAll;
import org.apache.rocketmq.remoting.RPCHook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Extended function related to producer . eg:initial .
*
* @author zkzlx
*/
public final class RocketMQProduceFactory {
private RocketMQProduceFactory() {
}
private final static Logger log = LoggerFactory
.getLogger(RocketMQProduceFactory.class);
/**
* init for the producer,including convert producer params.
* @param topic topic
* @param producerProperties producerProperties
* @return DefaultMQProducer
*/
public static DefaultMQProducer initRocketMQProducer(String topic,
RocketMQProducerProperties producerProperties) {
Assert.notNull(producerProperties.getGroup(),
"Property 'group' is required - producerGroup");
Assert.notNull(producerProperties.getNameServer(),
"Property 'nameServer' is required");
RPCHook rpcHook = null;
if (!StringUtils.isEmpty(producerProperties.getAccessKey())
&& !StringUtils.isEmpty(producerProperties.getSecretKey())) {
rpcHook = new AclClientRPCHook(
new SessionCredentials(producerProperties.getAccessKey(),
producerProperties.getSecretKey()));
}
DefaultMQProducer producer;
if (RocketMQProducerProperties.ProducerType.Trans
.equalsName(producerProperties.getProducerType())) {
producer = new TransactionMQProducer(producerProperties.getNamespace(),
producerProperties.getGroup(), rpcHook);
if (producerProperties.getEnableMsgTrace()) {
try {
AsyncTraceDispatcher dispatcher = new AsyncTraceDispatcher(
producerProperties.getGroup(), TraceDispatcher.Type.PRODUCE,
producerProperties.getCustomizedTraceTopic(), rpcHook);
dispatcher.setHostProducer(producer.getDefaultMQProducerImpl());
Field field = DefaultMQProducer.class
.getDeclaredField("traceDispatcher");
field.setAccessible(true);
field.set(producer, dispatcher);
producer.getDefaultMQProducerImpl().registerSendMessageHook(
new SendMessageTraceHookImpl(dispatcher));
}
catch (Throwable e) {
log.error(
"system mq-trace hook init failed ,maybe can't send msg trace data");
}
}
}
else {
producer = new DefaultMQProducer(producerProperties.getNamespace(),
producerProperties.getGroup(), rpcHook,
producerProperties.getEnableMsgTrace(),
producerProperties.getCustomizedTraceTopic());
}
producer.setVipChannelEnabled(
null == rpcHook && producerProperties.getVipChannelEnabled());
producer.setInstanceName(
RocketMQUtils.getInstanceName(rpcHook, topic + "|" + UtilAll.getPid()));
producer.setNamesrvAddr(producerProperties.getNameServer());
producer.setSendMsgTimeout(producerProperties.getSendMsgTimeout());
producer.setRetryTimesWhenSendFailed(
producerProperties.getRetryTimesWhenSendFailed());
producer.setRetryTimesWhenSendAsyncFailed(
producerProperties.getRetryTimesWhenSendAsyncFailed());
producer.setCompressMsgBodyOverHowmuch(
producerProperties.getCompressMsgBodyThreshold());
producer.setRetryAnotherBrokerWhenNotStoreOK(
producerProperties.getRetryAnotherBroker());
producer.setMaxMessageSize(producerProperties.getMaxMessageSize());
producer.setUseTLS(producerProperties.getUseTLS());
CheckForbiddenHook checkForbiddenHook = RocketMQBeanContainerCache.getBean(
producerProperties.getCheckForbiddenHook(), CheckForbiddenHook.class);
if (null != checkForbiddenHook) {
producer.getDefaultMQProducerImpl()
.registerCheckForbiddenHook(checkForbiddenHook);
}
SendMessageHook sendMessageHook = RocketMQBeanContainerCache
.getBean(producerProperties.getSendMessageHook(), SendMessageHook.class);
if (null != sendMessageHook) {
producer.getDefaultMQProducerImpl().registerSendMessageHook(sendMessageHook);
}
return producer;
}
}

@ -0,0 +1,292 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.stream.binder.rocketmq.integration.outbound;
import java.util.List;
import com.alibaba.cloud.stream.binder.rocketmq.contants.RocketMQConst;
import com.alibaba.cloud.stream.binder.rocketmq.custom.RocketMQBeanContainerCache;
import com.alibaba.cloud.stream.binder.rocketmq.metrics.Instrumentation;
import com.alibaba.cloud.stream.binder.rocketmq.metrics.InstrumentationManager;
import com.alibaba.cloud.stream.binder.rocketmq.properties.RocketMQProducerProperties;
import com.alibaba.cloud.stream.binder.rocketmq.provisioning.selector.PartitionMessageQueueSelector;
import com.alibaba.cloud.stream.binder.rocketmq.support.RocketMQMessageConverterSupport;
import org.apache.rocketmq.client.exception.MQBrokerException;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.MessageQueueSelector;
import org.apache.rocketmq.client.producer.SendCallback;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.client.producer.SendStatus;
import org.apache.rocketmq.client.producer.TransactionListener;
import org.apache.rocketmq.client.producer.TransactionMQProducer;
import org.apache.rocketmq.common.message.MessageQueue;
import org.apache.rocketmq.remoting.exception.RemotingException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.stream.binder.ExtendedProducerProperties;
import org.springframework.cloud.stream.binding.MessageConverterConfigurer;
import org.springframework.cloud.stream.binding.MessageConverterConfigurer.PartitioningInterceptor;
import org.springframework.cloud.stream.provisioning.ProducerDestination;
import org.springframework.context.Lifecycle;
import org.springframework.integration.handler.AbstractMessageHandler;
import org.springframework.integration.support.ErrorMessageStrategy;
import org.springframework.integration.support.ErrorMessageUtils;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessagingException;
/**
* @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
*/
public class RocketMQProducerMessageHandler extends AbstractMessageHandler
implements Lifecycle {
private final static Logger log = LoggerFactory
.getLogger(RocketMQProducerMessageHandler.class);
private volatile boolean running = false;
private volatile boolean isTrans = false;
private ErrorMessageStrategy errorMessageStrategy;
private MessageChannel sendFailureChannel;
private MessageConverterConfigurer.PartitioningInterceptor partitioningInterceptor;
private DefaultMQProducer defaultMQProducer;
private MessageQueueSelector messageQueueSelector;
private final ProducerDestination destination;
private final ExtendedProducerProperties<RocketMQProducerProperties> extendedProducerProperties;
private final RocketMQProducerProperties mqProducerProperties;
public RocketMQProducerMessageHandler(ProducerDestination destination,
ExtendedProducerProperties<RocketMQProducerProperties> extendedProducerProperties,
RocketMQProducerProperties mqProducerProperties) {
this.destination = destination;
this.extendedProducerProperties = extendedProducerProperties;
this.mqProducerProperties = mqProducerProperties;
}
@Override
protected void onInit() {
if (null == mqProducerProperties || !mqProducerProperties.getEnabled()) {
return;
}
super.onInit();
this.defaultMQProducer = RocketMQProduceFactory
.initRocketMQProducer(destination.getName(), mqProducerProperties);
this.isTrans = defaultMQProducer instanceof TransactionMQProducer;
// Use the default if the partition is on and no customization is available.
this.messageQueueSelector = RocketMQBeanContainerCache.getBean(
mqProducerProperties.getMessageQueueSelector(),
MessageQueueSelector.class, extendedProducerProperties.isPartitioned()
? new PartitionMessageQueueSelector() : null);
}
@Override
public void start() {
Instrumentation instrumentation = new Instrumentation(destination.getName(),
this);
try {
defaultMQProducer.start();
// TransactionMQProducer does not currently support custom
// MessageQueueSelector.
if (!isTrans && extendedProducerProperties.isPartitioned()) {
List<MessageQueue> messageQueues = defaultMQProducer
.fetchPublishMessageQueues(destination.getName());
if (extendedProducerProperties.getPartitionCount() != messageQueues
.size()) {
logger.info(String.format(
"The partition count of topic '%s' will change from '%s' to '%s'",
destination.getName(),
extendedProducerProperties.getPartitionCount(),
messageQueues.size()));
extendedProducerProperties.setPartitionCount(messageQueues.size());
// may be npe!
partitioningInterceptor.setPartitionCount(
extendedProducerProperties.getPartitionCount());
}
}
running = true;
instrumentation.markStartedSuccessfully();
}
catch (MQClientException | NullPointerException e) {
instrumentation.markStartFailed(e);
log.error("The defaultMQProducer startup failure !!!", e);
}
finally {
InstrumentationManager.addHealthInstrumentation(instrumentation);
}
}
@Override
public void stop() {
if (running && null != defaultMQProducer) {
defaultMQProducer.shutdown();
}
running = false;
}
@Override
public boolean isRunning() {
return running;
}
@Override
protected void handleMessageInternal(Message<?> message) {
try {
org.apache.rocketmq.common.message.Message mqMessage = RocketMQMessageConverterSupport
.convertMessage2MQ(destination.getName(), message);
SendResult sendResult;
if (defaultMQProducer instanceof TransactionMQProducer) {
TransactionListener transactionListener = RocketMQBeanContainerCache
.getBean(mqProducerProperties.getTransactionListener(),
TransactionListener.class);
if (transactionListener == null) {
throw new MessagingException(
"TransactionMQProducer must have a TransactionListener !!! ");
}
((TransactionMQProducer) defaultMQProducer)
.setTransactionListener(transactionListener);
log.info("send transaction message :" + mqMessage);
sendResult = defaultMQProducer.sendMessageInTransaction(mqMessage,
message.getHeaders().get(RocketMQConst.USER_TRANSACTIONAL_ARGS));
}
else {
log.info("send message :" + mqMessage);
sendResult = this.send(mqMessage, this.messageQueueSelector,
message.getHeaders(), message);
}
if (sendResult == null
|| !SendStatus.SEND_OK.equals(sendResult.getSendStatus())) {
log.error("message send fail.SendStatus is not OK ");
this.doFail(message, new MessagingException(
"message send fail.SendStatus is not OK."));
}
}
catch (Exception e) {
log.error("RocketMQ Message hasn't been sent. Caused by " + e.getMessage(),
e);
this.doFail(message, e);
}
}
private SendResult send(org.apache.rocketmq.common.message.Message mqMessage,
MessageQueueSelector selector, Object args, Message<?> message)
throws RemotingException, MQClientException, InterruptedException,
MQBrokerException {
SendResult sendResult = new SendResult();
sendResult.setSendStatus(SendStatus.SEND_OK);
if (RocketMQProducerProperties.SendType.OneWay
.equalsName(mqProducerProperties.getSendType())) {
if (null != selector) {
defaultMQProducer.sendOneway(mqMessage, selector, args);
}
else {
defaultMQProducer.sendOneway(mqMessage);
}
return sendResult;
}
if (RocketMQProducerProperties.SendType.Sync
.equalsName(mqProducerProperties.getSendType())) {
if (null != selector) {
return defaultMQProducer.send(mqMessage, selector, args);
}
return defaultMQProducer.send(mqMessage);
}
if (RocketMQProducerProperties.SendType.Async
.equalsName(mqProducerProperties.getSendType())) {
if (null != selector) {
defaultMQProducer.send(mqMessage, selector, args,
this.getSendCallback(message));
}
else {
defaultMQProducer.send(mqMessage, this.getSendCallback(message));
}
return sendResult;
}
throw new MessagingException(
"message hasn't been sent,cause by : the SendType must be in this values[OneWay, Async, Sync]");
}
/**
* https://github.com/alibaba/spring-cloud-alibaba/issues/1408 .
* @param message message
* @return SendCallback
*/
private SendCallback getSendCallback(Message<?> message) {
SendCallback sendCallback = RocketMQBeanContainerCache
.getBean(mqProducerProperties.getSendCallBack(), SendCallback.class);
if (null == sendCallback) {
sendCallback = new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
}
@Override
public void onException(Throwable e) {
RocketMQProducerMessageHandler.this.doFail(message, e);
}
};
}
return sendCallback;
}
private void doFail(Message<?> message, Throwable e) {
if (getSendFailureChannel() != null) {
getSendFailureChannel().send(getErrorMessageStrategy().buildErrorMessage(e,
ErrorMessageUtils.getAttributeAccessor(message, message)));
}
else {
throw new MessagingException(message, e);
}
}
public MessageChannel getSendFailureChannel() {
return sendFailureChannel;
}
public void setSendFailureChannel(MessageChannel sendFailureChannel) {
this.sendFailureChannel = sendFailureChannel;
}
public ErrorMessageStrategy getErrorMessageStrategy() {
return errorMessageStrategy;
}
public void setErrorMessageStrategy(ErrorMessageStrategy errorMessageStrategy) {
this.errorMessageStrategy = errorMessageStrategy;
}
public PartitioningInterceptor getPartitioningInterceptor() {
return partitioningInterceptor;
}
public RocketMQProducerMessageHandler setPartitioningInterceptor(
PartitioningInterceptor partitioningInterceptor) {
this.partitioningInterceptor = partitioningInterceptor;
return this;
}
}

@ -16,8 +16,11 @@
package com.alibaba.cloud.stream.binder.rocketmq.metrics;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import org.springframework.context.Lifecycle;
/**
* @author Timur Valiev
* @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
@ -26,6 +29,8 @@ public class Instrumentation {
private final String name;
private Lifecycle actuator;
protected final AtomicBoolean started = new AtomicBoolean(false);
protected Exception startException = null;
@ -34,6 +39,19 @@ public class Instrumentation {
this.name = name;
}
public Instrumentation(String name, Lifecycle actuator) {
this.name = name;
this.actuator = actuator;
}
public Lifecycle getActuator() {
return actuator;
}
public void setActuator(Lifecycle actuator) {
this.actuator = actuator;
}
public boolean isDown() {
return startException != null;
}
@ -67,4 +85,21 @@ public class Instrumentation {
return startException;
}
@Override
public int hashCode() {
return Objects.hash(getName(), getActuator());
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Instrumentation that = (Instrumentation) o;
return name.equals(that.name) && actuator.equals(that.actuator);
}
}

@ -16,37 +16,37 @@
package com.alibaba.cloud.stream.binder.rocketmq.metrics;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
/**
* @author Timur Valiev
* @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
*/
public class InstrumentationManager {
public final class InstrumentationManager {
private final Map<String, Object> runtime = new ConcurrentHashMap<>();
private final Map<String, Instrumentation> healthInstrumentations = new HashMap<>();
public Set<Instrumentation> getHealthInstrumentations() {
return healthInstrumentations.entrySet().stream().map(Map.Entry::getValue)
.collect(Collectors.toSet());
private InstrumentationManager() {
}
public void addHealthInstrumentation(Instrumentation instrumentation) {
healthInstrumentations.put(instrumentation.getName(), instrumentation);
}
private static final Map<Integer, Instrumentation> HEALTH_INSTRUMENTATIONS = new HashMap<>();
public Instrumentation getHealthInstrumentation(String key) {
return healthInstrumentations.get(key);
public static Collection<Instrumentation> getHealthInstrumentations() {
return HEALTH_INSTRUMENTATIONS.values();
}
public Map<String, Object> getRuntime() {
return runtime;
public static void addHealthInstrumentation(Instrumentation instrumentation) {
if (null != instrumentation) {
HEALTH_INSTRUMENTATIONS.computeIfPresent(instrumentation.hashCode(),
(k, v) -> {
if (instrumentation.getActuator() != null) {
instrumentation.getActuator().stop();
}
throw new IllegalArgumentException(
"The current actuator exists, please confirm if there is a repeat operation!!!");
});
}
}
}

@ -16,86 +16,14 @@
package com.alibaba.cloud.stream.binder.rocketmq.properties;
import java.util.Arrays;
import java.util.List;
import com.alibaba.cloud.stream.binder.rocketmq.RocketMQBinderConstants;
import org.apache.rocketmq.common.MixAll;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @author Timur Valiev
* binding rocketMq properties.
*
* @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
*/
@ConfigurationProperties(prefix = "spring.cloud.stream.rocketmq.binder")
public class RocketMQBinderConfigurationProperties {
/**
* The name server list for rocketMQ.
*/
private List<String> nameServer = Arrays
.asList(RocketMQBinderConstants.DEFAULT_NAME_SERVER);
/**
* The property of "access-key".
*/
private String accessKey;
/**
* The property of "secret-key".
*/
private String secretKey;
/**
* Switch flag instance for message trace.
*/
private boolean enableMsgTrace = true;
/**
* The name value of message trace topic.If you don't config,you can use the default
* trace topic name.
*/
private String customizedTraceTopic = MixAll.RMQ_SYS_TRACE_TOPIC;
public List<String> getNameServer() {
return nameServer;
}
public void setNameServer(List<String> nameServer) {
this.nameServer = nameServer;
}
public String getAccessKey() {
return accessKey;
}
public void setAccessKey(String accessKey) {
this.accessKey = accessKey;
}
public String getSecretKey() {
return secretKey;
}
public void setSecretKey(String secretKey) {
this.secretKey = secretKey;
}
public boolean isEnableMsgTrace() {
return enableMsgTrace;
}
public void setEnableMsgTrace(boolean enableMsgTrace) {
this.enableMsgTrace = enableMsgTrace;
}
public String getCustomizedTraceTopic() {
return customizedTraceTopic;
}
public void setCustomizedTraceTopic(String customizedTraceTopic) {
this.customizedTraceTopic = customizedTraceTopic;
}
public class RocketMQBinderConfigurationProperties extends RocketMQCommonProperties {
}

@ -0,0 +1,202 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.stream.binder.rocketmq.properties;
import java.io.Serializable;
import org.apache.rocketmq.client.AccessChannel;
import org.apache.rocketmq.client.impl.factory.MQClientInstance;
import org.apache.rocketmq.remoting.netty.TlsSystemConfig;
/**
* @author zkzlx
*/
public class RocketMQCommonProperties implements Serializable {
private static final long serialVersionUID = -6724870154343284715L;
private boolean enabled = true;
private String nameServer;
/**
* The property of "access-key".
*/
private String accessKey;
/**
* The property of "secret-key".
*/
private String secretKey;
/**
* Consumers of the same role is required to have exactly same subscriptions and
* consumerGroup to correctly achieve load balance. It's required and needs to be
* globally unique. Producer group conceptually aggregates all producer instances of
* exactly same role, which is particularly important when transactional messages are
* involved. For non-transactional messages, it does not matter as long as it's unique
* per process. See <a href="http://rocketmq.apache.org/docs/core-concept/">here</a>
* for further discussion.
*/
private String group;
private String namespace;
private String accessChannel = AccessChannel.LOCAL.name();
/**
* Pulling topic information interval from the named server.
* see{@link MQClientInstance#startScheduledTask()},eg:ScheduledTask
* updateTopicRouteInfoFromNameServer.
*/
private int pollNameServerInterval = 1000 * 30;
/**
* Heartbeat interval in microseconds with message broker.
* see{@link MQClientInstance#startScheduledTask()},eg:ScheduledTask
* sendHeartbeatToAllBroker .
*/
private int heartbeatBrokerInterval = 1000 * 30;
/**
* Offset persistent interval for consumer.
* see{@link MQClientInstance#startScheduledTask()},eg:ScheduledTask
* sendHeartbeatToAllBroker .
*/
private int persistConsumerOffsetInterval = 1000 * 5;
private boolean vipChannelEnabled = false;
private boolean useTLS = TlsSystemConfig.tlsEnable;
private boolean enableMsgTrace = true;
private String customizedTraceTopic;
public boolean getEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getNameServer() {
return nameServer;
}
public void setNameServer(String nameServer) {
this.nameServer = nameServer;
}
public String getAccessKey() {
return accessKey;
}
public void setAccessKey(String accessKey) {
this.accessKey = accessKey;
}
public String getSecretKey() {
return secretKey;
}
public void setSecretKey(String secretKey) {
this.secretKey = secretKey;
}
public String getGroup() {
return group;
}
public void setGroup(String group) {
this.group = group;
}
public String getNamespace() {
return namespace;
}
public void setNamespace(String namespace) {
this.namespace = namespace;
}
public String getAccessChannel() {
return accessChannel;
}
public void setAccessChannel(String accessChannel) {
this.accessChannel = accessChannel;
}
public int getPollNameServerInterval() {
return pollNameServerInterval;
}
public void setPollNameServerInterval(int pollNameServerInterval) {
this.pollNameServerInterval = pollNameServerInterval;
}
public int getHeartbeatBrokerInterval() {
return heartbeatBrokerInterval;
}
public void setHeartbeatBrokerInterval(int heartbeatBrokerInterval) {
this.heartbeatBrokerInterval = heartbeatBrokerInterval;
}
public int getPersistConsumerOffsetInterval() {
return persistConsumerOffsetInterval;
}
public void setPersistConsumerOffsetInterval(int persistConsumerOffsetInterval) {
this.persistConsumerOffsetInterval = persistConsumerOffsetInterval;
}
public boolean getVipChannelEnabled() {
return vipChannelEnabled;
}
public void setVipChannelEnabled(boolean vipChannelEnabled) {
this.vipChannelEnabled = vipChannelEnabled;
}
public boolean getUseTLS() {
return useTLS;
}
public void setUseTLS(boolean useTLS) {
this.useTLS = useTLS;
}
public boolean getEnableMsgTrace() {
return enableMsgTrace;
}
public void setEnableMsgTrace(boolean enableMsgTrace) {
this.enableMsgTrace = enableMsgTrace;
}
public String getCustomizedTraceTopic() {
return customizedTraceTopic;
}
public void setCustomizedTraceTopic(String customizedTraceTopic) {
this.customizedTraceTopic = customizedTraceTopic;
}
}

@ -16,153 +16,441 @@
package com.alibaba.cloud.stream.binder.rocketmq.properties;
import java.util.Set;
import java.io.Serializable;
import com.alibaba.cloud.stream.binder.rocketmq.support.JacksonRocketMQHeaderMapper;
import org.apache.rocketmq.client.consumer.MQPushConsumer;
import org.apache.rocketmq.client.consumer.MessageSelector;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyContext;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.client.consumer.listener.MessageListenerOrderly;
import org.apache.rocketmq.client.impl.consumer.ConsumeMessageConcurrentlyService;
import org.apache.rocketmq.client.impl.consumer.ConsumeMessageOrderlyService;
import org.apache.rocketmq.common.UtilAll;
import org.apache.rocketmq.common.consumer.ConsumeFromWhere;
import org.apache.rocketmq.common.protocol.heartbeat.MessageModel;
/**
* @author Timur Valiev
* Extended consumer properties for RocketMQ binder.
*
* @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
*/
public class RocketMQConsumerProperties {
public class RocketMQConsumerProperties extends RocketMQCommonProperties {
/**
* using '||' to split tag {@link MQPushConsumer#subscribe(String, String)}.
* Message model defines the way how messages are delivered to each consumer clients.
* This field defaults to clustering.
*/
private String tags;
private String messageModel = MessageModel.CLUSTERING.getModeCN();
/**
* {@link MQPushConsumer#subscribe(String, MessageSelector)}
* {@link MessageSelector#bySql(String)}.
* Queue allocation algorithm specifying how message queues are allocated to each
* consumer clients.
*/
private String sql;
private String allocateMessageQueueStrategy;
/**
* {@link MessageModel#BROADCASTING}.
* The expressions include tags or SQL,as follow:
* <p/>
* tag: {@code tag1||tag2||tag3 }; sql: {@code 'color'='blue' AND 'price'>100 } .
* <p/>
* Determines whether there are specific characters "{@code ||}" in the expression to
* determine how the message is filtered,tags or SQL.
*/
private Boolean broadcasting = false;
private String subscription;
/**
* if orderly is true, using {@link MessageListenerOrderly} else if orderly if false,
* using {@link MessageListenerConcurrently}.
* Delay some time when exception occur .
*/
private Boolean orderly = false;
private long pullTimeDelayMillsWhenException = 1000;
/**
* for concurrently listener. message consume retry strategy. see
* {@link ConsumeConcurrentlyContext#delayLevelWhenNextConsume}. -1 means dlq(or
* discard, see {@link this#shouldRequeue}), others means requeue.
* Consuming point on consumer booting.
*
* There are three consuming points:
* <ul>
* <li><code>CONSUME_FROM_LAST_OFFSET</code>: consumer clients pick up where it
* stopped previously. If it were a newly booting up consumer client, according aging
* of the consumer group, there are two cases:
* <ol>
* <li>if the consumer group is created so recently that the earliest message being
* subscribed has yet expired, which means the consumer group represents a lately
* launched business, consuming will start from the very beginning;</li>
* <li>if the earliest message being subscribed has expired, consuming will start from
* the latest messages, meaning messages born prior to the booting timestamp would be
* ignored.</li>
* </ol>
* </li>
* <li><code>CONSUME_FROM_FIRST_OFFSET</code>: Consumer client will start from
* earliest messages available.</li>
* <li><code>CONSUME_FROM_TIMESTAMP</code>: Consumer client will start from specified
* timestamp, which means messages born prior to {@link #consumeTimestamp} will be
* ignored</li>
* </ul>
*/
private int delayLevelWhenNextConsume = 0;
private ConsumeFromWhere consumeFromWhere = ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET;
/**
* for orderly listener. next retry delay time.
* Backtracking consumption time with second precision. Time format is
* 20131223171201<br>
* Implying Seventeen twelve and 01 seconds on December 23, 2013 year<br>
* Default backtracking consumption time Half an hour ago.
*/
private long suspendCurrentQueueTimeMillis = 1000;
private String consumeTimestamp = UtilAll
.timeMillisToHumanString3(System.currentTimeMillis() - (1000 * 60 * 30));
private Boolean enabled = true;
/**
* Flow control threshold on queue level, each message queue will cache at most 1000
* messages by default, Consider the {@link #pullBatchSize}, the instantaneous value
* may exceed the limit .
*/
private int pullThresholdForQueue = 1000;
/**
* Limit the cached message size on queue level, each message queue will cache at most
* 100 MiB messages by default, Consider the {@link #pullBatchSize}, the instantaneous
* value may exceed the limit .
*
* <p>
* The size of a message only measured by message body, so it's not accurate
*/
private int pullThresholdSizeForQueue = 100;
/**
* {@link JacksonRocketMQHeaderMapper#addTrustedPackages(String...)}.
* Maximum number of messages pulled each time.
*/
private Set<String> trustedPackages;
private int pullBatchSize = 10;
// ------------ For Pull Consumer ------------
/**
* Consume max span offset.it has no effect on sequential consumption.
*/
private int consumeMaxSpan = 2000;
private long pullTimeout = 10 * 1000;
private Push push = new Push();
private boolean fromStore;
private Pull pull = new Pull();
// ------------ For Pull Consumer ------------
public String getMessageModel() {
return messageModel;
}
public String getTags() {
return tags;
public RocketMQConsumerProperties setMessageModel(String messageModel) {
this.messageModel = messageModel;
return this;
}
public void setTags(String tags) {
this.tags = tags;
public String getAllocateMessageQueueStrategy() {
return allocateMessageQueueStrategy;
}
public String getSql() {
return sql;
public void setAllocateMessageQueueStrategy(String allocateMessageQueueStrategy) {
this.allocateMessageQueueStrategy = allocateMessageQueueStrategy;
}
public void setSql(String sql) {
this.sql = sql;
public String getSubscription() {
return subscription;
}
public Boolean getOrderly() {
return orderly;
public void setSubscription(String subscription) {
this.subscription = subscription;
}
public void setOrderly(Boolean orderly) {
this.orderly = orderly;
public Push getPush() {
return push;
}
public Boolean getEnabled() {
return enabled;
public void setPush(Push push) {
this.push = push;
}
public void setEnabled(Boolean enabled) {
this.enabled = enabled;
public long getPullTimeDelayMillsWhenException() {
return pullTimeDelayMillsWhenException;
}
public Boolean getBroadcasting() {
return broadcasting;
public RocketMQConsumerProperties setPullTimeDelayMillsWhenException(
long pullTimeDelayMillsWhenException) {
this.pullTimeDelayMillsWhenException = pullTimeDelayMillsWhenException;
return this;
}
public void setBroadcasting(Boolean broadcasting) {
this.broadcasting = broadcasting;
public ConsumeFromWhere getConsumeFromWhere() {
return consumeFromWhere;
}
public int getDelayLevelWhenNextConsume() {
return delayLevelWhenNextConsume;
public RocketMQConsumerProperties setConsumeFromWhere(
ConsumeFromWhere consumeFromWhere) {
this.consumeFromWhere = consumeFromWhere;
return this;
}
public void setDelayLevelWhenNextConsume(int delayLevelWhenNextConsume) {
this.delayLevelWhenNextConsume = delayLevelWhenNextConsume;
public String getConsumeTimestamp() {
return consumeTimestamp;
}
public long getSuspendCurrentQueueTimeMillis() {
return suspendCurrentQueueTimeMillis;
public RocketMQConsumerProperties setConsumeTimestamp(String consumeTimestamp) {
this.consumeTimestamp = consumeTimestamp;
return this;
}
public void setSuspendCurrentQueueTimeMillis(long suspendCurrentQueueTimeMillis) {
this.suspendCurrentQueueTimeMillis = suspendCurrentQueueTimeMillis;
public int getPullThresholdForQueue() {
return pullThresholdForQueue;
}
public long getPullTimeout() {
return pullTimeout;
public RocketMQConsumerProperties setPullThresholdForQueue(
int pullThresholdForQueue) {
this.pullThresholdForQueue = pullThresholdForQueue;
return this;
}
public void setPullTimeout(long pullTimeout) {
this.pullTimeout = pullTimeout;
public int getPullThresholdSizeForQueue() {
return pullThresholdSizeForQueue;
}
public boolean isFromStore() {
return fromStore;
public RocketMQConsumerProperties setPullThresholdSizeForQueue(
int pullThresholdSizeForQueue) {
this.pullThresholdSizeForQueue = pullThresholdSizeForQueue;
return this;
}
public void setFromStore(boolean fromStore) {
this.fromStore = fromStore;
public int getPullBatchSize() {
return pullBatchSize;
}
public boolean shouldRequeue() {
return delayLevelWhenNextConsume != -1;
public RocketMQConsumerProperties setPullBatchSize(int pullBatchSize) {
this.pullBatchSize = pullBatchSize;
return this;
}
public Set<String> getTrustedPackages() {
return trustedPackages;
public Pull getPull() {
return pull;
}
public void setTrustedPackages(Set<String> trustedPackages) {
this.trustedPackages = trustedPackages;
public RocketMQConsumerProperties setPull(Pull pull) {
this.pull = pull;
return this;
}
public int getConsumeMaxSpan() {
return consumeMaxSpan;
}
public RocketMQConsumerProperties setConsumeMaxSpan(int consumeMaxSpan) {
this.consumeMaxSpan = consumeMaxSpan;
return this;
}
public static class Push implements Serializable {
private static final long serialVersionUID = -7398468554978817630L;
/**
* if orderly is true, using {@link MessageListenerOrderly} else if orderly if
* false, using {@link MessageListenerConcurrently}.
*/
private boolean orderly = false;
/**
* Suspending pulling time for cases requiring slow pulling like flow-control
* scenario. see{@link ConsumeMessageOrderlyService#processConsumeResult}.
* see{@link ConsumeOrderlyContext#getSuspendCurrentQueueTimeMillis}.
*/
private int suspendCurrentQueueTimeMillis = 1000;
/**
* https://github.com/alibaba/spring-cloud-alibaba/issues/1866 Max re-consume
* times. -1 means 16 times. If messages are re-consumed more than
* {@link #maxReconsumeTimes} before success, it's be directed to a deletion queue
* waiting.
*/
private int maxReconsumeTimes;
/**
* for concurrently listener. message consume retry strategy. -1 means dlq(or
* discard. see {@link ConsumeMessageConcurrentlyService#processConsumeResult}.
* see {@link ConsumeConcurrentlyContext#getDelayLevelWhenNextConsume}.
*/
private int delayLevelWhenNextConsume = 0;
/**
* Flow control threshold on topic level, default value is -1(Unlimited)
* <p>
* The value of {@code pullThresholdForQueue} will be overwrote and calculated
* based on {@code pullThresholdForTopic} if it is't unlimited
* <p>
* For example, if the value of pullThresholdForTopic is 1000 and 10 message
* queues are assigned to this consumer, then pullThresholdForQueue will be set to
* 100.
*/
private int pullThresholdForTopic = -1;
/**
* Limit the cached message size on topic level, default value is -1
* MiB(Unlimited)
* <p>
* The value of {@code pullThresholdSizeForQueue} will be overwrote and calculated
* based on {@code pullThresholdSizeForTopic} if it is't unlimited .
* <p>
* For example, if the value of pullThresholdSizeForTopic is 1000 MiB and 10
* message queues are assigned to this consumer, then pullThresholdSizeForQueue
* will be set to 100 MiB .
*/
private int pullThresholdSizeForTopic = -1;
/**
* Message pull Interval.
*/
private long pullInterval = 0;
/**
* Batch consumption size.
*/
private int consumeMessageBatchMaxSize = 1;
public boolean getOrderly() {
return orderly;
}
public void setOrderly(boolean orderly) {
this.orderly = orderly;
}
public int getSuspendCurrentQueueTimeMillis() {
return suspendCurrentQueueTimeMillis;
}
public void setSuspendCurrentQueueTimeMillis(int suspendCurrentQueueTimeMillis) {
this.suspendCurrentQueueTimeMillis = suspendCurrentQueueTimeMillis;
}
public int getMaxReconsumeTimes() {
return maxReconsumeTimes;
}
public void setMaxReconsumeTimes(int maxReconsumeTimes) {
this.maxReconsumeTimes = maxReconsumeTimes;
}
public int getDelayLevelWhenNextConsume() {
return delayLevelWhenNextConsume;
}
public void setDelayLevelWhenNextConsume(int delayLevelWhenNextConsume) {
this.delayLevelWhenNextConsume = delayLevelWhenNextConsume;
}
public int getPullThresholdForTopic() {
return pullThresholdForTopic;
}
public void setPullThresholdForTopic(int pullThresholdForTopic) {
this.pullThresholdForTopic = pullThresholdForTopic;
}
public int getPullThresholdSizeForTopic() {
return pullThresholdSizeForTopic;
}
public void setPullThresholdSizeForTopic(int pullThresholdSizeForTopic) {
this.pullThresholdSizeForTopic = pullThresholdSizeForTopic;
}
public long getPullInterval() {
return pullInterval;
}
public void setPullInterval(long pullInterval) {
this.pullInterval = pullInterval;
}
public int getConsumeMessageBatchMaxSize() {
return consumeMessageBatchMaxSize;
}
public void setConsumeMessageBatchMaxSize(int consumeMessageBatchMaxSize) {
this.consumeMessageBatchMaxSize = consumeMessageBatchMaxSize;
}
}
public static class Pull implements Serializable {
/**
* The poll timeout in milliseconds.
*/
private long pollTimeoutMillis = 1000 * 5;
/**
* Pull thread number.
*/
private int pullThreadNums = 20;
/**
* Interval time in in milliseconds for checking changes in topic metadata.
*/
private long topicMetadataCheckIntervalMillis = 30 * 1000;
/**
* Long polling mode, the Consumer connection timeout(must greater than
* brokerSuspendMaxTimeMillis), it is not recommended to modify.
*/
private long consumerTimeoutMillisWhenSuspend = 1000 * 30;
/**
* Ack state handling, including receive, reject, and retry, when a consumption
* exception occurs.
*/
private String errAcknowledge;
private long pullThresholdForAll = 1000L;
public long getPollTimeoutMillis() {
return pollTimeoutMillis;
}
public void setPollTimeoutMillis(long pollTimeoutMillis) {
this.pollTimeoutMillis = pollTimeoutMillis;
}
public int getPullThreadNums() {
return pullThreadNums;
}
public void setPullThreadNums(int pullThreadNums) {
this.pullThreadNums = pullThreadNums;
}
public long getTopicMetadataCheckIntervalMillis() {
return topicMetadataCheckIntervalMillis;
}
public void setTopicMetadataCheckIntervalMillis(
long topicMetadataCheckIntervalMillis) {
this.topicMetadataCheckIntervalMillis = topicMetadataCheckIntervalMillis;
}
public long getConsumerTimeoutMillisWhenSuspend() {
return consumerTimeoutMillisWhenSuspend;
}
public void setConsumerTimeoutMillisWhenSuspend(
long consumerTimeoutMillisWhenSuspend) {
this.consumerTimeoutMillisWhenSuspend = consumerTimeoutMillisWhenSuspend;
}
public String getErrAcknowledge() {
return errAcknowledge;
}
public void setErrAcknowledge(String errAcknowledge) {
this.errAcknowledge = errAcknowledge;
}
public long getPullThresholdForAll() {
return pullThresholdForAll;
}
public void setPullThresholdForAll(long pullThresholdForAll) {
this.pullThresholdForAll = pullThresholdForAll;
}
}
}

@ -21,12 +21,14 @@ import org.springframework.cloud.stream.binder.AbstractExtendedBindingProperties
import org.springframework.cloud.stream.binder.BinderSpecificPropertiesProvider;
/**
* @author Timur Valiev
* rocketMQ specific extended binding properties class that extends from
* {@link AbstractExtendedBindingProperties}.
*
* @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
*/
@ConfigurationProperties("spring.cloud.stream.rocketmq")
public class RocketMQExtendedBindingProperties extends
AbstractExtendedBindingProperties<RocketMQConsumerProperties, RocketMQProducerProperties, RocketMQBindingProperties> {
AbstractExtendedBindingProperties<RocketMQConsumerProperties, RocketMQProducerProperties, RocketMQSpecificPropertiesProvider> {
private static final String DEFAULTS_PREFIX = "spring.cloud.stream.rocketmq.default";
@ -37,7 +39,7 @@ public class RocketMQExtendedBindingProperties extends
@Override
public Class<? extends BinderSpecificPropertiesProvider> getExtendedPropertiesEntryClass() {
return RocketMQBindingProperties.class;
return RocketMQSpecificPropertiesProvider.class;
}
}

@ -16,55 +16,37 @@
package com.alibaba.cloud.stream.binder.rocketmq.properties;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
/**
* @author Timur Valiev
* Extended producer properties for RocketMQ binder.
*
* @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
*/
public class RocketMQProducerProperties {
private Boolean enabled = true;
public class RocketMQProducerProperties extends RocketMQCommonProperties {
/**
* Name of producer.
* Timeout for sending messages.
*/
private String group;
/**
* Maximum allowed message size in bytes {@link DefaultMQProducer#maxMessageSize}.
*/
private Integer maxMessageSize = 1024 * 1024 * 4;
private Boolean transactional = false;
private Boolean sync = false;
private Boolean vipChannelEnabled = true;
/**
* Millis of send message timeout.
*/
private int sendMessageTimeout = 3000;
private int sendMsgTimeout = 3000;
/**
* Compress message body threshold, namely, message body larger than 4k will be
* compressed on default.
*/
private int compressMessageBodyThreshold = 1024 * 4;
private int compressMsgBodyThreshold = 1024 * 4;
/**
* Maximum number of retry to perform internally before claiming sending failure in
* synchronous mode. This may potentially cause message duplication which is up to
* application developers to resolve.
* synchronous mode.
*
* This may potentially cause message duplication which is up to application
* developers to resolve.
*/
private int retryTimesWhenSendFailed = 2;
/**
* <p>
* Maximum number of retry to perform internally before claiming sending failure in
* asynchronous mode.
* </p>
*
* This may potentially cause message duplication which is up to application
* developers to resolve.
*/
@ -73,94 +55,187 @@ public class RocketMQProducerProperties {
/**
* Indicate whether to retry another broker on sending failure internally.
*/
private boolean retryNextServer = false;
private boolean retryAnotherBroker = false;
/**
* Maximum allowed message size in bytes.
*/
private int maxMessageSize = 1024 * 1024 * 4;
private String producerType = ProducerType.Normal.name();
private String sendType = SendType.Sync.name();
private String sendCallBack;
private String transactionListener;
private String messageQueueSelector;
private String errorMessageStrategy;
private String sendFailureChannel;
private String checkForbiddenHook;
private String sendMessageHook;
public int getSendMsgTimeout() {
return sendMsgTimeout;
}
public void setSendMsgTimeout(int sendMsgTimeout) {
this.sendMsgTimeout = sendMsgTimeout;
}
public int getCompressMsgBodyThreshold() {
return compressMsgBodyThreshold;
}
public void setCompressMsgBodyThreshold(int compressMsgBodyThreshold) {
this.compressMsgBodyThreshold = compressMsgBodyThreshold;
}
public String getGroup() {
return group;
public int getRetryTimesWhenSendFailed() {
return retryTimesWhenSendFailed;
}
public void setGroup(String group) {
this.group = group;
public void setRetryTimesWhenSendFailed(int retryTimesWhenSendFailed) {
this.retryTimesWhenSendFailed = retryTimesWhenSendFailed;
}
public Boolean getEnabled() {
return enabled;
public int getRetryTimesWhenSendAsyncFailed() {
return retryTimesWhenSendAsyncFailed;
}
public void setEnabled(Boolean enabled) {
this.enabled = enabled;
public void setRetryTimesWhenSendAsyncFailed(int retryTimesWhenSendAsyncFailed) {
this.retryTimesWhenSendAsyncFailed = retryTimesWhenSendAsyncFailed;
}
public Integer getMaxMessageSize() {
public boolean getRetryAnotherBroker() {
return retryAnotherBroker;
}
public void setRetryAnotherBroker(boolean retryAnotherBroker) {
this.retryAnotherBroker = retryAnotherBroker;
}
public int getMaxMessageSize() {
return maxMessageSize;
}
public void setMaxMessageSize(Integer maxMessageSize) {
public void setMaxMessageSize(int maxMessageSize) {
this.maxMessageSize = maxMessageSize;
}
public Boolean getTransactional() {
return transactional;
public String getProducerType() {
return producerType;
}
public void setTransactional(Boolean transactional) {
this.transactional = transactional;
public void setProducerType(String producerType) {
this.producerType = producerType;
}
public Boolean getSync() {
return sync;
public String getSendType() {
return sendType;
}
public void setSync(Boolean sync) {
this.sync = sync;
public void setSendType(String sendType) {
this.sendType = sendType;
}
public Boolean getVipChannelEnabled() {
return vipChannelEnabled;
public String getSendCallBack() {
return sendCallBack;
}
public void setVipChannelEnabled(Boolean vipChannelEnabled) {
this.vipChannelEnabled = vipChannelEnabled;
public void setSendCallBack(String sendCallBack) {
this.sendCallBack = sendCallBack;
}
public int getSendMessageTimeout() {
return sendMessageTimeout;
public String getTransactionListener() {
return transactionListener;
}
public void setSendMessageTimeout(int sendMessageTimeout) {
this.sendMessageTimeout = sendMessageTimeout;
public void setTransactionListener(String transactionListener) {
this.transactionListener = transactionListener;
}
public int getCompressMessageBodyThreshold() {
return compressMessageBodyThreshold;
public String getMessageQueueSelector() {
return messageQueueSelector;
}
public void setCompressMessageBodyThreshold(int compressMessageBodyThreshold) {
this.compressMessageBodyThreshold = compressMessageBodyThreshold;
public void setMessageQueueSelector(String messageQueueSelector) {
this.messageQueueSelector = messageQueueSelector;
}
public int getRetryTimesWhenSendFailed() {
return retryTimesWhenSendFailed;
public String getErrorMessageStrategy() {
return errorMessageStrategy;
}
public void setRetryTimesWhenSendFailed(int retryTimesWhenSendFailed) {
this.retryTimesWhenSendFailed = retryTimesWhenSendFailed;
public void setErrorMessageStrategy(String errorMessageStrategy) {
this.errorMessageStrategy = errorMessageStrategy;
}
public int getRetryTimesWhenSendAsyncFailed() {
return retryTimesWhenSendAsyncFailed;
public String getSendFailureChannel() {
return sendFailureChannel;
}
public void setRetryTimesWhenSendAsyncFailed(int retryTimesWhenSendAsyncFailed) {
this.retryTimesWhenSendAsyncFailed = retryTimesWhenSendAsyncFailed;
public void setSendFailureChannel(String sendFailureChannel) {
this.sendFailureChannel = sendFailureChannel;
}
public String getCheckForbiddenHook() {
return checkForbiddenHook;
}
public void setCheckForbiddenHook(String checkForbiddenHook) {
this.checkForbiddenHook = checkForbiddenHook;
}
public String getSendMessageHook() {
return sendMessageHook;
}
public boolean isRetryNextServer() {
return retryNextServer;
public void setSendMessageHook(String sendMessageHook) {
this.sendMessageHook = sendMessageHook;
}
public void setRetryNextServer(boolean retryNextServer) {
this.retryNextServer = retryNextServer;
public enum ProducerType {
/**
* Is not a transaction.
*/
Normal,
/**
* a transaction.
*/
Trans;
public boolean equalsName(String name) {
return this.name().equalsIgnoreCase(name);
}
}
public enum SendType {
/**
* one way.
*/
OneWay,
/**
* Asynchronization Model.
*/
Async,
/**
* synchronization.
*/
Sync,;
public boolean equalsName(String name) {
return this.name().equalsIgnoreCase(name);
}
}
}

@ -19,27 +19,44 @@ package com.alibaba.cloud.stream.binder.rocketmq.properties;
import org.springframework.cloud.stream.binder.BinderSpecificPropertiesProvider;
/**
* @author Timur Valiev
* Container object for RocketMQ specific extended producer and consumer binding
* properties.
*
* @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
*/
public class RocketMQBindingProperties implements BinderSpecificPropertiesProvider {
public class RocketMQSpecificPropertiesProvider
implements BinderSpecificPropertiesProvider {
/**
* Consumer specific binding properties. @see {@link RocketMQConsumerProperties}.
*/
private RocketMQConsumerProperties consumer = new RocketMQConsumerProperties();
/**
* Producer specific binding properties. @see {@link RocketMQProducerProperties}.
*/
private RocketMQProducerProperties producer = new RocketMQProducerProperties();
/**
* @return {@link RocketMQConsumerProperties} Consumer specific binding
* properties. @see {@link RocketMQConsumerProperties}.
*/
@Override
public RocketMQConsumerProperties getConsumer() {
return consumer;
return this.consumer;
}
public void setConsumer(RocketMQConsumerProperties consumer) {
this.consumer = consumer;
}
/**
* @return {@link RocketMQProducerProperties} Producer specific binding
* properties. @see {@link RocketMQProducerProperties}.
*/
@Override
public RocketMQProducerProperties getProducer() {
return producer;
return this.producer;
}
public void setProducer(RocketMQProducerProperties producer) {

@ -36,7 +36,7 @@ public class PartitionMessageQueueSelector implements MessageQueueSelector {
@Override
public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
Integer partition = 0;
int partition = 0;
try {
partition = Math.abs(
Integer.parseInt(msg.getProperty(BinderHeaders.PARTITION_HEADER)));

@ -0,0 +1,187 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.stream.binder.rocketmq.support;
import java.nio.charset.Charset;
import java.util.Map;
import java.util.Objects;
import com.alibaba.cloud.stream.binder.rocketmq.contants.RocketMQConst;
import com.alibaba.cloud.stream.binder.rocketmq.contants.RocketMQConst.Headers;
import com.alibaba.cloud.stream.binder.rocketmq.convert.RocketMQMessageConverter;
import com.alibaba.cloud.stream.binder.rocketmq.custom.RocketMQBeanContainerCache;
import org.apache.rocketmq.common.message.MessageConst;
import org.apache.rocketmq.common.message.MessageExt;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.converter.CompositeMessageConverter;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.util.CollectionUtils;
import org.springframework.util.MimeTypeUtils;
import org.springframework.util.StringUtils;
/**
* @author zkzlx
*/
public final class RocketMQMessageConverterSupport {
private RocketMQMessageConverterSupport() {
}
private static final CompositeMessageConverter MESSAGE_CONVERTER = RocketMQBeanContainerCache
.getBean(RocketMQMessageConverter.DEFAULT_NAME,
CompositeMessageConverter.class,
new RocketMQMessageConverter().getMessageConverter());
public static Message convertMessage2Spring(MessageExt message) {
MessageBuilder messageBuilder = MessageBuilder.withPayload(message.getBody())
.setHeader(toRocketHeaderKey(Headers.KEYS), message.getKeys())
.setHeader(toRocketHeaderKey(Headers.TAGS), message.getTags())
.setHeader(toRocketHeaderKey(Headers.TOPIC), message.getTopic())
.setHeader(toRocketHeaderKey(Headers.MESSAGE_ID), message.getMsgId())
.setHeader(toRocketHeaderKey(Headers.BORN_TIMESTAMP),
message.getBornTimestamp())
.setHeader(toRocketHeaderKey(Headers.BORN_HOST),
message.getBornHostString())
.setHeader(toRocketHeaderKey(Headers.FLAG), message.getFlag())
.setHeader(toRocketHeaderKey(Headers.QUEUE_ID), message.getQueueId())
.setHeader(toRocketHeaderKey(Headers.SYS_FLAG), message.getSysFlag())
.setHeader(toRocketHeaderKey(Headers.TRANSACTION_ID),
message.getTransactionId());
addUserProperties(message.getProperties(), messageBuilder);
return messageBuilder.build();
}
public static String toRocketHeaderKey(String rawKey) {
return "ROCKET_" + rawKey;
}
private static void addUserProperties(Map<String, String> properties,
MessageBuilder messageBuilder) {
if (!CollectionUtils.isEmpty(properties)) {
properties.forEach((key, val) -> {
if (!MessageConst.STRING_HASH_SET.contains(key)
&& !MessageHeaders.ID.equals(key)
&& !MessageHeaders.TIMESTAMP.equals(key)) {
messageBuilder.setHeader(key, val);
}
});
}
}
public static org.apache.rocketmq.common.message.Message convertMessage2MQ(
String destination, Message<?> source) {
Message<?> message = MESSAGE_CONVERTER.toMessage(source.getPayload(),
source.getHeaders());
assert message != null;
MessageBuilder<?> builder = MessageBuilder.fromMessage(message);
builder.setHeaderIfAbsent(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.TEXT_PLAIN);
message = builder.build();
return doConvert(destination, message);
}
private static org.apache.rocketmq.common.message.Message doConvert(String topic,
Message<?> message) {
Charset charset = Charset.defaultCharset();
Object payloadObj = message.getPayload();
byte[] payloads;
try {
if (payloadObj instanceof String) {
payloads = ((String) payloadObj).getBytes(charset);
}
else if (payloadObj instanceof byte[]) {
payloads = (byte[]) message.getPayload();
}
else {
String jsonObj = (String) MESSAGE_CONVERTER.fromMessage(message,
payloadObj.getClass());
if (null == jsonObj) {
throw new RuntimeException(String.format(
"empty after conversion [messageConverter:%s,payloadClass:%s,payloadObj:%s]",
MESSAGE_CONVERTER.getClass(), payloadObj.getClass(),
payloadObj));
}
payloads = jsonObj.getBytes(charset);
}
}
catch (Exception e) {
throw new RuntimeException("convert to RocketMQ message failed.", e);
}
return getAndWrapMessage(topic, message.getHeaders(), payloads);
}
private static org.apache.rocketmq.common.message.Message getAndWrapMessage(
String topic, MessageHeaders headers, byte[] payloads) {
if (topic == null || topic.length() < 1) {
return null;
}
if (payloads == null || payloads.length < 1) {
return null;
}
org.apache.rocketmq.common.message.Message rocketMsg = new org.apache.rocketmq.common.message.Message(
topic, payloads);
if (Objects.nonNull(headers) && !headers.isEmpty()) {
Object tag = headers.getOrDefault(Headers.TAGS,
headers.get(toRocketHeaderKey(Headers.TAGS)));
if (!StringUtils.isEmpty(tag)) {
rocketMsg.setTags(String.valueOf(tag));
}
Object keys = headers.getOrDefault(Headers.KEYS,
headers.get(toRocketHeaderKey(Headers.KEYS)));
if (!StringUtils.isEmpty(keys)) {
rocketMsg.setKeys(keys.toString());
}
Object flagObj = headers.getOrDefault(Headers.FLAG,
headers.get(toRocketHeaderKey(Headers.FLAG)));
int flag = 0;
int delayLevel = 0;
try {
flagObj = flagObj == null ? 0 : flagObj;
Object delayLevelObj = headers.getOrDefault(
RocketMQConst.PROPERTY_DELAY_TIME_LEVEL,
headers.get(toRocketHeaderKey(
RocketMQConst.PROPERTY_DELAY_TIME_LEVEL)));
delayLevelObj = delayLevelObj == null ? 0 : delayLevelObj;
delayLevel = Integer.parseInt(String.valueOf(delayLevelObj));
flag = Integer.parseInt(String.valueOf(flagObj));
}
catch (Exception ignored) {
}
if (delayLevel > 0) {
rocketMsg.setDelayTimeLevel(delayLevel);
}
rocketMsg.setFlag(flag);
Object waitStoreMsgOkObj = headers
.getOrDefault(RocketMQConst.PROPERTY_WAIT_STORE_MSG_OK, "true");
rocketMsg.setWaitStoreMsgOK(
Boolean.parseBoolean(String.valueOf(waitStoreMsgOkObj)));
headers.entrySet().stream()
.filter(entry -> !Objects.equals(entry.getKey(), Headers.FLAG))
.forEach(entry -> {
if (!MessageConst.STRING_HASH_SET.contains(entry.getKey())) {
rocketMsg.putUserProperty(entry.getKey(),
String.valueOf(entry.getValue()));
}
});
}
return rocketMsg;
}
}

@ -0,0 +1,100 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.stream.binder.rocketmq.utils;
import com.alibaba.cloud.stream.binder.rocketmq.contants.RocketMQConst;
import com.alibaba.cloud.stream.binder.rocketmq.properties.RocketMQBinderConfigurationProperties;
import com.alibaba.cloud.stream.binder.rocketmq.properties.RocketMQCommonProperties;
import org.apache.rocketmq.acl.common.AclClientRPCHook;
import org.apache.rocketmq.acl.common.SessionCredentials;
import org.apache.rocketmq.client.consumer.MessageSelector;
import org.apache.rocketmq.common.UtilAll;
import org.apache.rocketmq.remoting.RPCHook;
import org.springframework.util.StringUtils;
/**
* @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
*/
public final class RocketMQUtils {
private RocketMQUtils() {
}
public static <T extends RocketMQCommonProperties> T mergeRocketMQProperties(
RocketMQBinderConfigurationProperties binderConfigurationProperties,
T mqProperties) {
if (null == binderConfigurationProperties || mqProperties == null) {
return mqProperties;
}
if (StringUtils.isEmpty(mqProperties.getNameServer())) {
mqProperties.setNameServer(binderConfigurationProperties.getNameServer());
}
if (StringUtils.isEmpty(mqProperties.getSecretKey())) {
mqProperties.setSecretKey(binderConfigurationProperties.getSecretKey());
}
if (StringUtils.isEmpty(mqProperties.getAccessKey())) {
mqProperties.setAccessKey(binderConfigurationProperties.getAccessKey());
}
if (StringUtils.isEmpty(mqProperties.getAccessChannel())) {
mqProperties
.setAccessChannel(binderConfigurationProperties.getAccessChannel());
}
if (StringUtils.isEmpty(mqProperties.getNamespace())) {
mqProperties.setNamespace(binderConfigurationProperties.getNamespace());
}
if (StringUtils.isEmpty(mqProperties.getGroup())) {
mqProperties.setGroup(binderConfigurationProperties.getGroup());
}
if (StringUtils.isEmpty(mqProperties.getCustomizedTraceTopic())) {
mqProperties.setCustomizedTraceTopic(
binderConfigurationProperties.getCustomizedTraceTopic());
}
mqProperties.setNameServer(getNameServerStr(mqProperties.getNameServer()));
return mqProperties;
}
public static String getInstanceName(RPCHook rpcHook, String identify) {
String separator = "|";
StringBuilder instanceName = new StringBuilder();
if (null != rpcHook) {
SessionCredentials sessionCredentials = ((AclClientRPCHook) rpcHook)
.getSessionCredentials();
instanceName.append(sessionCredentials.getAccessKey()).append(separator)
.append(sessionCredentials.getSecretKey()).append(separator);
}
instanceName.append(identify).append(separator).append(UtilAll.getPid());
return instanceName.toString();
}
public static String getNameServerStr(String nameServer) {
if (StringUtils.isEmpty(nameServer)) {
return RocketMQConst.DEFAULT_NAME_SERVER;
}
return nameServer.replaceAll(",", ";");
}
private static final String SQL = "sql:";
public static MessageSelector getMessageSelector(String expression) {
if (StringUtils.hasText(expression) && expression.startsWith(SQL)) {
return MessageSelector.bySql(expression.replaceFirst(SQL, ""));
}
return MessageSelector.byTag(expression);
}
}

@ -1 +1 @@
rocketmq:com.alibaba.cloud.stream.binder.rocketmq.config.RocketMQBinderAutoConfiguration
rocketmq:com.alibaba.cloud.stream.binder.rocketmq.autoconfigurate.RocketMQBinderAutoConfiguration

@ -1,2 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.alibaba.cloud.stream.binder.rocketmq.config.RocketMQComponent4BinderAutoConfiguration
com.alibaba.cloud.stream.binder.rocketmq.autoconfigurate.ExtendedBindingHandlerMappingsProviderConfiguration

@ -16,9 +16,7 @@
package com.alibaba.cloud.stream.binder.rocketmq;
import java.util.Arrays;
import com.alibaba.cloud.stream.binder.rocketmq.config.RocketMQBinderAutoConfiguration;
import com.alibaba.cloud.stream.binder.rocketmq.autoconfigurate.RocketMQBinderAutoConfiguration;
import com.alibaba.cloud.stream.binder.rocketmq.properties.RocketMQBinderConfigurationProperties;
import com.alibaba.cloud.stream.binder.rocketmq.properties.RocketMQExtendedBindingProperties;
import org.junit.Test;
@ -37,20 +35,20 @@ public class RocketMQAutoConfigurationTests {
.withConfiguration(
AutoConfigurations.of(RocketMQBinderAutoConfiguration.class))
.withPropertyValues(
"spring.cloud.stream.rocketmq.binder.name-server[0]=127.0.0.1:9876",
"spring.cloud.stream.rocketmq.binder.name-server[1]=127.0.0.1:9877",
"spring.cloud.stream.rocketmq.binder.name-server=127.0.0.1:9876,127.0.0.1:9877",
"spring.cloud.stream.bindings.output.destination=TopicOrderTest",
"spring.cloud.stream.bindings.output.content-type=application/json",
"spring.cloud.stream.bindings.input1.destination=TopicOrderTest",
"spring.cloud.stream.bindings.input1.content-type=application/json",
"spring.cloud.stream.bindings.input1.group=test-group1",
"spring.cloud.stream.rocketmq.bindings.input1.consumer.orderly=true",
"spring.cloud.stream.rocketmq.bindings.input1.consumer.push.orderly=true",
"spring.cloud.stream.bindings.input1.consumer.maxAttempts=1",
"spring.cloud.stream.bindings.input2.destination=TopicOrderTest",
"spring.cloud.stream.bindings.input2.content-type=application/json",
"spring.cloud.stream.bindings.input2.group=test-group2",
"spring.cloud.stream.rocketmq.bindings.input2.consumer.orderly=false",
"spring.cloud.stream.rocketmq.bindings.input2.consumer.tags=tag1");
"spring.cloud.stream.rocketmq.bindings.input2.consumer.push.orderly=false",
"spring.cloud.stream.rocketmq.bindings.input2.consumer.subscription=tag1");
@Test
public void testProperties() {
@ -58,15 +56,14 @@ public class RocketMQAutoConfigurationTests {
RocketMQBinderConfigurationProperties binderConfigurationProperties = context
.getBean(RocketMQBinderConfigurationProperties.class);
assertThat(binderConfigurationProperties.getNameServer())
.isEqualTo(Arrays.asList("127.0.0.1:9876", "127.0.0.1:9877"));
.isEqualTo("127.0.0.1:9876,127.0.0.1:9877");
RocketMQExtendedBindingProperties bindingProperties = context
.getBean(RocketMQExtendedBindingProperties.class);
assertThat(
bindingProperties.getExtendedConsumerProperties("input2").getTags())
.isEqualTo("tag1");
assertThat(bindingProperties.getExtendedConsumerProperties("input2")
.getSubscription()).isEqualTo("tag1");
assertThat(bindingProperties.getExtendedConsumerProperties("input2").getPush()
.getOrderly()).isFalse();
assertThat(bindingProperties.getExtendedConsumerProperties("input1")
assertThat(bindingProperties.getExtendedConsumerProperties("input1").getPush()
.getOrderly()).isTrue();
});
}

Loading…
Cancel
Save