Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright 2016-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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.kafka.listener.adapter;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.WildcardType;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.kafka.clients.consumer.Consumer;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.common.TopicPartition;
import org.springframework.context.expression.MapAccessor;
import org.springframework.core.MethodParameter;
import org.springframework.expression.BeanResolver;
import org.springframework.expression.Expression;
import org.springframework.expression.ParserContext;
import org.springframework.expression.common.LiteralExpression;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.expression.spel.support.StandardTypeConverter;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.listener.ConsumerSeekAware;
import org.springframework.kafka.listener.ListenerExecutionFailedException;
import org.springframework.kafka.support.Acknowledgment;
import org.springframework.kafka.support.KafkaHeaders;
import org.springframework.kafka.support.KafkaUtils;
import org.springframework.kafka.support.converter.MessagingMessageConverter;
import org.springframework.kafka.support.converter.RecordMessageConverter;
import org.springframework.lang.Nullable;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.MessagingException;
import org.springframework.messaging.converter.MessageConversionException;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
* An abstract {@link org.springframework.kafka.listener.MessageListener} adapter
* providing the necessary infrastructure to extract the payload of a
* {@link org.springframework.messaging.Message}.
*
* @param the key type.
* @param the value type.
*
* @author Stephane Nicoll
* @author Gary Russell
* @author Artem Bilan
* @author Venil Noronha
*/
public abstract class MessagingMessageListenerAdapter implements ConsumerSeekAware {
private static final SpelExpressionParser PARSER = new SpelExpressionParser();
private static final ParserContext PARSER_CONTEXT = new TemplateParserContext("!{", "}");
private final Object bean;
protected final Log logger = LogFactory.getLog(getClass()); //NOSONAR
private final Type inferredType;
private final StandardEvaluationContext evaluationContext = new StandardEvaluationContext();
private HandlerAdapter handlerMethod;
private boolean isConsumerRecordList;
private boolean isConsumerRecords;
private boolean isMessageList;
private RecordMessageConverter messageConverter = new MessagingMessageConverter();
private Type fallbackType = Object.class;
private Expression replyTopicExpression;
@SuppressWarnings("rawtypes")
private KafkaTemplate replyTemplate;
private boolean hasAckParameter;
private boolean messageReturnType;
private ReplyHeadersConfigurer replyHeadersConfigurer;
public MessagingMessageListenerAdapter(Object bean, Method method) {
this.bean = bean;
this.inferredType = determineInferredType(method); // NOSONAR = intentionally not final
}
/**
* Set the MessageConverter.
* @param messageConverter the converter.
*/
public void setMessageConverter(RecordMessageConverter messageConverter) {
this.messageConverter = messageConverter;
}
/**
* Return the {@link MessagingMessageConverter} for this listener,
* being able to convert {@link org.springframework.messaging.Message}.
* @return the {@link MessagingMessageConverter} for this listener,
* being able to convert {@link org.springframework.messaging.Message}.
*/
protected final RecordMessageConverter getMessageConverter() {
return this.messageConverter;
}
/**
* Returns the inferred type for conversion or, if null, the
* {@link #setFallbackType(Class) fallbackType}.
* @return the type.
*/
protected Type getType() {
return this.inferredType == null ? this.fallbackType : this.inferredType;
}
/**
* Set a fallback type to use when using a type-aware message converter and this
* adapter cannot determine the inferred type from the method. An example of a
* type-aware message converter is the {@code StringJsonMessageConverter}. Defaults to
* {@link Object}.
* @param fallbackType the type.
*/
public void setFallbackType(Class fallbackType) {
this.fallbackType = fallbackType;
}
/**
* Set the {@link HandlerAdapter} to use to invoke the method
* processing an incoming {@link ConsumerRecord}.
* @param handlerMethod {@link HandlerAdapter} instance.
*/
public void setHandlerMethod(HandlerAdapter handlerMethod) {
this.handlerMethod = handlerMethod;
}
protected boolean isConsumerRecordList() {
return this.isConsumerRecordList;
}
public boolean isConsumerRecords() {
return this.isConsumerRecords;
}
/**
* Set the topic to which to send any result from the method invocation.
* May be a SpEL expression {@code !{...}} evaluated at runtime.
* @param replyTopicParam the topic or expression.
* @since 2.0
*/
public void setReplyTopic(String replyTopicParam) {
String replyTopic = replyTopicParam;
if (!StringUtils.hasText(replyTopic)) {
replyTopic = PARSER_CONTEXT.getExpressionPrefix() + "source.headers['"
+ KafkaHeaders.REPLY_TOPIC + "']" + PARSER_CONTEXT.getExpressionSuffix();
}
if (replyTopic.contains(PARSER_CONTEXT.getExpressionPrefix())) {
this.replyTopicExpression = PARSER.parseExpression(replyTopic, PARSER_CONTEXT);
}
else {
this.replyTopicExpression = new LiteralExpression(replyTopic);
}
}
/**
* Set the template to use to send any result from the method invocation.
* @param replyTemplate the template.
* @since 2.0
*/
public void setReplyTemplate(KafkaTemplate replyTemplate) {
this.replyTemplate = replyTemplate;
}
/**
* Set a bean resolver for runtime SpEL expressions. Also configures the evaluation
* context with a standard type converter and map accessor.
* @param beanResolver the resolver.
* @since 2.0
*/
public void setBeanResolver(BeanResolver beanResolver) {
this.evaluationContext.setBeanResolver(beanResolver);
this.evaluationContext.setTypeConverter(new StandardTypeConverter());
this.evaluationContext.addPropertyAccessor(new MapAccessor());
}
protected boolean isMessageList() {
return this.isMessageList;
}
/**
* Return the reply configurer.
* @return the configurer.
* @since 2.2
* @see #setReplyHeadersConfigurer(ReplyHeadersConfigurer)
*/
protected ReplyHeadersConfigurer getReplyHeadersConfigurer() {
return this.replyHeadersConfigurer;
}
/**
* Set a configurer which will be invoked when creating a reply message.
* @param replyHeadersConfigurer the configurer.
* @since 2.2
*/
public void setReplyHeadersConfigurer(ReplyHeadersConfigurer replyHeadersConfigurer) {
this.replyHeadersConfigurer = replyHeadersConfigurer;
}
@Override
public void registerSeekCallback(ConsumerSeekCallback callback) {
if (this.bean instanceof ConsumerSeekAware) {
((ConsumerSeekAware) this.bean).registerSeekCallback(callback);
}
}
@Override
public void onPartitionsAssigned(Map assignments, ConsumerSeekCallback callback) {
if (this.bean instanceof ConsumerSeekAware) {
((ConsumerSeekAware) this.bean).onPartitionsAssigned(assignments, callback);
}
}
@Override
public void onIdleContainer(Map assignments, ConsumerSeekCallback callback) {
if (this.bean instanceof ConsumerSeekAware) {
((ConsumerSeekAware) this.bean).onIdleContainer(assignments, callback);
}
}
protected Message toMessagingMessage(ConsumerRecord record, Acknowledgment acknowledgment,
Consumer consumer) {
return getMessageConverter().toMessage(record, acknowledgment, consumer, getType());
}
/**
* Invoke the handler, wrapping any exception to a {@link ListenerExecutionFailedException}
* with a dedicated error message.
* @param data the data to process during invocation.
* @param acknowledgment the acknowledgment to use if any.
* @param message the message to process.
* @param consumer the consumer.
* @return the result of invocation.
*/
protected final Object invokeHandler(Object data, Acknowledgment acknowledgment, Message message,
Consumer consumer) {
try {
if (data instanceof List && !this.isConsumerRecordList) {
return this.handlerMethod.invoke(message, acknowledgment, consumer);
}
else {
return this.handlerMethod.invoke(message, data, acknowledgment, consumer);
}
}
catch (org.springframework.messaging.converter.MessageConversionException ex) {
if (this.hasAckParameter && acknowledgment == null) {
throw new ListenerExecutionFailedException("invokeHandler Failed",
new IllegalStateException("No Acknowledgment available as an argument, "
+ "the listener container must have a MANUAL Ackmode to populate the Acknowledgment.", ex));
}
throw new ListenerExecutionFailedException(createMessagingErrorMessage("Listener method could not " +
"be invoked with the incoming message", message.getPayload()),
new MessageConversionException("Cannot handle message", ex));
}
catch (MessagingException ex) {
throw new ListenerExecutionFailedException(createMessagingErrorMessage("Listener method could not " +
"be invoked with the incoming message", message.getPayload()), ex);
}
catch (Exception ex) {
throw new ListenerExecutionFailedException("Listener method '" +
this.handlerMethod.getMethodAsString(message.getPayload()) + "' threw exception", ex);
}
}
/**
* Handle the given result object returned from the listener method, sending a
* response message to the SendTo topic.
* @param resultArg the result object to handle (never null)
* @param request the original request message
* @param source the source data for the method invocation - e.g.
* {@code o.s.messaging.Message}; may be null
*/
protected void handleResult(Object resultArg, Object request, Object source) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Listener method returned result [" + resultArg
+ "] - generating response message for it");
}
boolean isInvocationResult = resultArg instanceof InvocationResult;
Object result = isInvocationResult ? ((InvocationResult) resultArg).getResult() : resultArg;
String replyTopic = evaluateReplyTopic(request, source, resultArg);
Assert.state(replyTopic == null || this.replyTemplate != null,
"a KafkaTemplate is required to support replies");
sendResponse(result, replyTopic, source, isInvocationResult
? ((InvocationResult) resultArg).isMessageReturnType() : this.messageReturnType);
}
private String evaluateReplyTopic(Object request, Object source, Object result) {
String replyTo = null;
if (result instanceof InvocationResult) {
replyTo = evaluateTopic(request, source, result, ((InvocationResult) result).getSendTo());
}
else if (this.replyTopicExpression != null) {
replyTo = evaluateTopic(request, source, result, this.replyTopicExpression);
}
return replyTo;
}
private String evaluateTopic(Object request, Object source, Object result, Expression sendTo) {
if (sendTo instanceof LiteralExpression) {
return sendTo.getValue(String.class);
}
else {
Object value = sendTo == null ? null
: sendTo.getValue(this.evaluationContext, new ReplyExpressionRoot(request, source, result));
boolean isByteArray = value instanceof byte[];
if (!(value == null || value instanceof String || isByteArray)) {
throw new IllegalStateException(
"replyTopic expression must evaluate to a String or byte[], it is: "
+ value.getClass().getName());
}
if (isByteArray) {
return new String((byte[]) value, StandardCharsets.UTF_8);
}
return (String) value;
}
}
/**
* Send the result to the topic.
*
* @param result the result.
* @param topic the topic.
* @deprecated in favor of {@link #sendResponse(Object, String, Object, boolean)}.
*/
@Deprecated
protected void sendResponse(Object result, String topic) {
sendResponse(result, topic, null, false);
}
/**
* Send the result to the topic.
*
* @param result the result.
* @param topic the topic.
* @param source the source (input).
* @param messageReturnType true if we are returning message(s).
* @since 2.1.3
*/
@SuppressWarnings("unchecked")
protected void sendResponse(Object result, String topic, @Nullable Object source, boolean messageReturnType) {
if (!messageReturnType && topic == null) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("No replyTopic to handle the reply: " + result);
}
}
else if (result instanceof Message) {
this.replyTemplate.send((Message) result);
}
else {
if (result instanceof Collection) {
((Collection) result).forEach(v -> {
if (v instanceof Message) {
this.replyTemplate.send((Message) v);
}
else {
this.replyTemplate.send(topic, v);
}
});
}
else {
sendSingleResult(result, topic, source);
}
}
}
@SuppressWarnings("unchecked")
private void sendSingleResult(Object result, String topic, @Nullable Object source) {
byte[] correlationId = null;
boolean sourceIsMessage = source instanceof Message;
if (sourceIsMessage
&& ((Message) source).getHeaders().get(KafkaHeaders.CORRELATION_ID) != null) {
correlationId = ((Message) source).getHeaders().get(KafkaHeaders.CORRELATION_ID, byte[].class);
}
if (sourceIsMessage) {
sendReplyForMessageSource(result, topic, source, correlationId);
}
else {
this.replyTemplate.send(topic, result);
}
}
@SuppressWarnings("unchecked")
private void sendReplyForMessageSource(Object result, String topic, Object source, byte[] correlationId) {
MessageBuilder