All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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