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

org.pipservices4.messaging.queues.MemoryMessageQueue Maven / Gradle / Ivy

The newest version!
package org.pipservices4.messaging.queues;

import org.pipservices4.components.config.ConfigParams;
import org.pipservices4.components.context.Context;
import org.pipservices4.config.auth.CredentialParams;
import org.pipservices4.config.connect.ConnectionParams;
import org.pipservices4.components.context.IContext;

import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Message queue that sends and receives messages within the same process by using shared memory.
 * 

* This queue is typically used for testing to mock real queues. *

* ### Configuration parameters ### *

    *
  • name: name of the message queue *
*

* ### References ### *

    *
  • *:logger:*:*:1.0 (optional) ILogger components to pass log messages *
  • *:counters:*:*:1.0 (optional) ICounters components to pass collected measurements *
*

* ### Example ### *

 * {@code
 * MessageQueue queue = new MessageQueue("myqueue");
 *
 * queue.send("123", new MessageEnvelope(null, "mymessage", "ABC"));
 *
 * queue.receive("123", 0);
 * }
 * 
* * @see MessageQueue * @see MessagingCapabilities */ public class MemoryMessageQueue extends MessageQueue { private final List _messages = new ArrayList<>(); private int _lockTokenSequence = 0; private final Map _lockedMessages = new HashMap<>(); private boolean _opened = false; /** * Used to stop the listening process. */ private boolean _cancel = false; private long _listenInterval = 1000; /** * Creates a new instance of the message queue. */ public MemoryMessageQueue() { this(null); } /** * Creates a new instance of the message queue. * * @param name (optional) a queue name. * @see MessagingCapabilities */ public MemoryMessageQueue(String name) { super(name); _capabilities = new MessagingCapabilities(true, true, true, true, true, true, true, false, true); } /** * Checks if the component is opened. * * @return true if the component has been opened and false otherwise. */ @Override public boolean isOpen() { return _opened; } /** * Opens the component with given connection and credential parameters. * * @param context (optional) a context to trace execution through call chain. * @param connection connection parameters * @param credential credential parameters */ @Override public void openWithParams(IContext context, ConnectionParams connection, CredentialParams credential) { _logger.trace(context, "Opened queue %s", this); _opened = true; } /** * Closes component and frees used resources. * * @param context (optional) a context to trace execution through call chain. */ @Override public void close(IContext context) { synchronized (_lock) { _cancel = false; _opened = false; _lock.notifyAll(); } _logger.trace(context, "Closed queue %s", this); } /** * Clears component state. * * @param context (optional) a context to trace execution through call chain. */ @Override public void clear(IContext context) { synchronized (_lock) { // Clear messages _messages.clear(); _lockedMessages.clear(); } _logger.trace(context, "Cleared queue %s", this); } @Override public void configure(ConfigParams config) { super.configure(config); this._listenInterval = config.getAsLongWithDefault("listen_interval", this._listenInterval); this._listenInterval = config.getAsLongWithDefault("options.listen_interval", this._listenInterval); } /** * Gets the current number of messages in the queue to be delivered. * * @return number of messages. */ @Override public int readMessageCount() { synchronized (_lock) { return _messages.size(); } } /** * Sends a message into the queue. * * @param context (optional) a context to trace execution through call chain. * @param message a message envelop to be sent. */ @Override public void send(IContext context, MessageEnvelope message) { if (message == null) return; synchronized (_lock) { // Set sent time message.setSentTime(ZonedDateTime.now(ZoneOffset.UTC)); // Add message to the queue _messages.add(message); // Release threads waiting for messages _lock.notify(); } _counters.incrementOne("queue." + getName() + ".sent_messages"); _logger.debug(context, "Sent message %s via %s", message, this); } /** * Peeks a single incoming message from the queue without removing it. If there * are no messages available in the queue it returns null. * * @param context (optional) a context to trace execution through call chain. * @return a message envelop object. */ @Override public MessageEnvelope peek(IContext context) { MessageEnvelope message = null; synchronized (_lock) { // Pick a message if (_messages.size() > 0) message = _messages.get(0); } if (message != null) _logger.trace(context, "Peeked message %s on %s", message, this); return message; } /** * Peeks multiple incoming messages from the queue without removing them. If * there are no messages available in the queue it returns an empty list. * * @param context (optional) a context to trace execution through call chain. * @param messageCount a maximum number of messages to peek. * @return a list with messages. */ @Override public List peekBatch(IContext context, int messageCount) { List messages = new ArrayList<>(); synchronized (_lock) { for (int index = 0; index < _messages.size() && index < messageCount; index++) messages.add(_messages.get(index)); } _logger.trace(context, "Peeked %d messages on %s", messages.size(), this); return messages; } /** * Receives an incoming message and removes it from the queue. * * @param context (optional) a context to trace execution through call chain. * @param waitTimeout a timeout in milliseconds to wait for a message to come. * @return a message envelop object. */ @Override public MessageEnvelope receive(IContext context, long waitTimeout) { MessageEnvelope message = null; long checkIntervalMs = 100; long elapsedTime = 0; synchronized (_lock) { // Get message the the queue if (_messages.size() > 0) { message = _messages.get(0); _messages.remove(0); } } while (elapsedTime < waitTimeout && message == null) { synchronized (_lock) { if (message == null) { try { // Wait for a while _lock.wait(checkIntervalMs); } catch (InterruptedException ex) { return null; } } // Try to get a message again if (message == null && _messages.size() > 0) { message = _messages.get(0); _messages.remove(0); } } } // Exit if message was not found if (message == null) return null; // Generate and set locked token int lockedToken = _lockTokenSequence++; message.setReference(lockedToken); // Add messages to locked messages list LockedMessage lockedMessage = new LockedMessage(); lockedMessage.expirationTime = ZonedDateTime.now().plus(waitTimeout, ChronoUnit.MILLIS); lockedMessage.message = message; lockedMessage.timeout = waitTimeout; _lockedMessages.put(lockedToken, lockedMessage); _counters.incrementOne("queue." + getName() + ".received_messages"); _logger.debug(Context.fromTraceId(message.getTraceId()), "Received message %s via %s", message, this); return message; } /** * Renews a lock on a message that makes it invisible from other receivers in * the queue. This method is usually used to extend the message processing time. * * @param message a message to extend its lock. * @param lockTimeout a locking timeout in milliseconds. */ @Override public void renewLock(MessageEnvelope message, long lockTimeout) { if (message == null || message.getReference() == null) return; synchronized (_lock) { // Get message from locked queue int lockedToken = (int) message.getReference(); LockedMessage lockedMessage = _lockedMessages.get(lockedToken); // If lock is found, extend the lock if (lockedMessage != null) lockedMessage.expirationTime = ZonedDateTime.now().plus(lockTimeout, ChronoUnit.MILLIS); } _logger.trace(Context.fromTraceId(message.getTraceId()), "Renewed lock for message %s at %s", message, this); } /** * Returnes message into the queue and makes it available for all subscribers to * receive it again. This method is usually used to return a message which could * not be processed at the moment to repeat the attempt. Messages that cause * unrecoverable errors shall be removed permanently or/and send to dead letter * queue. * * @param message a message to return. */ @Override public void abandon(MessageEnvelope message) { if (message == null || message.getReference() == null) return; synchronized (_lock) { // Get message from locked queue int lockedToken = (int) message.getReference(); LockedMessage lockedMessage = _lockedMessages.get(lockedToken); if (lockedMessage != null) { // Remove from locked messages _lockedMessages.remove(lockedToken); message.setReference(null); // Skip if it is already expired if (lockedMessage.expirationTime.toInstant().toEpochMilli() <= ZonedDateTime.now().toInstant().toEpochMilli()) return; } // Skip if it absent else return; } _logger.trace(Context.fromTraceId(message.getTraceId()), "Abandoned message %s at %s", message, this); // Add back to the queue send(Context.fromTraceId(message.getTraceId()), message); } /** * Permanently removes a message from the queue. This method is usually used to * remove the message after successful processing. * * @param message a message to remove. */ @Override public void complete(MessageEnvelope message) { if (message == null || message.getReference() == null) return; synchronized (_lock) { int lockKey = (int) message.getReference(); _lockedMessages.remove(lockKey); message.setReference(null); } _logger.trace(Context.fromTraceId(message.getTraceId()), "Completed message %s at %s", message, this); } /** * Permanently removes a message from the queue and sends it to dead letter * queue. * * @param message a message to be removed. */ @Override public void moveToDeadLetter(MessageEnvelope message) { if (message == null || message.getReference() == null) return; synchronized (_lock) { int lockKey = (int) message.getReference(); _lockedMessages.remove(lockKey); message.setReference(null); } _counters.incrementOne("queue." + getName() + ".dead_messages"); _logger.trace(Context.fromTraceId(message.getTraceId()), "Moved to dead message %s at %s", message, this); } /** * Listens for incoming messages and blocks the current thread until queue is * closed. * * @param context (optional) a context to trace execution through call chain. * @param receiver a receiver to receive incoming messages. * @see IMessageReceiver * @see #receive(IContext, long) */ @Override public void listen(IContext context, IMessageReceiver receiver) { if (_cancel) { _logger.error(context, "Already listening queue %s", this); return; } _logger.trace(context, "Started listening messages at %s", this); _cancel = true; while (_cancel) { MessageEnvelope message = receive(context, _listenInterval); if (_cancel && message != null) { try { receiver.receiveMessage(message, this); } catch (Exception ex) { _logger.error(context, ex, "Failed to process the message"); // await AbandonAsync(message); } } } _logger.trace(context, "Stopped listening messages at %s", this); } /** * Ends listening for incoming messages. When this method is call listen() * unblocks the thread and execution continues. * * @param context (optional) a context to trace execution through call chain. */ @Override public void endListen(IContext context) { synchronized (_lock) { _cancel = false; } } /** * Override toString() method, string representation of queue. * * @return queue name */ @Override public String toString() { return "[" + getName() + "]"; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy