Merge pull request #2013 from zkzlx/rocketmq
[major update]many features enhanced & many bugs fixedpull/2102/head 2.2.5-RocketMQ-RC1
commit
9d27402646
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
25
spring-cloud-alibaba-starters/spring-cloud-starter-stream-rocketmq/src/main/java/com/alibaba/cloud/stream/binder/rocketmq/properties/RocketMQBindingProperties.java → spring-cloud-alibaba-starters/spring-cloud-starter-stream-rocketmq/src/main/java/com/alibaba/cloud/stream/binder/rocketmq/properties/RocketMQSpecificPropertiesProvider.java
25
spring-cloud-alibaba-starters/spring-cloud-starter-stream-rocketmq/src/main/java/com/alibaba/cloud/stream/binder/rocketmq/properties/RocketMQBindingProperties.java → spring-cloud-alibaba-starters/spring-cloud-starter-stream-rocketmq/src/main/java/com/alibaba/cloud/stream/binder/rocketmq/properties/RocketMQSpecificPropertiesProvider.java
@ -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
|
||||
|
Loading…
Reference in New Issue