/**
   * Returns the ApnsService, based on the required profile (production VS sandbox/test). Null is
   * returned if there is no "configuration" for the request stage
   */
  private ApnsService buildApnsService(iOSVariant iOSVariant) {

    // this check should not be needed, but you never know:
    if (iOSVariant.getCertificate() != null && iOSVariant.getPassphrase() != null) {

      final ApnsServiceBuilder builder = APNS.newService();

      // add the certificate:
      ByteArrayInputStream stream = new ByteArrayInputStream(iOSVariant.getCertificate());
      builder.withCert(stream, iOSVariant.getPassphrase());

      try {
        // release the stream
        stream.close();
      } catch (IOException e) {
        logger.log(Level.SEVERE, "Error reading certificate", e);
      }

      // pick the destination:
      if (iOSVariant.isProduction()) {
        builder.withProductionDestination();
      } else {
        builder.withSandboxDestination();
      }

      // create the service
      return builder.build();
    }
    // null if, why ever, there was no cert/passphrase
    return null;
  }
  /**
   * Sends APNs notifications ({@link UnifiedPushMessage}) to all devices, that are represented by
   * the {@link Collection} of tokens for the given {@link iOSVariant}.
   *
   * @param iOSVariant the logical construct, needed to lookup the certificate and the passphrase.
   * @param tokens collection of tokens, representing actual iOS devices
   * @param pushMessage the payload to be submitted
   */
  public void sendPushMessage(
      iOSVariant iOSVariant, Collection<String> tokens, UnifiedPushMessage pushMessage) {
    // no need to send empty list
    if (tokens.isEmpty()) {
      return;
    }

    PayloadBuilder builder =
        APNS.newPayload()
            // adding recognized key values
            .alertBody(pushMessage.getAlert()) // alert dialog, in iOS
            .badge(pushMessage.getBadge()) // little badge icon update;
            .sound(pushMessage.getSound()); // sound to be played by app

    // apply the 'content-available:1' value:
    if (pushMessage.isContentAvailable()) {
      // content-available:1 is (with iOS7) not only used
      // Newsstand, however 'notnoop' names it this way (legacy)...
      builder = builder.forNewsstand();
    }

    builder = builder.customFields(pushMessage.getData()); // adding other (submitted) fields

    final String apnsMessage = builder.build(); // build the JSON payload, for APNs

    ApnsService service = buildApnsService(iOSVariant);

    if (service != null) {
      try {
        logger.fine(String.format("Sending transformed APNs payload: '%s' ", apnsMessage));
        // send:
        service.start();

        Date expireDate = createFutureDateBasedOnTTL(pushMessage.getTimeToLive());
        service.push(tokens, apnsMessage, expireDate);

        // after sending, let's ask for the inactive tokens:
        final Set<String> inactiveTokens = service.getInactiveDevices().keySet();

        // transform the tokens to be all lower-case:
        final Set<String> transformedTokens = lowerCaseAllTokens(inactiveTokens);

        // trigger asynchronous deletion:
        clientInstallationService.removeInstallationsForVariantByDeviceTokens(
            iOSVariant.getVariantID(), transformedTokens);
      } catch (RuntimeException e) {
        logger.log(Level.SEVERE, "Error sending messages to APN server", e);
      } finally {

        // tear down and release resources:
        service.stop();
      }
    } else {
      logger.severe("No certificate was found. Could not send messages to APNs");
    }
  }