@Test
  public void testHandleRequestPartitionClosedSlave() throws Exception {
    final int partition = 1;
    final int opaque = 0;
    final int maxSize = 1024;
    final long offset = 10;

    this.metaConfig.setTopics(Arrays.asList(this.topic));
    this.metaConfig.closePartitions(this.topic, partition, partition);

    final MessageStore store = this.mocksControl.createMock(MessageStore.class);
    EasyMock.expect(this.storeManager.getMessageStore(this.topic, partition)).andReturn(store);
    final MessageSet set = this.mocksControl.createMock(MessageSet.class);
    EasyMock.expect(store.slice(offset, maxSize)).andReturn(set);

    final GetCommand request =
        new GetCommand(
            this.topic, this.metaConfig.getSlaveGroup(), partition, offset, maxSize, opaque);
    // this.conn.response(new BooleanCommand(request.getOpaque(),
    // HttpStatus.Forbidden, "partition["
    // + this.metaConfig.getBrokerId() + "-" + request.getPartition() +
    // "] have been closed"));
    set.write(request, this.sessionContext);
    EasyMock.expectLastCall();

    this.mocksControl.replay();

    this.getProcessor.handleRequest(request, this.conn);
    this.mocksControl.verify();
    assertEquals(0, this.statsManager.getCmdGetMiss());
    assertEquals(1, this.statsManager.getCmdGets());
    assertEquals(0, this.statsManager.getCmdOffsets());
  }
  @Test
  public void testHandleRequestNormal() throws Exception {
    final int partition = 1;
    final int opaque = 0;
    final int maxSize = 1024;
    final long offset = 10;
    final MessageStore store = this.mocksControl.createMock(MessageStore.class);
    EasyMock.expect(this.storeManager.getMessageStore(this.topic, partition)).andReturn(store);
    final MessageSet set = this.mocksControl.createMock(MessageSet.class);
    EasyMock.expect(store.slice(offset, maxSize)).andReturn(set);
    final GetCommand request =
        new GetCommand(this.topic, this.group, partition, offset, maxSize, opaque);
    set.write(request, this.sessionContext);
    EasyMock.expectLastCall();
    this.mocksControl.replay();

    this.getProcessor.handleRequest(request, this.conn);
    this.mocksControl.verify();
    assertEquals(0, this.statsManager.getCmdGetMiss());
    assertEquals(1, this.statsManager.getCmdGets());
    assertEquals(0, this.statsManager.getCmdOffsets());
  }
  @Test
  public void testHandleRequestArrayIndexOutOfBounds() throws Exception {
    final int partition = 1;
    final int opaque = 0;
    final int maxSize = 1024;
    final long offset = 10;
    final long newOffset = 512;
    final MessageStore store = this.mocksControl.createMock(MessageStore.class);
    EasyMock.expect(this.storeManager.getMessageStore(this.topic, partition)).andReturn(store);
    EasyMock.expect(store.slice(offset, maxSize)).andThrow(new ArrayIndexOutOfBoundsException());
    EasyMock.expect(store.getNearestOffset(offset)).andReturn(newOffset);
    this.conn.response(new BooleanCommand(opaque, HttpStatus.Moved, String.valueOf(newOffset)));
    EasyMock.expectLastCall();
    this.mocksControl.replay();

    final GetCommand request =
        new GetCommand(this.topic, this.group, partition, offset, maxSize, opaque);
    this.getProcessor.handleRequest(request, this.conn);
    this.mocksControl.verify();
    assertEquals(1, this.statsManager.getCmdGetMiss());
    assertEquals(1, this.statsManager.getCmdGets());
    assertEquals(1, this.statsManager.getCmdOffsets());
  }
  @Test
  public void testHandleRequestGreatThanMaxOffsetSliceNull() throws Exception {
    final int partition = 1;
    final int opaque = 0;
    final int maxSize = 1024;
    final long offset = 10;
    final MessageStore store = this.mocksControl.createMock(MessageStore.class);
    EasyMock.expect(this.storeManager.getMessageStore(this.topic, partition)).andReturn(store);
    EasyMock.expect(store.slice(offset, maxSize)).andReturn(null);
    this.conn.response(
        new BooleanCommand(
            opaque, HttpStatus.NotFound, "Could not find message at position " + offset));
    EasyMock.expect(store.getMaxOffset()).andReturn(offset - 1);
    EasyMock.expectLastCall();
    this.mocksControl.replay();

    final GetCommand request =
        new GetCommand(this.topic, this.group, partition, offset, maxSize, opaque);
    this.getProcessor.handleRequest(request, this.conn);
    this.mocksControl.verify();
    assertEquals(1, this.statsManager.getCmdGetMiss());
    assertEquals(1, this.statsManager.getCmdGets());
    assertEquals(0, this.statsManager.getCmdOffsets());
  }
  @Test
  public void testHandleRequestMaxOffset() throws Exception {
    // 从实际最大的offset开始订阅
    final int partition = 1;
    final int opaque = 0;
    final int maxSize = 1024;
    final long offset = Long.MAX_VALUE;
    final long realMaxOffset = 100;
    final MessageStore store = this.mocksControl.createMock(MessageStore.class);
    EasyMock.expect(this.storeManager.getMessageStore(this.topic, partition)).andReturn(store);
    EasyMock.expect(store.slice(offset, maxSize)).andReturn(null);
    this.conn.response(new BooleanCommand(opaque, HttpStatus.Moved, String.valueOf(realMaxOffset)));
    EasyMock.expect(store.getMaxOffset()).andReturn(realMaxOffset);
    EasyMock.expectLastCall();
    this.mocksControl.replay();

    final GetCommand request =
        new GetCommand(this.topic, this.group, partition, offset, maxSize, opaque);
    this.getProcessor.handleRequest(request, this.conn);
    this.mocksControl.verify();
    assertEquals(1, this.statsManager.getCmdGetMiss());
    assertEquals(1, this.statsManager.getCmdGets());
    assertEquals(1, this.statsManager.getCmdOffsets());
  }
  @Override
  public void commit(final TransactionId txid, final boolean wasPrepared) throws IOException {
    final Tx tx;
    if (wasPrepared) {
      synchronized (this.preparedTransactions) {
        tx = this.preparedTransactions.remove(txid);
      }
    } else {
      synchronized (this.inflightTransactions) {
        tx = this.inflightTransactions.remove(txid);
      }
    }
    if (tx == null) {
      return;
    }
    // Append messages
    final Map<MessageStore, List<Long>> msgIds = tx.getMsgIds();
    final Map<MessageStore, List<PutCommand>> putCommands = tx.getPutCommands();
    final Map<String, AddMsgLocation> locations =
        new LinkedHashMap<String, JournalTransactionStore.AddMsgLocation>();

    final int count = msgIds.size();

    for (final Map.Entry<MessageStore, List<Long>> entry : msgIds.entrySet()) {
      final MessageStore msgStore = entry.getKey();
      final List<Long> ids = entry.getValue();
      final List<PutCommand> cmds = putCommands.get(msgStore);
      // Append message
      msgStore.append(
          ids,
          cmds,
          new AppendCallback() {

            @Override
            public void appendComplete(final Location location) {
              // Calculate checksum
              final int checkSum =
                  CheckSum.crc32(MessageUtils.makeMessageBuffer(ids, cmds).array());
              final String description = msgStore.getDescription();
              // Store append location
              synchronized (locations) {
                locations.put(
                    description,
                    new AddMsgLocation(
                        location.getOffset(), location.getLength(), checkSum, description));
                // 处理完成
                if (locations.size() == count) {
                  // 将位置信息序列化,并作为tx
                  // command的附加数据存储,这部分数据的长度是固定的,因此可以在replay的时候更改
                  final ByteBuffer localtionBytes = AddMsgLocationUtils.encodeLocation(locations);

                  TxCommand msg = null;
                  // Log transaction
                  final int attachmentLen = localtionBytes.remaining();
                  if (txid.isXATransaction()) {
                    final TransactionOperation to =
                        TransactionOperation.newBuilder() //
                            .setType(TransactionType.XA_COMMIT) //
                            .setTransactionId(txid.getTransactionKey()) //
                            .setWasPrepared(wasPrepared) //
                            .setDataLength(attachmentLen) // 设置附加数据长度
                            .build();
                    msg =
                        TxCommand.newBuilder()
                            .setCmdType(TxCommandType.TX_OP)
                            .setCmdContent(to.toByteString())
                            .build();
                  } else {
                    final TransactionOperation to =
                        TransactionOperation.newBuilder() //
                            .setType(TransactionType.LOCAL_COMMIT) //
                            .setTransactionId(txid.getTransactionKey()) //
                            .setWasPrepared(wasPrepared) //
                            .setDataLength(attachmentLen) // 设置附加数据长度
                            .build();
                    msg =
                        TxCommand.newBuilder()
                            .setCmdType(TxCommandType.TX_OP)
                            .setCmdContent(to.toByteString())
                            .build();
                  }
                  // 记录commit日志,并附加位置信息
                  try {
                    JournalTransactionStore.this.journalStore.write(
                        msg, localtionBytes, tx.location, true);
                  } catch (final IOException e) {
                    throw new RuntimeException("Write tx log failed", e);
                  }
                }
              }
            }
          });
    }
  }