
fi.evolver.basics.spring.messaging.MessageSender Maven / Gradle / Ivy
package fi.evolver.basics.spring.messaging;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.time.LocalDateTime;
import java.util.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import fi.evolver.basics.spring.log.LogUtils;
import fi.evolver.basics.spring.log.LogUtils.Specifier;
import fi.evolver.basics.spring.log.MessageLogService;
import fi.evolver.basics.spring.log.entity.MessageLog.Direction;
import fi.evolver.basics.spring.messaging.entity.Message;
import fi.evolver.basics.spring.messaging.entity.Message.MessageState;
import fi.evolver.basics.spring.messaging.model.MessageDetails;
import fi.evolver.basics.spring.messaging.model.PendingMessageDetails;
import fi.evolver.basics.spring.messaging.sender.Sender;
import fi.evolver.basics.spring.messaging.util.SendUtils;
import fi.evolver.basics.spring.status.model.ComponentStatus;
import fi.evolver.basics.spring.status.model.Reportable;
import fi.evolver.basics.spring.util.MessageChainUtils;
import fi.evolver.basics.spring.util.MessageChainUtils.MessageChain;
import fi.evolver.utils.CommunicationException;
import fi.evolver.utils.NullSafetyUtils;
@Component
public class MessageSender implements IMessageSender, Reportable {
private static final Logger LOG = LoggerFactory.getLogger(MessageSender.class);
private static final String PROPERTY_FORWARD_POLICY = "ForwardPolicy";
private enum ForwardResponse { ALWAYS, SUCCESS, ERROR, NEVER }
private static final String DISABLED_PREFIX = "DISABLED:";
private static final String DISABLED_URI = "disabled://uri";
private static long sentMessagesCount = 0;
private final MessageLogService messageLogService;
private final MessageRepository messageRepository;
private final MessagingService messagingService;
private final Map senders;
@Autowired
public MessageSender(MessageLogService messageLogService, MessageRepository messageRepository, MessagingService messagingService, List senders) {
this.messageLogService = messageLogService;
this.messageRepository = messageRepository;
this.messagingService = messagingService;
Map map = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
for (Sender sender: senders) {
for (String protocol: sender.getSupportedProtocols())
map.put(protocol, sender);
}
this.senders = Collections.unmodifiableMap(map);
}
@Async
@Override
public void sendPendingMessages(long targetId, String groupId) {
long startTime = System.currentTimeMillis();
int sent = 0;
try {
while (true) {
List messages = messageRepository.findPendingMessagesByTarget(targetId, groupId);
if (messages.isEmpty())
return;
for (PendingMessageDetails message: messages) {
if (!message.isOkToHandle())
return;
if (!handleMessage(message))
return;
++sent;
if (System.currentTimeMillis() - startTime >= 9_000)
return;
}
}
}
catch (RuntimeException e) {
LOG.warn("Failed sending pending messages for target {}, group {}", targetId, groupId, e);
}
finally {
if (sent > 0) {
updateSentCount(sent);
LOG.info("Sent {} messages for target {}, group {}", sent, targetId, groupId);
}
}
}
private static synchronized void updateSentCount(int count) {
sentMessagesCount += count;
}
/**
* Sends a pending message if no other sender gets to it first.
*
* @param messageDetails The pending message.
* @return Whether the message was handled and no error occurred.
*/
private boolean handleMessage(PendingMessageDetails messageDetails) {
if (!messageDetails.isOkToHandle())
return false;
Optional message = Optional.empty();
MessageState sendState = MessageState.FAILED;
try (MessageChain mc = MessageChainUtils.startMessageChain(messageDetails.getMessageChainId())) {
message = messageRepository.fetchForHandling(messageDetails);
if (!message.isPresent()) {
LOG.debug("Not sending message {}, another sender got to it first", messageDetails.getId());
return false;
}
Optional specifier = message.map(Message::getMetadata).map(m -> m.get(MessagingService.METADATA_SPECIFIER));
try (Specifier s = specifier.map(LogUtils::startSpecifier).orElse(null)) {
switch (message.get().getMessageTargetConfig().getState()) {
case DISABLED:
sendState = MessageState.DISABLED;
break;
case LOG_ONLY:
logMessage(message.get());
sendState = MessageState.DISABLED;
break;
case ENABLED:
if (messageDetails.getState() != MessageState.PENDING)
LOG.warn("Failing message {} (chain {}) for not finishing in time", messageDetails.getId(), messageDetails.getMessageChainId());
else if (sendMessage(message.get()))
sendState = MessageState.SENT;
break;
case PAUSED:
return false;
}
}
}
catch (RuntimeException e) {
LOG.warn("Could not send message {}", messageDetails.getId(), e);
return false;
}
finally {
if (message.isPresent())
messageRepository.updateState(message.get(), sendState);
}
return sendState != MessageState.FAILED;
}
private boolean sendMessage(Message message) {
try {
String uriString = message.getTargetUri();
if (uriString.toUpperCase().startsWith(DISABLED_PREFIX))
uriString = DISABLED_URI;
URI uri = SendUtils.updateUri(uriString, message);
Sender sender = senders.get(uri.getScheme());
if (sender == null)
throw new CommunicationException("Unsupported protocol: %s", uri.getScheme());
SendResult result = sender.send(message, uri);
forwardResponse(message, result);
return result.isSuccess();
}
catch (IOException e) {
LOG.warn("FAILED sending {} message to {}", message.getMessageType(), message.getTargetSystem(), e);
}
catch (URISyntaxException | RuntimeException e) {
LOG.error("SENDING {} message {} to {} FAILED", message.getMessageType(), message.getId(), message.getTargetSystem(), e);
}
return false;
}
private void forwardResponse(Message message, SendResult sendResult) {
if (!shouldForwardResponse(message, sendResult.isSuccess()))
return;
Map responseParams = new LinkedHashMap<>();
String responseBody = NullSafetyUtils.denull(sendResult.getResponseBody(), "");
responseParams.putAll(sendResult.getResponseDetails());
responseParams.put("Status", sendResult.isSuccess() ? "SUCCESS" : "FAILURE");
responseParams.put("FailCount", message.getFailCount().toString());
responseParams.put("TargetSystem", message.getTargetSystem());
messagingService.send(message.getMessageType() + "-Response", MessageDetails.create(responseBody, responseParams));
}
private static boolean shouldForwardResponse(Message message, boolean success) {
ForwardResponse forwardType = inferForwardType(message);
return (ForwardResponse.ALWAYS == forwardType) ||
(success && ForwardResponse.SUCCESS == forwardType) ||
(!success && ForwardResponse.ERROR == forwardType);
}
private static ForwardResponse inferForwardType(Message message) {
String type = null;
try {
type = message.getMessageTargetConfig().getProperty(PROPERTY_FORWARD_POLICY)
.map(String::toUpperCase)
.orElse(ForwardResponse.NEVER.name());
return ForwardResponse.valueOf(type);
}
catch (RuntimeException e) {
LOG.warn("Invalid forwardType parameter value {}: {}", type, e.getMessage());
return ForwardResponse.NEVER;
}
}
private void logMessage(Message message) {
int requestSize = 0;
try (InputStream in = message.getDataStream()) {
while (in.read() != -1)
++requestSize;
}
catch (Exception e) {
LOG.warn("FAILED counting request size", e);
}
messageLogService.logZippedMessage(
LocalDateTime.now(),
message.getMessageType(),
"disabled",
message.getTargetUri(),
messageLogService.getApplicationName(),
message.getTargetSystem(),
Direction.OUTBOUND,
requestSize,
message.getCompressedData(),
null,
0,
null,
null,
"DISABLED",
"Not sent",
SendUtils.mapMetadata(message.getMetadata()));
}
@Override
public List getStatus() {
return Collections.singletonList(new ComponentStatus(
"Bean",
getClass().getSimpleName(),
Collections.singletonMap("MessagesSent", sentMessagesCount)));
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy