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

org.zeromq.jms.protocol.AbstractZmqGateway Maven / Gradle / Ivy

There is a newer version: 3.0.1-RELEASE
Show newest version
package org.zeromq.jms.protocol;

/*
 * Copyright (c) 2015 Jeremy Miller
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Date;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TransferQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.zeromq.ZMQ;
import org.zeromq.jms.ZmqException;
import org.zeromq.jms.ZmqMessage;
import org.zeromq.jms.protocol.event.ZmqEventHandler;
import org.zeromq.jms.protocol.filter.ZmqFilterPolicy;
import org.zeromq.jms.protocol.redelivery.ZmqRedeliveryPolicy;
import org.zeromq.jms.protocol.store.ZmqJournalEntry;
import org.zeromq.jms.protocol.store.ZmqJournalStore;
import org.zeromq.jms.selector.ZmqMessageSelector;
import org.zeromq.jms.util.Stopwatch;

/**
 * Abstract class that implements the gateway functionality around 1 or more ZERO MQ sockets.
 */
public abstract class AbstractZmqGateway implements ZmqGateway {
    private static final Logger LOGGER = Logger.getLogger(AbstractZmqGateway.class.getCanonicalName());

    private static final int HEARTBEAT_RATE_MILLI_SECOND = 1000;
    private static final int AUTO_PAUSE_IDLE_MILLI_SECOND = 3000;

    private static final int SOCKET_STATUS_TIMEOUT_MILLI_SECOND = 5000;
    private static final int SOCKET_WAIT_MILLI_SECOND = 500;
    private static final int SOCKET_METRIC_BUCKET_COUNT = 360;
    private static final int SOCKET_METRIC_BUCKET_INTERVAL_MILLI_SECOND = 10000;

    private static final int LISTENER_THREAD_POOL = 1;
    private static final int LISTENER_WAIT_MILLI_SECOND = 500;


    private AtomicBoolean active = new AtomicBoolean(false);

    private final String name;

    private final ZmqSocketType type;
    private final boolean bound;
    private final String addr;

    private final int flags;

    private ZMQ.Context context;
    private ZMQ.Context proxyContext;

    private final List metrics;
    private final Map socketSessions;

    private ZmqProxySession proxySession = null; //optional proxy

    private final boolean transacted;
    private final boolean acknowledge;
    private final boolean heartbeat;
    private final Direction direction;

    private final Date startDateTime;

    private final ZmqSocketContext socketContext;
    private final ZmqRedeliveryPolicy redelivery;
    private final ZmqFilterPolicy filterPolicy;
    private final ZmqEventHandler eventHandler;
    private final ZmqMessageSelector messageSelector;
    private final ZmqJournalStore journalStore;

    private ZmqGatewayListener listener = null;

    private ExecutorService socketExecutor = null;
    private ExecutorService listenerExecutor = null;
    private ExecutorService proxyExecutor = null;

    private final TransferQueue incomingQueue = new LinkedTransferQueue();
    private final Queue incomingSnapshot = new LinkedList();

    private final TransferQueue outgoingQueue = new LinkedTransferQueue();
    private final Queue outgoingSnapshot = new LinkedList();

    /**
     * Inner class for publishing external JMS messages.
     */
    private class ListenerThread implements Runnable {

        @Override
        public void run() {
            while (active.get() && listener != null) {
                try {
                    final ZmqMessage message = receive(LISTENER_WAIT_MILLI_SECOND);

                    if (message != null) {
                        listener.onMessage(message);
                    }
                } catch (ZmqException ex) {
                    listener.onException(ex);
                }
            }
        }

    }

