/** ソケットの再接続 */
  private synchronized void reconnectNotificationSocket() {
    apnsInputMonitoringThread = null;

    // 既存の接続をクローズ
    ApnsUtil.close(apnsNotificationSocket);

    // パケット送信量をリセット
    packetSize = 0;

    // Socket取得
    apnsNotificationSocket = null;
    apnsNotificationSocket = ApnsUtil.createAPNSGatewaySocket(isProduction, factory, socksProxy);

    // APNsからの入力待ちスレッドを開始
    try {
      apnsInputMonitoringThread =
          new ApnsInputMonitoringThread(apnsNotificationSocket.getInputStream());
      apnsInputMonitoringThread.start();
    } catch (IOException e) {
      logger.info("APNs入力ストリーム取得エラー", e);
    }
    logger.debug("APNsへのSocket通信を構築または再構築しました。");
  }
 /**
  * 製品フラグ、証明書ファイル、証明書パスワード、SOCKSプロキシ、PUSH通信間隔(ミリ秒)、最大パケットサイズを指定してインスタンスを生成します
  *
  * @param isProduction 製品フラグ
  * @param certFileName 証明書ファイル
  * @param certPassword 証明書パスワード
  * @param socksProxy SOCKSプロキシ
  * @param pushIntervalMs PUSH通信間隔(ミリ秒)
  * @param maxPacket 最大パケットサイズ
  */
 public ApnsNotificationService(
     boolean isProduction,
     String certFileName,
     String certPassword,
     Proxy socksProxy,
     int pushIntervalMs,
     long maxPacket) {
   this.isProduction = isProduction;
   this.certFileName = certFileName;
   this.certPassword = certPassword;
   this.socksProxy = socksProxy;
   this.pushIntervalMs = pushIntervalMs;
   this.maxPacket = maxPacket;
   this.factory = ApnsUtil.getSSLSocketFactory(this.certFileName, this.certPassword);
 }
  /**
   * PUSH通知(複数件送信)
   *
   * @param apnsNotificationList 送信データリスト
   * @return 送信結果
   */
  public synchronized ApnsResult push(List<ApnsNotification> apnsNotificationList) {
    logger.info("PUSH通知 - 開始");
    try {

      // Socketの生成
      reconnectNotificationSocket();

      // 通知リスト読み込み位置
      int pos = 0;
      packetSize = 0;
      while (true) {
        int i;
        for (i = pos; i < apnsNotificationList.size(); i++) {
          pos = i;

          // 通知情報取り出し
          ApnsNotification apnsNotification = apnsNotificationList.get(i);

          // 実行Mapに格納
          executeApnsNotificationMapIdentifier.put(
              apnsNotification.getIdentifier(), apnsNotification);
          executeIndexMapApnsNotification.put(apnsNotification, i);

          logger.trace(
              "deviceToken:{}, payload:{}",
              apnsNotification.getToken(),
              apnsNotification.getPayload());

          // バイナリデータに変換
          byte[] pushData = apnsNotification.getNotificationBytes();
          if (pushData == null) {
            logger.info(
                "PUSHデータを生成できませんでした。token={}, payload={}",
                apnsNotification.getToken(),
                apnsNotification.getPayload());
            // エラーにステータス変更
            apnsNotification.setPushStatus(PushStatus.ERROR);
            // 読み飛ばし
            continue;
          }

          // パケット量制限を超える場合は再接続
          if (this.maxPacket != 0 && this.packetSize > this.maxPacket) {
            reconnectNotificationSocket();
          }

          try {
            // PUSH通知
            OutputStream os = apnsNotificationSocket.getOutputStream();
            os.write(pushData);
            os.flush();

            // パケット量加算
            packetSize += pushData.length;

            // 実行済にステータス設定
            apnsNotification.setPushStatus(PushStatus.DONE);
          } catch (IOException e) {
            // OutputStream書き込みエラー
            logger.debug("APNs通知情報送信エラー。リトライを試行します。", e);
            pos = processPushError(i, apnsNotificationList);
            // forループを抜けてリトライ
            break;
          }

          // 待機
          ApnsUtil.sleep(pushIntervalMs);

          // エラー確認
          if (apnsInputMonitoringThread.hasError()) {
            pos = processPushError(i, apnsNotificationList);
            // forループを抜けてリトライ
            break;
          }
          pos++;
        }

        // 終端まで達していない場合は残りを処理
        if (pos < apnsNotificationList.size()) {
          continue;
        }

        // 1秒間待機(最終通信後のAPNsからの入力を待つ)
        ApnsUtil.sleep(1000);

        // エラー確認
        if (apnsInputMonitoringThread.hasError()) {
          pos = processPushError(i, apnsNotificationList);
          // リトライ
          continue;
        }
        // 全件処理完了
        break;
      }

      return createApnsSendResult(apnsNotificationList, true, null);
    } catch (Exception e) {
      logger.error("PUSH通知処理中にException発生。", e);
      return createApnsSendResult(apnsNotificationList, false, e);
    } finally {
      ApnsUtil.close(apnsNotificationSocket);
      logger.info("PUSH通知 - 終了");
    }
  }
  /**
   * PUSH通知(マルチスレッド送信)
   *
   * <p>分割単位ごとにマルチスレッドでPUSH通知します。<br>
   *
   * @param apnsNotificationList 送信データリスト
   * @param threadCount スレッド数
   * @return 送信結果
   */
  public synchronized ApnsResult push(
      List<ApnsNotification> apnsNotificationList, int threadCount) {
    logger.info("PUSH通知(マルチスレッド送信) - 開始");

    // 送信完了を待機して各スレッドの送信結果を取得
    ApnsResult apnsResult = new ApnsResult();

    try {
      // スレッド数が1の場合はシングルスレッド送信
      if (threadCount == 1) {
        logger.info("スレッド数が1のため、シングルスレッドで実行します。");
        return push(apnsNotificationList);
      }

      // Listをスレッド数に応じて分割
      List<List<ApnsNotification>> apnsNotificationListList =
          ApnsUtil.splitList(apnsNotificationList, threadCount);

      logger.info("スレッド数:{}", apnsNotificationListList.size());

      // 非同期処理用ExecutorService生成
      ExecutorService service = Executors.newFixedThreadPool(apnsNotificationListList.size());
      List<Future<ApnsResult>> futureList = new ArrayList<Future<ApnsResult>>();

      // 非同期PUSH送信
      for (List<ApnsNotification> apnsNotificationThreadList : apnsNotificationListList) {
        ApnsNotificationPushCaller caller =
            new ApnsNotificationPushCaller(apnsNotificationThreadList);
        Future<ApnsResult> future = service.submit(caller);
        futureList.add(future);
      }

      // シャットダウン宣言
      service.shutdown();

      apnsResult.setSuccess(true);
      for (int i = 0; i < futureList.size(); i++) {
        // 待機&結果取得
        ApnsResult r = futureList.get(i).get();

        // 1つでも異常終了があれば異常とする
        if (!r.isSuccess()) {
          apnsResult.setSuccess(r.isSuccess());
        }

        // 複数スレッドでExceptionがあった場合は最後のものを格納
        if (r.getException() != null) {
          apnsResult.setException(r.getException());
        }

        // 未実行リスト
        apnsResult.addAllNone(r.getNoneList());
        // 実行済リスト
        apnsResult.addAllDone(r.getDoneList());
        // エラーリスト
        apnsResult.addAllError(r.getErrorList());
      }
    } catch (Exception e) {
      apnsResult.setException(e);
      apnsResult.setSuccess(false);
    } finally {
      logger.info("PUSH通知(マルチスレッド送信) - 終了");
    }
    return apnsResult;
  }