private void endTransaction( //
      final SendResult sendResult, //
      final LocalTransactionState localTransactionState, //
      final Throwable localException)
      throws RemotingException, MQBrokerException, InterruptedException, UnknownHostException {
    final MessageId id = MessageDecoder.decodeMessageId(sendResult.getMsgId());
    final String addr = RemotingUtil.socketAddress2String(id.getAddress());
    EndTransactionRequestHeader requestHeader = new EndTransactionRequestHeader();
    requestHeader.setCommitLogOffset(id.getOffset());
    switch (localTransactionState) {
      case COMMIT_MESSAGE:
        requestHeader.setCommitOrRollback(MessageSysFlag.TransactionCommitType);
        break;
      case ROLLBACK_MESSAGE:
        requestHeader.setCommitOrRollback(MessageSysFlag.TransactionRollbackType);
        break;
      case UNKNOW:
        requestHeader.setCommitOrRollback(MessageSysFlag.TransactionNotType);
        break;
      default:
        break;
    }

    requestHeader.setProducerGroup(this.defaultMQProducer.getProducerGroup());
    requestHeader.setTranStateTableOffset(sendResult.getQueueOffset());
    requestHeader.setMsgId(sendResult.getMsgId());
    String remark =
        localException != null
            ? ("executeLocalTransactionBranch exception: " + localException.toString())
            : null;
    this.mQClientFactory
        .getMQClientAPIImpl()
        .endTransactionOneway(
            addr, requestHeader, remark, this.defaultMQProducer.getSendMsgTimeout());
  }
  public TransactionSendResult sendMessageInTransaction(
      final Message msg, final LocalTransactionExecuter tranExecuter, final Object arg)
      throws MQClientException {
    if (null == tranExecuter) {
      throw new MQClientException("tranExecuter is null", null);
    }

    // 第一步,向Broker发送一条Prepared消息
    SendResult sendResult = null;
    msg.putProperty(MessageConst.PROPERTY_TRANSACTION_PREPARED, "true");
    msg.putProperty(
        MessageConst.PROPERTY_PRODUCER_GROUP, this.defaultMQProducer.getProducerGroup());
    try {
      sendResult = this.send(msg);
    } catch (Exception e) {
      throw new MQClientException("send message Exception", e);
    }

    // 第二步,回调本地事务
    LocalTransactionState localTransactionState = LocalTransactionState.UNKNOW;
    Throwable localException = null;
    try {
      localTransactionState = tranExecuter.executeLocalTransactionBranch(msg, arg);
      if (null == localTransactionState) {
        localTransactionState = LocalTransactionState.UNKNOW;
      }

      if (localTransactionState != LocalTransactionState.COMMIT_MESSAGE) {
        log.info("executeLocalTransactionBranch return {}", localTransactionState);
        log.info(msg.toString());
      }
    } catch (Throwable e) {
      log.info("executeLocalTransactionBranch exception", e);
      log.info(msg.toString());
      localException = e;
    }

    // 第三步,提交或者回滚Broker端消息
    try {
      this.endTransaction(sendResult, localTransactionState, localException);
    } catch (Exception e) {
      log.warn(
          "local transaction execute "
              + localTransactionState
              + ", but end broker transaction failed",
          e);
    }

    TransactionSendResult transactionSendResult = new TransactionSendResult();
    transactionSendResult.setSendStatus(sendResult.getSendStatus());
    transactionSendResult.setMessageQueue(sendResult.getMessageQueue());
    transactionSendResult.setMsgId(sendResult.getMsgId());
    transactionSendResult.setQueueOffset(sendResult.getQueueOffset());
    transactionSendResult.setLocalTransactionState(localTransactionState);
    return transactionSendResult;
  }
  private SendResult sendDefaultImpl( //
      Message msg, //
      final CommunicationMode communicationMode, //
      final SendCallback sendCallback //
      ) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
    // 有效性检查
    this.makeSureStateOK();
    Validators.checkMessage(msg, this.defaultMQProducer);

    final long beginTimestamp = System.currentTimeMillis();
    long endTimestamp = beginTimestamp;
    TopicPublishInfo topicPublishInfo = this.tryToFindTopicPublishInfo(msg.getTopic());
    if (topicPublishInfo != null && topicPublishInfo.ok()) {
      MessageQueue mq = null;
      Exception exception = null;
      SendResult sendResult = null;
      int timesTotal = 1 + this.defaultMQProducer.getRetryTimesWhenSendFailed();
      for (int times = 0;
          times < timesTotal
              && (endTimestamp - beginTimestamp) < this.defaultMQProducer.getSendMsgTimeout();
          times++) {
        String lastBrokerName = null == mq ? null : mq.getBrokerName();
        MessageQueue tmpmq = topicPublishInfo.selectOneMessageQueue(lastBrokerName);
        if (tmpmq != null) {
          mq = tmpmq;
          try {
            sendResult = this.sendKernelImpl(msg, mq, communicationMode, sendCallback);
            endTimestamp = System.currentTimeMillis();
            switch (communicationMode) {
              case ASYNC:
                return null;
              case ONEWAY:
                return null;
              case SYNC:
                if (sendResult.getSendStatus() != SendStatus.SEND_OK) {
                  if (this.defaultMQProducer.isRetryAnotherBrokerWhenNotStoreOK()) {
                    continue;
                  }
                }

                return sendResult;
              default:
                break;
            }
          } catch (RemotingException e) {
            log.warn("sendKernelImpl exception", e);
            log.warn(msg.toString());
            exception = e;
            endTimestamp = System.currentTimeMillis();
            continue;
          } catch (MQClientException e) {
            log.warn("sendKernelImpl exception", e);
            log.warn(msg.toString());
            exception = e;
            endTimestamp = System.currentTimeMillis();
            continue;
          } catch (MQBrokerException e) {
            log.warn("sendKernelImpl exception", e);
            log.warn(msg.toString());
            exception = e;
            endTimestamp = System.currentTimeMillis();
            switch (e.getResponseCode()) {
              case MQResponseCode.TOPIC_NOT_EXIST_VALUE:
              case MQResponseCode.SERVICE_NOT_AVAILABLE_VALUE:
              case ResponseCode.SYSTEM_ERROR_VALUE:
              case MQResponseCode.NO_PERMISSION_VALUE:
                continue;
              default:
                if (sendResult != null) {
                  return sendResult;
                }

                throw e;
            }
          } catch (InterruptedException e) {
            log.warn("sendKernelImpl exception", e);
            log.warn(msg.toString());
            throw e;
          }
        } else {
          break;
        }
      } // end of for

      if (sendResult != null) {
        return sendResult;
      }

      throw new MQClientException("Retry many times, still failed", exception);
    }

    List<String> nsList = this.getmQClientFactory().getMQClientAPIImpl().getNameServerAddressList();
    if (null == nsList || nsList.isEmpty()) {
      // 说明没有设置Name Server地址
      throw new MQClientException(
          "No name server address, please set it."
              + FAQUrl.suggestTodo(FAQUrl.NAME_SERVER_ADDR_NOT_EXIST_URL),
          null);
    }

    throw new MQClientException(
        "No route info of this topic, "
            + msg.getTopic()
            + FAQUrl.suggestTodo(FAQUrl.NO_TOPIC_ROUTE_INFO),
        null);
  }