    /**
     * Construct abstract gateway.
     * @param name              the name of display the gateway
     * @param socketContext     the socket context for the ZMQ socket
     * @param filter            the message filter policy
     * @param handler           the event handler functionality
     * @param listener          the listener instance
     * @param store             the (optional) message store
     * @param selector          the (optional) message selection policy
     * @param redelivery        the (optional) message re-delivery policy
     * @param transacted        the transaction indicator
     * @param acknowledge       the always acknowledge indicator
     * @param heartbeat         the send heart-beat indicator
     * @param direction         the direction, i.e. Incoming, Outgoing, etc..
     */
    public AbstractZmqGateway(final String name, final ZmqSocketContext socketContext,
        final ZmqFilterPolicy filter, final ZmqEventHandler handler, final ZmqGatewayListener listener,
        final ZmqJournalStore store, final ZmqMessageSelector selector, final ZmqRedeliveryPolicy redelivery,
        final boolean transacted, final boolean acknowledge,
        final boolean heartbeat, final Direction direction) {

        this.name = name;
        this.type = socketContext.getType();
        this.socketContext = new ZmqSocketContext(socketContext);
        this.bound = socketContext.isBindFlag();
        this.addr = socketContext.getAddr();
        this.flags = socketContext.getRecieveMsgFlag();
        this.filterPolicy = filter;
        this.eventHandler = handler;
        this.listener = listener;
        this.journalStore = store;
        this.messageSelector = selector;
        this.redelivery = redelivery;
        this.transacted = transacted;
        this.acknowledge = acknowledge;
        this.heartbeat = heartbeat;
        this.direction = direction;
        this.startDateTime = new Date();

        this.metrics = Collections.synchronizedList(new LinkedList());
        this.socketSessions = Collections.synchronizedMap(new HashMap());
    }

    /**
     * @return  return a list of socket addresses
     */
    protected String[] getSocketAddrs() {
        String[] addrs = addr.split(",");
        return addrs;
    }

    /**
     * wait for a status to change and return true, otherwise timeout and return false.
     * @param  millis    the milliseconds to wait before giving up
     * @param  onStatus  the set of status you are waiting for
     * @return           return true when the status have been met
     */
    protected boolean waitOnStatus(final long millis, final EnumSet onStatus) {
        final Stopwatch stopwatch = new Stopwatch();

        long waitTime = SOCKET_WAIT_MILLI_SECOND;

        if (millis < 0) {
            waitTime = SOCKET_STATUS_TIMEOUT_MILLI_SECOND;
        } else if (millis < waitTime) {
            waitTime = millis / 2;
        }
        boolean success = false;

        do {
            success = true;

            for (ZmqSocketSession socketSession : socketSessions.values()) {
                final ZmqSocketStatus status = socketSession.getStatus();

                if (!onStatus.contains(status)) {
                    success = false;
                    break;
                }
            }

            if (success) {
                break;
            }

            stopwatch.sleep(waitTime);
        } while (stopwatch.before(millis));

        return success;
    }

    @Override
    public void open(final int timeout) {
        if (active.get()) {
            return;
        }

        context = ZMQ.context(socketContext.getIOThreads());

        active.set(true);

        if (journalStore != null) {
            try {
                journalStore.open();
            } catch (ZmqException ex) {
                LOGGER.log(Level.SEVERE, "Unable to journal store: " + journalStore, ex);
                return;
            }
        }

        listenerExecutor = Executors.newFixedThreadPool(LISTENER_THREAD_POOL);

        if (listener != null) {
            ListenerThread listenerThread = new ListenerThread();
            listenerExecutor.execute(listenerThread);
        }

        String[] socketAddrs = getSocketAddrs();
        socketExecutor = Executors.newFixedThreadPool(socketAddrs.length);
        proxyExecutor = (socketContext.isProxy()) ? Executors.newFixedThreadPool(1) : null;

        final boolean socketOutgoing = (direction == Direction.OUTGOING || heartbeat || acknowledge);
        final boolean socketIncoming = (direction == Direction.INCOMING || heartbeat || acknowledge);

        // Setup the ZMQ sockets
        for (String socketAddr : socketAddrs) {
            final ZMQ.Socket socket = getSocket(context, socketContext);

            // Set the filters when they exist
            if (type == ZmqSocketType.SUB && filterPolicy != null) {
                String[] filters = filterPolicy.getSubscirbeTags();
                if (filters != null) {
                    for (String filter : filters) {
                        byte[] filterAsBytes = filter.getBytes();
                        socket.subscribe(filterAsBytes);
                    }
                }
            }

            ZmqSocketSession socketSession = socketSessions.get(socketAddr);
            // re-use socket metrics on a closed socket
            ZmqSocketMetrics socketMetrics = (socketSession != null) ? socketSession.getMetrics() : null;

            if (socketMetrics == null) {
                socketMetrics = new ZmqSocketMetrics(socketAddr, SOCKET_METRIC_BUCKET_COUNT,
                    SOCKET_METRIC_BUCKET_INTERVAL_MILLI_SECOND, socketOutgoing, socketIncoming);
                metrics.add(socketMetrics);
            }

            final ZmqSocketListener socketListener = getSocketListener(socketAddr, socketIncoming, socketOutgoing);

            socketSession = new ZmqSocketSession(name, active,
                socket, type, socketAddr, bound, socketIncoming, socketOutgoing, flags,
                SOCKET_WAIT_MILLI_SECOND, heartbeat, acknowledge, socketListener, filterPolicy, eventHandler, socketMetrics);

            // override closed socket (cannot re-use)
            socketSessions.put(socketAddr, socketSession);
            socketExecutor.execute(socketSession);

            // Make sure only ONE bound session (address) is active on startup
            if (socketSession.isBound()) {
                ZmqSocketStatus status = socketSession.getStatus();

                while (status == ZmqSocketStatus.STOPPED || status == ZmqSocketStatus.PENDING) {
                    try {
                        Thread.sleep(SOCKET_WAIT_MILLI_SECOND);
                    } catch (InterruptedException ex) {
                        LOGGER.warning("Binding sleep interrupted: " + this);
                    }
                    status = socketSession.getStatus();
               }
            }
        }

        //Setup the ZMQ PROXY
        if (socketContext.isProxy()) {
            proxyContext = ZMQ.context(socketContext.getIOThreads());

            final String proxyName = "proxy(" + name + ")";
            final String frontSocketAddr = socketContext.getProxyAddr();
            final ZmqSocketType frontSocketType = (socketContext.getProxyType() == null) ? ZmqSocketType.ROUTER : socketContext.getProxyType();
            final boolean frontSocketBound = true;
            final ZMQ.Socket frontSocket = context.socket(frontSocketType.getType());
            final String backSocketAddr = addr;
            final ZmqSocketType backSocketType = (socketContext.getProxyOutType() == null) ? ZmqSocketType.DEALER : socketContext.getProxyOutType();
            final boolean backSocketBound = true;
            final ZMQ.Socket backSocket =  context.socket(backSocketType.getType());

            proxySession =
                new ZmqProxySession(proxyName, active,
                    frontSocket, frontSocketType, frontSocketAddr, frontSocketBound,
                    backSocket, backSocketType, backSocketAddr, backSocketBound);
            proxyExecutor.execute(proxySession);
        }

        waitOnStatus(timeout,
            EnumSet.of(ZmqSocketStatus.RUNNING, ZmqSocketStatus.PAUSED, ZmqSocketStatus.ERROR));

        LOGGER.info("Gateway openned: " + toString());
    }

    /**
     * Construct a ZMQ socket and initialise default settings.
     * @param context           the Zero MQ context
     * @param socketContext     the Zero MQ socket context
     * @return                  return the constructed and initialised ZMQ socket
     */
    protected ZMQ.Socket getSocket(final ZMQ.Context context, final ZmqSocketContext socketContext) {
        final int socketType = socketContext.getType().getType();
        final ZMQ.Socket socket = context.socket(socketType);

        socket.setSendTimeOut(0);

        final Map valueMap = new HashMap();

        for (Method method : socketContext.getClass().getMethods()) {
            if ((method.getParameterTypes().length == 0) && (method.getReturnType() != null)) {
                final String getterMethodName = method.getName();
                if (getterMethodName.startsWith("get")) {
                    try {
                        final Object value = method.invoke(socketContext);

                        if (value != null) {
                            valueMap.put(getterMethodName.substring(3), value);
                        }
                    } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
                        LOGGER.log(Level.WARNING, "Ignoring 'getter' as potential 'setter': " + getterMethodName, ex);
                    }
                }
            }
        }

