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

com.solace.spring.cloud.stream.binder.util.FlowReceiverContainer Maven / Gradle / Ivy

The newest version!
package com.solace.spring.cloud.stream.binder.util;

import com.solace.spring.cloud.stream.binder.health.handlers.SolaceFlowHealthEventHandler;
import com.solacesystems.jcsmp.*;
import com.solacesystems.jcsmp.XMLMessage.Outcome;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.lang.Nullable;

import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 

A {@link FlowReceiver} wrapper object which allows for flow rebinds.

*

Messaging operations concurrently invoked through this object during a rebind operation are not affected * by the rebind.

*/ @Slf4j public class FlowReceiverContainer { @Getter private final UUID id = UUID.randomUUID(); private final JCSMPSession session; private final Endpoint endpoint; private final EndpointProperties endpointProperties; private final ConsumerFlowProperties consumerFlowProperties; private final AtomicReference flowReceiverAtomicReference = new AtomicReference<>(); private final AtomicBoolean isPaused = new AtomicBoolean(false); //Lock to serialize operations like - bind, unbind, pause, resume private final ReentrantLock writeLock = new ReentrantLock(); private final XMLMessageMapper xmlMessageMapper = new XMLMessageMapper(); @Setter private FlowEventHandler eventHandler; public FlowReceiverContainer(JCSMPSession session, Endpoint endpoint, EndpointProperties endpointProperties, ConsumerFlowProperties consumerFlowProperties) { this.session = session; this.endpoint = endpoint; this.endpointProperties = endpointProperties; this.consumerFlowProperties = consumerFlowProperties .setEndpoint(endpoint) .setAckMode(JCSMPProperties.SUPPORTED_MESSAGE_ACK_CLIENT); this.eventHandler = new SolaceFlowEventHandler(xmlMessageMapper, id.toString()); } /** *

Create the {@link FlowReceiver} and {@link FlowReceiver#start() starts} it.

*

Does nothing if this container is already bound to a {@link FlowReceiver}.

* * @return If no flow is bound, return the new flow reference ID. Otherwise, return the existing flow reference ID. * @throws JCSMPException a JCSMP exception */ public UUID bind() throws JCSMPException { // Ideally would just use flowReceiverReference.compareAndSet(null, newFlowReceiver), // but we can't initialize a FlowReceiver object without actually provisioning it. // Explicitly using a lock here is our only option. writeLock.lock(); try { log.info("Binding flow receiver container {}", id); FlowReceiverReference existingFlowReceiverReference = flowReceiverAtomicReference.get(); if (existingFlowReceiverReference != null) { UUID existingFlowRefId = existingFlowReceiverReference.getId(); log.info("Flow receiver container {} is already bound to {}", id, existingFlowRefId); return existingFlowRefId; } else { log.info("Flow receiver container {} started in state '{}'", id, isPaused.get() ? "Paused" : "Running"); consumerFlowProperties.setStartState(!isPaused.get()); consumerFlowProperties.addRequiredSettlementOutcomes(Outcome.ACCEPTED, Outcome.FAILED, Outcome.REJECTED); FlowReceiver flowReceiver = session.createFlow(null, consumerFlowProperties, endpointProperties, eventHandler); if (eventHandler != null && eventHandler instanceof SolaceFlowHealthEventHandler) { ((SolaceFlowHealthEventHandler) eventHandler).setHealthStatusUp(); } FlowReceiverReference newFlowReceiverReference = new FlowReceiverReference(flowReceiver); flowReceiverAtomicReference.set(newFlowReceiverReference); xmlMessageMapper.resetIgnoredProperties(id.toString()); return newFlowReceiverReference.getId(); } } finally { writeLock.unlock(); } } /** * Closes the bound {@link FlowReceiver}. */ public void unbind() { writeLock.lock(); try { FlowReceiverReference flowReceiverReference = flowReceiverAtomicReference.getAndSet(null); if (flowReceiverReference != null) { log.info("Unbinding flow receiver container {}", id); flowReceiverReference.getStaleMessagesFlag().set(true); flowReceiverReference.get().close(); } } finally { writeLock.unlock(); } } /** *

Receives the next available message, waiting until one is available.

*

Note: This method is not thread-safe.

* * @return The next available message or null if is interrupted or no flow is bound. * @throws JCSMPException a JCSMP exception * @throws UnboundFlowReceiverContainerException flow receiver container is not bound * @see FlowReceiver#receive() */ public MessageContainer receive() throws JCSMPException, UnboundFlowReceiverContainerException { return receive(null); } /** *

Receives the next available message. If no message is available, this method blocks until * {@code timeoutInMillis} is reached.

*

Note: This method is not thread-safe.

* * @param timeoutInMillis The timeout in milliseconds. If {@code null}, wait forever. * If less than zero and no message is available, return immediately. * @return The next available message or null if the timeout expires, is interrupted, or no flow is bound. * @throws JCSMPException a JCSMP exception * @throws UnboundFlowReceiverContainerException flow receiver container is not bound * @see FlowReceiver#receive(int) */ public MessageContainer receive(Integer timeoutInMillis) throws JCSMPException, UnboundFlowReceiverContainerException { FlowReceiverReference flowReceiverReference = flowReceiverAtomicReference.get(); if (flowReceiverReference == null) { throw new UnboundFlowReceiverContainerException( String.format("Flow receiver container %s is not bound", id)); } // Always true: expiry - System.currentTimeMillis() < timeoutInMillis // So just set it to 0 (no-wait) if we underflow // The flow's receive shouldn't be locked behind the read lock. // This lets it be interrupt-able if the flow were to be shutdown mid-receive. BytesXMLMessage xmlMessage; if (timeoutInMillis == null) { xmlMessage = flowReceiverReference.get().receive(); } else if (timeoutInMillis == 0) { xmlMessage = flowReceiverReference.get().receiveNoWait(); } else { // realTimeout > 0: Wait until timeout // realTimeout < 0: Equivalent to receiveNoWait() xmlMessage = flowReceiverReference.get().receive(timeoutInMillis); } if (xmlMessage == null) { return null; } return new MessageContainer(xmlMessage, flowReceiverReference.getId(), flowReceiverReference.getStaleMessagesFlag()); } /** *

Acknowledge the message off the broker and mark the provided message container as acknowledged.

*

WARNING: Only messages created by this {@link FlowReceiverContainer} instance's {@link #receive()} * may be passed as a parameter to this function.

* * @param messageContainer The message */ public void acknowledge(MessageContainer messageContainer) { if (messageContainer == null || messageContainer.isAcknowledged()) { return; } try { messageContainer.getMessage().settle(Outcome.ACCEPTED); } catch (JCSMPException | IllegalStateException ex) { throw new SolaceAcknowledgmentException("Failed to ACK a message", ex); } messageContainer.setAcknowledged(true); } /** *

Represents a negative acknowledgement outcome. Is used to signal that the application * failed to process the message and request broker to requeue/redeliver the message and mark the * provided message container as acknowledged.

Message may be moved to DMQ by broker once * max-redelivered configured on endpoint is reached. Message may be delayed if the endpoint has * delayed redelivery configured.

*

WARNING: Only messages created by this {@link FlowReceiverContainer} instance's * {@link #receive()} may be passed as a parameter to this function.

* * @param messageContainer The message */ public void requeue(MessageContainer messageContainer) { if (messageContainer == null || messageContainer.isAcknowledged()) { return; } try { messageContainer.getMessage().settle(Outcome.FAILED); } catch (JCSMPException | IllegalStateException ex) { throw new SolaceAcknowledgmentException("Failed to REQUEUE a message", ex); } messageContainer.setAcknowledged(true); } /** *

Represents a negative acknowledgement outcome. Is used to signal that the application has * rejected the message such as when application determines the message is invalid and mark the * provided message container as acknowledged.

Message will NOT be redelivered. Message will * be moved to DMQ. If DMQ is not configured, message is discarded/deleted.

*

WARNING: Only messages created by this {@link FlowReceiverContainer} instance's * {@link #receive()} may be passed as a parameter to this function.

* * @param messageContainer The message */ public void reject(MessageContainer messageContainer) { if (messageContainer == null || messageContainer.isAcknowledged()) { return; } try { messageContainer.getMessage().settle(Outcome.REJECTED); } catch (JCSMPException | IllegalStateException ex) { throw new SolaceAcknowledgmentException("Failed to REJECT a message", ex); } messageContainer.setAcknowledged(true); } public void pause() { writeLock.lock(); try { log.info("Pausing flow receiver container {}", id); doFlowReceiverReferencePause(); isPaused.set(true); } finally { writeLock.unlock(); } } /** * CAUTION: DO NOT USE THIS. This is only exposed for testing. Use {@link #pause()} instead to pause * the flow receiver container. * * @see #pause() */ void doFlowReceiverReferencePause() { writeLock.lock(); try { FlowReceiverReference flowReceiverReference = flowReceiverAtomicReference.get(); if (flowReceiverReference != null) { flowReceiverReference.pause(); } } finally { writeLock.unlock(); } } public void resume() throws JCSMPException { writeLock.lock(); try { log.info("Resuming flow receiver container {}", id); doFlowReceiverReferenceResume(); isPaused.set(false); } finally { writeLock.unlock(); } } /** * CAUTION: DO NOT USE THIS. This is only exposed for testing. Use {@link #resume()} instead to resume * the flow receiver container. * * @see #resume() */ void doFlowReceiverReferenceResume() throws JCSMPException { writeLock.lock(); try { FlowReceiverReference flowReceiverReference = flowReceiverAtomicReference.get(); if (flowReceiverReference != null) { flowReceiverReference.resume(); } } finally { writeLock.unlock(); } } public boolean isPaused() { return isPaused.get(); } /** *

Get the nested {@link FlowReceiver}.

*

Caution: Instead of using this, consider instead implementing a new function with the required rebind * guards.

* * @return The nested flow receiver. */ @Nullable FlowReceiverReference getFlowReceiverReference() { return flowReceiverAtomicReference.get(); } public String getEndpointName() { return endpoint.getName(); } public XMLMessageMapper getXMLMessageMapper() { return xmlMessageMapper; } static class FlowReceiverReference { /** * -- GETTER -- * Get the flow receiver reference ID. * This is NOT the actual flow ID, but just a reference ID used by this binder to identify a * particular * instance. * We can't use the actual flow ID for this since that can transparently change by itself due to * events such as reconnection. * * @return the flow receiver reference ID. */ @Getter private final UUID id = UUID.randomUUID(); private final FlowReceiver flowReceiver; @Getter private final AtomicBoolean staleMessagesFlag = new AtomicBoolean(false); public FlowReceiverReference(FlowReceiver flowReceiver) { this.flowReceiver = flowReceiver; } public FlowReceiver get() { return flowReceiver; } private void pause() { flowReceiver.stop(); } private void resume() throws JCSMPException { flowReceiver.start(); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; FlowReceiverReference that = (FlowReceiverReference) o; return Objects.equals(id, that.id); } @Override public int hashCode() { return Objects.hash(id); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy