@Override
  public void stop() throws Exception {
    if (stopping) {
      return;
    }

    stopping = true;

    if (logger.isDebugEnabled()) {
      logger.debug("Bridge " + this.name + " being stopped");
    }

    if (futureScheduledReconnection != null) {
      futureScheduledReconnection.cancel(true);
    }

    executor.execute(new StopRunnable());

    if (notificationService != null) {
      TypedProperties props = new TypedProperties();
      props.putSimpleStringProperty(new SimpleString("name"), name);
      Notification notification =
          new Notification(nodeUUID.toString(), CoreNotificationType.BRIDGE_STOPPED, props);
      try {
        notificationService.sendNotification(notification);
      } catch (Exception e) {
        ActiveMQServerLogger.LOGGER.broadcastBridgeStoppedError(e);
      }
    }
  }
  @Override
  public void connectionFailed(
      final ActiveMQException me, boolean failedOver, String scaleDownTargetNodeID) {
    ActiveMQServerLogger.LOGGER.bridgeConnectionFailed(failedOver);

    synchronized (connectionGuard) {
      keepConnecting = true;
    }

    try {
      if (producer != null) {
        producer.close();
      }

      cleanUpSessionFactory(csf);
    } catch (Throwable dontCare) {
    }

    try {
      session.cleanUp(false);
    } catch (Throwable dontCare) {
    }

    if (scaleDownTargetNodeID != null && !scaleDownTargetNodeID.equals(nodeUUID.toString())) {
      synchronized (this) {
        try {
          logger.debug(
              "Moving "
                  + queue.getMessageCount()
                  + " messages from "
                  + queue.getName()
                  + " to "
                  + scaleDownTargetNodeID);
          ((QueueImpl) queue)
              .moveReferencesBetweenSnFQueues(SimpleString.toSimpleString(scaleDownTargetNodeID));

          // stop the bridge from trying to reconnect and clean up all the bindings
          fail(true);
        } catch (Exception e) {
          ActiveMQServerLogger.LOGGER.warn(e.getMessage(), e);
        }
      }
    } else if (scaleDownTargetNodeID != null) {
      // the disconnected node is scaling down to me, no need to reconnect to it
      logger.debug(
          "Received scaleDownTargetNodeID: " + scaleDownTargetNodeID + "; cancelling reconnect.");
      fail(true);
    } else {
      logger.debug("Received invalid scaleDownTargetNodeID: " + scaleDownTargetNodeID);

      fail(me.getType() == ActiveMQExceptionType.DISCONNECTED);
    }

    tryScheduleRetryReconnect(me.getType());
  }
  public static final byte[] getDuplicateBytes(final UUID nodeUUID, final long messageID) {
    byte[] bytes = new byte[24];

    ByteBuffer bb = ByteBuffer.wrap(bytes);

    bb.put(nodeUUID.asBytes());

    bb.putLong(messageID);

    return bytes;
  }
  @Override
  public String sendMessage(
      final Map<String, String> headers,
      final int type,
      final String body,
      final String userID,
      boolean durable,
      final String user,
      final String password)
      throws Exception {
    securityStore.check(
        queue.getAddress(),
        CheckType.SEND,
        new SecurityAuth() {
          @Override
          public String getUsername() {
            return user;
          }

          @Override
          public String getPassword() {
            return password;
          }

          @Override
          public RemotingConnection getRemotingConnection() {
            return null;
          }
        });
    ServerMessageImpl message = new ServerMessageImpl(storageManager.generateID(), 50);
    for (String header : headers.keySet()) {
      message.putStringProperty(new SimpleString(header), new SimpleString(headers.get(header)));
    }
    message.setType((byte) type);
    message.setDurable(durable);
    message.setTimestamp(System.currentTimeMillis());
    message.setUserID(new UUID(UUID.TYPE_TIME_BASED, UUID.stringToBytes(userID)));
    if (body != null) {
      message.getBodyBuffer().writeBytes(Base64.decode(body));
    }
    message.setAddress(queue.getAddress());
    postOffice.route(message, null, true);
    return "" + message.getMessageID();
  }
  @Override
  public void pause() throws Exception {
    if (logger.isDebugEnabled()) {
      logger.debug("Bridge " + this.name + " being paused");
    }

    executor.execute(new PauseRunnable());

    if (notificationService != null) {
      TypedProperties props = new TypedProperties();
      props.putSimpleStringProperty(new SimpleString("name"), name);
      Notification notification =
          new Notification(nodeUUID.toString(), CoreNotificationType.BRIDGE_STOPPED, props);
      try {
        notificationService.sendNotification(notification);
      } catch (Exception e) {
        ActiveMQServerLogger.LOGGER.notificationBridgeStoppedError(e);
      }
    }
  }
  @Override
  public synchronized void start() throws Exception {
    if (started) {
      return;
    }

    started = true;

    stopping = false;

    activate();

    if (notificationService != null) {
      TypedProperties props = new TypedProperties();
      props.putSimpleStringProperty(new SimpleString("name"), name);
      Notification notification =
          new Notification(nodeUUID.toString(), CoreNotificationType.BRIDGE_STARTED, props);
      notificationService.sendNotification(notification);
    }
  }
  /* This is called only when the bridge is activated */
  protected void connect() {
    if (stopping) return;

    synchronized (connectionGuard) {
      if (!keepConnecting) return;

      logger.debug(
          "Connecting  "
              + this
              + " to its destination ["
              + nodeUUID.toString()
              + "], csf="
              + this.csf);

      retryCount++;

      try {
        if (csf == null || csf.isClosed()) {
          if (stopping) return;
          csf = createSessionFactory();
          if (csf == null) {
            // Retrying. This probably means the node is not available (for the cluster connection
            // case)
            scheduleRetryConnect();
            return;
          }
          // Session is pre-acknowledge
          session =
              (ClientSessionInternal) csf.createSession(user, password, false, true, true, true, 1);
          sessionConsumer =
              (ClientSessionInternal) csf.createSession(user, password, false, true, true, true, 1);
        }

        if (forwardingAddress != null) {
          ClientSession.AddressQuery query = null;

          try {
            query = session.addressQuery(forwardingAddress);
          } catch (Throwable e) {
            ActiveMQServerLogger.LOGGER.errorQueryingBridge(e, name);
            // This was an issue during startup, we will not count this retry
            retryCount--;

            scheduleRetryConnectFixedTimeout(100);
            return;
          }

          if (forwardingAddress.startsWith(BridgeImpl.JMS_QUEUE_ADDRESS_PREFIX)
              || forwardingAddress.startsWith(BridgeImpl.JMS_TOPIC_ADDRESS_PREFIX)) {
            if (!query.isExists()) {
              ActiveMQServerLogger.LOGGER.errorQueryingBridge(forwardingAddress, retryCount);
              scheduleRetryConnect();
              return;
            }
          } else {
            if (!query.isExists()) {
              ActiveMQServerLogger.LOGGER.bridgeNoBindings(
                  getName(), getForwardingAddress(), getForwardingAddress());
            }
          }
        }

        producer = session.createProducer();
        session.addFailureListener(BridgeImpl.this);

        session.setSendAcknowledgementHandler(BridgeImpl.this);

        afterConnect();

        active = true;

        queue.addConsumer(BridgeImpl.this);
        queue.deliverAsync();

        ActiveMQServerLogger.LOGGER.bridgeConnected(this);

        // We only do this on plain core bridges
        if (isPlainCoreBridge()) {
          serverLocator.addClusterTopologyListener(new TopologyListener());
        }

        keepConnecting = false;
        return;
      } catch (ActiveMQException e) {
        // the session was created while its server was starting, retry it:
        if (e.getType() == ActiveMQExceptionType.SESSION_CREATION_REJECTED) {
          ActiveMQServerLogger.LOGGER.errorStartingBridge(name);

          // We are not going to count this one as a retry
          retryCount--;

          scheduleRetryConnectFixedTimeout(this.retryInterval);
          return;
        } else {
          if (logger.isDebugEnabled()) {
            logger.debug("Bridge " + this + " is unable to connect to destination. Retrying", e);
          }

          scheduleRetryConnect();
        }
      } catch (ActiveMQInterruptedException | InterruptedException e) {
        ActiveMQServerLogger.LOGGER.errorConnectingBridge(e, this);
      } catch (Exception e) {
        ActiveMQServerLogger.LOGGER.errorConnectingBridge(e, this);
        if (csf != null) {
          try {
            csf.close();
            csf = null;
          } catch (Throwable ignored) {
          }
        }
        fail(false);
        scheduleRetryConnect();
      }
    }
  }