        for (Method method : socket.getClass().getMethods()) {
            if ((method.getParameterTypes().length == 1) && (method.getReturnType() == null)) {
                final String setterMethodName = method.getName();
                if (setterMethodName.startsWith("set")) {
                    final Object value = valueMap.get(setterMethodName.substring(3));

                    if (value != null) {
                        try {
                            method.invoke(socket, value);
                        } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
                            LOGGER.log(Level.WARNING, "Ignoring 'setting' of socket context': " + setterMethodName, ex);
                        }
                    }
                }
            }
        }

        return socket;
    }

    /**
     * Return a socket listener for socket session. This is the main "event" based routine that will be extended
     * to add additional functionality.
     * @param socketAddr      the ZMQ address
     * @param socketIncoming  the incoming messages indicator
     * @param socketOutgoing  the outgoing messages indicator
     * @return                return the instance of the listener
     */
    protected ZmqSocketListener getSocketListener(final String socketAddr,
        final boolean socketIncoming, final boolean socketOutgoing) {

        final ZmqSocketListener socketListener = new ZmqSocketListener() {
            @Override
            public boolean open(final ZmqSocketSession session) {
                return socketOpen(session);
            }

            @Override
            public ZmqEvent send(final ZmqSocketSession session) {
                return socketSend(session);
            }

            @Override
            public void error(final ZmqSocketSession session, final ZmqEvent event) {
                socketError(session, event);
            }

            @Override
            public ZmqEvent receive(final ZmqSocketSession session, final ZmqEvent event) {
                return socketReceive(session, event);
            }

            @Override
            public boolean close(final ZmqSocketSession session) {
                return socketClose(session);
            }
        };

        return socketListener;
    }

    @Override
    public boolean isActive() {
        return active.get();
    }

    @Override
    public void close(final int timeout) {
        active.set(false);

        if (proxyContext != null) {
            // need to interrupt the proxy
            proxyContext.close();
        }

        if (acknowledge) {
            // Wait for a period before warning about failed ACKS
            int totalCount = 0;

            for (ZmqSocketSession socketSession : socketSessions.values()) {
                final int sessionCount = socketSession.trackedCount();

                if (sessionCount > 0) {
                    LOGGER.info("Gateway [" + name + "@" + socketSession.getAddr() + "] waiting for acknowledgement message(s): "
                        + sessionCount);
                }

                totalCount = totalCount + sessionCount;
            }

            if (totalCount > 0) {
                try {
                    Thread.sleep(SOCKET_WAIT_MILLI_SECOND);
                } catch (InterruptedException ex) {
                    LOGGER.throwing(AbstractZmqGateway.class.getCanonicalName(), "close()", ex);
                }
            }
        }

        // What for sockets to shut down
        waitOnStatus(timeout, EnumSet.of(ZmqSocketStatus.STOPPED));

        if (journalStore != null) {
            try {
                journalStore.close();
            } catch (ZmqException ex) {
                LOGGER.log(Level.SEVERE, "Gateway [" + name + "] unable to close the journal store: " + journalStore, ex);
            }
        }

        if (listenerExecutor != null) {
            try {
                listenerExecutor.shutdown();
                final boolean success = listenerExecutor.awaitTermination(3, TimeUnit.SECONDS);

                if (!success) {
                    LOGGER.severe("Gateway [" + name + "] listener threads failed to stop: " + toString());
                }
            } catch (InterruptedException ex) {
                LOGGER.log(Level.SEVERE, "Gateway [" + name + "] listener threads failed to stop: " + toString(), ex);
            }
        }

        if (proxyExecutor != null) {
            try {
                proxyExecutor.shutdown();
                final boolean success = proxyExecutor.awaitTermination(3, TimeUnit.SECONDS);

                if (!success) {
                    LOGGER.warning("Proxy thread fails to stop, until context terminated (ZMQ issue): " + toString());
                }
            } catch (InterruptedException ex) {
                LOGGER.log(Level.SEVERE, "Proxy threads failed to stop: " + toString(), ex);
            }
        }

        if (socketExecutor != null) {
            try {
                socketExecutor.shutdown();
                final boolean success = socketExecutor.awaitTermination(3, TimeUnit.SECONDS);

                if (!success) {
                    LOGGER.severe("Socket threads failed to stop: " + toString());
                }
            } catch (InterruptedException ex) {
                LOGGER.log(Level.SEVERE, "Socket threads failed to stop: " + toString(), ex);
            }
        }

        // dump all tracked missing
        if (acknowledge) {
            for (ZmqSocketSession socketSession : socketSessions.values()) {
                List lostEvents = socketSession.untrackAll();
                for (ZmqSocketSession.TrackEvent lostEvent : lostEvents) {
                    // Only send events should be logged as warnings, heart-beats can be ignored.
                    if (lostEvent.getEvent() instanceof ZmqSendEvent) {
                        LOGGER.warning("Gateway [" + name + "] has un-acknowledged message (LOST): " + lostEvent);
                    }
                }
            }
        }

        context.close();
        LOGGER.info("Gateway closed: " + toString());
    }

    /**
     * Socket open event has been triggered. For "connecting" sockets the
     * opening is always granted, but for "bind" we must ensure ONLY one of
     * the sockets is bound, while others stay in pending state.
     * @param source  the socket session
     * @return        return the true to open socket (difference between connect and bind)
     */
    protected boolean socketOpen(final ZmqSocketSession source) {
        if (source.isBound()) {
            for (ZmqSocketSession socketSession : socketSessions.values()) {
                ZmqSocketStatus status = socketSession.getStatus();
                if (status == ZmqSocketStatus.RUNNING) {
                    return false;
                }
            }
        }

        return true;
    }

    /**
     * Socket close event has been triggered.
     * @param source  the socket session
     * @return        return the true to close socket
     */
    protected boolean socketClose(final ZmqSocketSession source) {
        return true;
    }

    /**
     * Socket send event has been triggered.
     * @param source  the socket session
     * @return        return the event to be sent by the session
     */
    protected ZmqEvent socketSend(final ZmqSocketSession source) {
        final String socketAddr = source.getAddr();
        final boolean socketOutgoing = source.isOutgoing();

        ZmqEvent sendEvent = null;

        // Only get real message if the socket session is running.
        if (source.getStatus() == ZmqSocketStatus.RUNNING) {
            if (journalStore != null) {
                try {
                    final ZmqJournalEntry journalEntry = journalStore.read();
                    if (journalEntry != null && (!source.isTracked(journalEntry.getMessageId()))) {
                        sendEvent =
                                eventHandler.createSendEvent(journalEntry.getMessageId(), journalEntry.getMessage());
                    }
                } catch (ZmqException ex) {
                    LOGGER.log(Level.WARNING, "Socket [" + name + "@" + socketAddr + "] failed to read from the journal store", ex);
                }
            }

            if (sendEvent == null) {
                try {
                    sendEvent = outgoingQueue.poll(SOCKET_WAIT_MILLI_SECOND, TimeUnit.MILLISECONDS);
                } catch (InterruptedException ex) {
                    LOGGER.log(Level.WARNING, "Socket [" + name + "@" + socketAddr + "] polling of outgoing queue interrupted", ex);
                }
            }
        }

        // No message(s) so send a heart-beat when required
        if (heartbeat && socketOutgoing && sendEvent == null) {
            // check whether a heart-beat need to be sent since the last message sent
            final long lastReceiveTime = source.getLastReceiveTime();
            final long lastSendTime = source.getLastSendTime();
            final long currentTime = System.nanoTime();
            final long lastReceiveLaspedTime = (currentTime - lastReceiveTime) / 1000000;
            final long lastSendLaspedTime = (currentTime - lastSendTime) / 1000000;
            final ZmqSocketStatus status = source.getStatus();

            if (lastReceiveLaspedTime > HEARTBEAT_RATE_MILLI_SECOND && lastSendLaspedTime > HEARTBEAT_RATE_MILLI_SECOND) {
                if (lastReceiveLaspedTime > AUTO_PAUSE_IDLE_MILLI_SECOND && status == ZmqSocketStatus.RUNNING) {
                    // connection has been dropped, so stop sending messages apart from heart-beats
                    source.pause();

                    List redoEvents = source.untrackAll();
                    for (ZmqSocketSession.TrackEvent redoEvent : redoEvents) {
                        // Only save the send events, heart-beats can be ignored.
                        if (redoEvent.getEvent() instanceof ZmqSendEvent) {
                            socketError(source, redoEvent.getEvent());
                        }
                    }

                } else {
                    sendEvent = eventHandler.createHeartbeatEvent();

                    if (LOGGER.isLoggable(Level.FINEST)) {
                        LOGGER.log(Level.FINEST, "Socket [" + name + "@" + socketAddr + "] send heartbeat: " + sendEvent);
                    }
                }
            }

        }

        if (sendEvent != null && (socketOutgoing && acknowledge)) {
            source.track(sendEvent);

            if (LOGGER.isLoggable(Level.FINEST)) {
                LOGGER.log(Level.FINEST, "Socket [" + name + "@" + socketAddr + "] tacking event: " + sendEvent);
            }
        }

        if (LOGGER.isLoggable(Level.FINEST) && sendEvent != null) {
            LOGGER.log(Level.FINEST, "Socket [" + name + "@" + socketAddr + "] send event: " + sendEvent);
        }

        return sendEvent;
    }

    /**
     * There has been an error relating to the following event.
     * @param source   the socket session having the exception
     * @param event    the event involved
     */
    public void socketError(final ZmqSocketSession source, final ZmqEvent event) {
        if (event instanceof ZmqSendEvent) {
            final ZmqSendEvent sendEvent = (ZmqSendEvent) event;

            try {
                outgoingQueue.put(sendEvent);

                if (LOGGER.isLoggable(Level.FINEST) && sendEvent != null) {
                    LOGGER.log(Level.FINEST, "Socket [" + source.getAddr() + "] send event: " + sendEvent);
                }
            } catch (InterruptedException ex) {
                final String socketAddr = source.getAddr();
                LOGGER.log(Level.SEVERE, "Socket [" + name + "@" + socketAddr + "] was unable to re-send event: " + event, ex);
            }
        }
    }

    /**
     * Return the response event for the socket receiving the specified event
     * There has been an error relating to the following event.
     * @param  source   the socket session which received the event
     * @param  event    the event received
     * @return          return a response event
     */
    public ZmqEvent socketReceive(final ZmqSocketSession source, final ZmqEvent event) {
        final String socketAddr = source.getAddr();

        if (LOGGER.isLoggable(Level.FINEST)) {
            LOGGER.log(Level.FINEST, "Socket [" + name + "@" + socketAddr + "] consume event: " + event);
        }
        if (event instanceof ZmqSendEvent) {
            try {
                if (journalStore != null) {
                    journalStore.create(event.getMessageId(),  ((ZmqSendEvent) event).getMessage());
                }

                incomingQueue.put((ZmqSendEvent) event);
            } catch (InterruptedException ex) {
                LOGGER.log(Level.SEVERE, "Socket [" + name + "@" + socketAddr + "] for gateway " + name
                    + " cannot consume message due to intenral error: " + event, ex);

                return null;
            } catch (ZmqException ex) {
                LOGGER.log(Level.SEVERE, "Socket [" + name + "@" + socketAddr + "] for gateway " + name
                    + " cannot store messahe due to intenral error: " + event, ex);

                return null;
            }
        }

        ZmqEvent replyEvent = null;
        if (event instanceof ZmqHeartbeatEvent) {
            // Heart beat is ALL SENDS
            try {
                if (source.isAcknowledge() && source.isIncoming()) {
                    replyEvent = eventHandler.createAckEvent(event);
                }
            } catch (ZmqException ex) {
                LOGGER.log(Level.SEVERE, "Socket [" + name + "@" + socketAddr + "] received corrupt event: " + event, ex);
            }
        } else if (event instanceof ZmqAckEvent) {
            ZmqAckEvent ackEvent = (ZmqAckEvent) event;
            final Object messageId = ackEvent.getMessageId();
            if (messageId == null) {
                LOGGER.log(Level.SEVERE, "Socket [" + name + "@" + socketAddr + "] received corrupt event: " + event);
            } else {
                LOGGER.log(Level.INFO, "Socket [" + name + "@" + socketAddr + "] received ACK event: " + event);
                final ZmqSocketSession.TrackEvent trackedEvent = source.untrack(messageId);
                if (trackedEvent == null) {
                    LOGGER.log(Level.WARNING, "Socket [" + name + "@" + socketAddr + "] received ACK for untracked event: "
                        + event);
                }
            }
        }
        if (replyEvent != null && LOGGER.isLoggable(Level.FINEST)) {
            LOGGER.log(Level.FINEST, "Socket [" + name + "@" + socketAddr + "] reply event: " + event);
        }

        return replyEvent;
    }

    @Override
    public void start() {
    }

    @Override
    public void stop() {
    }

    @Override
    public void commit() throws ZmqException {
        if (!active.get()) {
            throw new ZmqException("The gateway has been close: " + toString());
        }

        if (!transacted) {
            throw new ZmqException("No transacion started: " + toString());
        }

        synchronized (outgoingSnapshot) {
            for (ZmqSendEvent event : outgoingSnapshot) {
                outgoingQueue.add(event);

                if (journalStore != null) {
                    journalStore.create(event.getMessageId(), event.getMessage());
                }
            }

            outgoingSnapshot.clear();
        }

        synchronized (incomingSnapshot) {
            if (redelivery != null) {
                redelivery.delivered(incomingSnapshot);

            }

            for (final ZmqSendEvent event : incomingSnapshot) {
                if (journalStore != null) {
                    journalStore.delete(event.getMessageId());
                }
            }

            incomingSnapshot.clear();
        }

        LOGGER.fine("Transaction committed: " + toString());
    }

    @Override
    public void rollback() throws ZmqException {
        if (!active.get()) {
            throw new ZmqException("The gateway has been close: " + toString());
        }

        if (!transacted) {
            throw new ZmqException("No transacion started: " + toString());
        }

        synchronized (outgoingSnapshot) {
            outgoingSnapshot.clear();
        }

        synchronized (incomingSnapshot) {
            if (redelivery != null) {
                redelivery.redeliver(incomingSnapshot);
            }

            incomingSnapshot.clear();
        }

        LOGGER.fine("Transaction rolledback: " + toString());
    }

    @Override
    public void send(final ZmqMessage message) throws ZmqException {
        final ZmqSendEvent event = eventHandler.createSendEvent(message);

        if (transacted) {
            synchronized (outgoingSnapshot) {
                outgoingSnapshot.add(event);
            }
        } else {
            outgoingQueue.add(event);

            if (journalStore != null) {
                journalStore.create(event.getMessageId(), event.getMessage());
            }
        }
    }

    /**
     * Return true when the message passes the JMS selector or non specified.
     * @param message  the message
     * @return         return true on selector pass or no selector specified.
     */
    protected boolean isValidMessage(final ZmqMessage message) {
        if (messageSelector != null) {
            final Map variables = message.getProperties();
            final boolean validMessage = messageSelector.evaluate(variables);

            return validMessage;
        }

        return true;
    }

    @Override
    public ZmqMessage receive() throws ZmqException {
        ZmqMessage message = receive(SOCKET_WAIT_MILLI_SECOND);

        while (message == null && active.get()) {
            message = receive(SOCKET_WAIT_MILLI_SECOND);
        }

        if (message == null) {
            throw new ZmqException("Receive request, buy gateway has been close: " + toString());
        }

        return message;
    }

    @Override
    public ZmqMessage receive(final int timeout) throws ZmqException {
        Stopwatch stopwatch = null;

        if (LOGGER.isLoggable(Level.FINER)) {
            stopwatch = new Stopwatch();
        }

        if (!active.get()) {
            throw new ZmqException("Receive request, buy gateway has been close: " + toString());
        }

        // check for re-delivers
        if (redelivery != null) {
            final ZmqSendEvent event = redelivery.getNextRedeliver();

            if (event != null) {
                final ZmqMessage message = event.getMessage();

                if (transacted) {
                    synchronized (incomingSnapshot) {
                        incomingSnapshot.add(event);
                    }
                }

                if (stopwatch != null) {
                    LOGGER.log(Level.FINER, "Receive re-delivery message: " + stopwatch.lapsedTime() + " (msec) :" + toString());
                }

                return message;
            }
        }

        // check the journal store for any messages.
        if (journalStore != null) {
            ZmqJournalEntry journalEntry = journalStore.read();

            if (journalEntry != null) {
                if (transacted) {
                    final ZmqSendEvent event =
                        eventHandler.createSendEvent(journalEntry.getMessageId(), journalEntry.getMessage());

                    synchronized (outgoingSnapshot) {
                        outgoingSnapshot.add(event);
                    }
                } else {
                    journalStore.delete(journalEntry.getMessageId());
                }

                return journalEntry.getMessage();
            }
        }

        // check the message from the JeroMQ queue.
        final long startTime = System.currentTimeMillis();

        try {
            final ZmqSendEvent event = incomingQueue.poll(timeout, TimeUnit.MILLISECONDS);

            if (event != null) {
                ZmqMessage message = event.getMessage();
                if (isValidMessage(message)) {
                    // when transacted kept track of messages for roll-back
                    if (transacted) {
                        synchronized (incomingSnapshot) {
                            incomingSnapshot.add(event);
                        }
                    } else {
                        if (journalStore != null) {
                            journalStore.create(event.getMessageId(), message);
                        }
                    }

                    if (stopwatch != null) {
                        LOGGER.log(Level.FINER, "Gateway [" + name + "] receive incoming message: " + stopwatch.lapsedTime() + " (msec)");
                    }

                    return message;
                }
            }
        } catch (InterruptedException ex) {
            throw new ZmqException("Unable to poll internal queue: " + toString(), ex);
        }

        final long endTime = System.currentTimeMillis();
        final long lapsedTime = endTime - startTime;

        if (lapsedTime < timeout) {
            final int remainingTimeout = (int) (timeout - lapsedTime);

            final ZmqMessage message = receive(remainingTimeout);

            if (stopwatch != null) {
                if (message == null) {
                    LOGGER.log(Level.FINER, "Gateway  [" + name + "] receive incoming message (Wait): " + stopwatch.lapsedTime() + " (msec)");
                } else {
                    LOGGER.log(Level.FINER, "Gatewau  [" + name + "] receive no message (Timeout): " + stopwatch.lapsedTime() + " (msec)");
                }
            }

            return message;
        }

        return null;
    }

    @Override
    public void setListener(final ZmqGatewayListener listener) {
        if (listener != null && this.listener == null) {
            ListenerThread listenerThread = new ListenerThread();
            listenerExecutor.execute(listenerThread);
        }

        this.listener = listener;
    }

    /**
     * @return  return the metric of the protocol, or null when nothing is being measured.
     */
    public List getMetrics() {
        return metrics;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public String getAddr() {
        return addr;
    }

    @Override
    public ZmqSocketType getType() {
        return type;
    }

    @Override
    public ZmqSocketContext getSocketContext() {
        return socketContext;
    }

    @Override
    public boolean isBound() {
        return bound;
    }

    @Override
    public boolean isTransacted() {
        return transacted;
    }

    @Override
    public boolean isAcknowledged() {
        return acknowledge;
    }

    @Override
    public boolean isHeartbeat() {
        return heartbeat;
    }

    @Override
    public Direction getDirection() {
        return direction;
    }

    @Override
    public Date getStartTime() {
        return startDateTime;
    }

    /**
     * @return  return the ZMQ context
     */
    protected ZMQ.Context getContext() {
        return context;
    }

    /**
     * @return  return the JMS to ZMQ filter resolver
     */
    protected ZmqFilterPolicy getFilterPolicy() {
        return filterPolicy;
    }

    /**
     * @return  return the ZMQ, protocol, JMS message marshaler
     */
    protected ZmqEventHandler getEventHandler() {
        return eventHandler;
    }

    /**
     * @return  return the JMS message selector or null when no selection applied
     */
    protected ZmqMessageSelector getMessageSelector() {
        return messageSelector;
    }

    @Override
    public String toString() {
        return getClass().getCanonicalName()
            + " [active=" + active + ", name=" + name + ", type=" + type + ", isBound=" + bound + ", addr=" + addr
            + ", proxyAddr=" + socketContext.getProxyAddr()
            + ", transacted=" + transacted + ", acknowleged=" + acknowledge + ", heartbeat=" + heartbeat + ", direction=" + direction
            + ", eventHandler=" + eventHandler + ", journalStore=" + journalStore + "]";
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy