@DELETE
  @Path("{token}")
  public Response unregisterInstallations(
      @PathParam("token") String token, @Context HttpServletRequest request) {

    // find the matching variation:
    final Variant variant = loadVariantWhenAuthorized(request);
    if (variant == null) {
      return appendAllowOriginHeader(
          Response.status(Status.UNAUTHORIZED)
              .header("WWW-Authenticate", "Basic realm=\"AeroGear UnifiedPush Server\"")
              .entity("Unauthorized Request"),
          request);
    }

    // look up all installations (with same token) for the given variant:
    Installation installation =
        clientInstallationService.findInstallationForVariantByDeviceToken(
            variant.getVariantID(), token);

    if (installation == null) {
      return appendAllowOriginHeader(Response.status(Status.NOT_FOUND), request);
    } else {
      logger.log(Level.INFO, "Deleting metadata Installation");
      // remove
      clientInstallationService.removeInstallation(installation);
    }

    return appendAllowOriginHeader(Response.noContent(), request);
  }
 private void putDeviceCountIntoResponseHeaders(PushApplication app, ResponseBuilder response) {
   long appCount = 0;
   for (Variant variant : app.getVariants()) {
     long variantCount = installationDao.getNumberOfDevicesForVariantID(variant.getVariantID());
     appCount += variantCount;
     response.header("deviceCount_variant_" + variant.getVariantID(), variantCount);
   }
   response.header("deviceCount_app_" + app.getPushApplicationID(), appCount);
 }
 private void putActivityIntoResponseHeaders(PushApplication app, ResponseBuilder response) {
   response.header(
       "activity_app_" + app.getPushApplicationID(),
       metricsService.countMessagesForPushApplication(app.getPushApplicationID()));
   for (Variant variant : app.getVariants()) {
     response.header(
         "activity_variant_" + variant.getVariantID(),
         metricsService.countMessagesForVariant(variant.getVariantID()));
   }
 }
  @POST
  @Consumes(MediaType.APPLICATION_JSON)
  public Response registerInstallation(Installation entity, @Context HttpServletRequest request) {

    // find the matching variation:
    final Variant variant = loadVariantWhenAuthorized(request);
    if (variant == null) {
      return appendAllowOriginHeader(
          Response.status(Status.UNAUTHORIZED)
              .header("WWW-Authenticate", "Basic realm=\"AeroGear UnifiedPush Server\"")
              .entity("Unauthorized Request"),
          request);
    }

    // Poor validation: We require the Token! And the 'simplePushEndpoint' for SimplePush clients!
    if (entity.getDeviceToken() == null
        || (variant.getType() == VariantType.SIMPLE_PUSH
            && entity.getSimplePushEndpoint() == null)) {
      return appendAllowOriginHeader(Response.status(Status.BAD_REQUEST), request);
    }

    // look up all installations (with same token) for the given variant:
    Installation installation =
        clientInstallationService.findInstallationForVariantByDeviceToken(
            variant.getVariantID(), entity.getDeviceToken());

    // Needed for the Admin UI Only. Help for setting up Routes
    entity.setPlatform(variant.getType().getTypeName());

    // The 'mobile application' on the device/client was launched.
    // If the installation is already in the DB, let's update the metadata,
    // otherwise we register a new installation:
    logger.log(Level.FINEST, "Mobile Application on device was launched");

    // new device/client ?
    if (installation == null) {
      logger.log(Level.FINEST, "Performing new device/client registration");
      // store the installation:
      clientInstallationService.addInstallation(variant.getType(), entity);
      // add installation to the matching variant
      genericVariantService.addInstallation(variant, entity);
    } else {
      // We only update the metadata, if the device is enabled:
      if (installation.isEnabled()) {
        logger.log(Level.FINEST, "Updating received metadata for an 'enabled' installation");
        // update the entity:
        clientInstallationService.updateInstallation(installation, entity);
      }
    }

    return appendAllowOriginHeader(Response.ok(entity), request);
  }
  /** returns application if the masterSecret is valid for the request PushApplicationEntity */
  private Variant loadVariantWhenAuthorized(HttpServletRequest request) {
    // extract the pushApplicationID and its secret from the HTTP Basic
    // header:
    String[] credentials = HttpBasicHelper.extractUsernameAndPasswordFromBasicHeader(request);
    String variantID = credentials[0];
    String secret = credentials[1];

    final Variant variant = genericVariantService.findByVariantID(variantID);
    if (variant != null && variant.getSecret().equals(secret)) {
      return variant;
    }

    // unauthorized...
    return null;
  }
  /**
   * Receives request for processing a {@link UnifiedPushMessage} and loads tokens for devices that
   * match requested parameters from database.
   *
   * <p>Device tokens are loaded in a stream and split to batches of configured size (see {@link
   * SenderConfiguration#batchSize()}). Once the pre-configured number of batches (see {@link
   * SenderConfiguration#batchesToLoad()}) is reached, this method resends message to the same queue
   * it took the request from, so that the transaction it worked in is split and further processing
   * may continue in next transaction.
   *
   * <p>Additionally it fires {@link BatchLoadedEvent} as CDI event (that is translated to JMS
   * event) that helps {@link MetricsCollector} to track how many batches were loaded. When all
   * batches were loaded for the given variant, it fires {@link AllBatchesLoadedEvent}.
   *
   * @param msg holder object containing the payload and info about the effected variants
   */
  public void loadAndQueueTokenBatch(@Observes @Dequeue MessageHolderWithVariants msg)
      throws IllegalStateException {
    final UnifiedPushMessage message = msg.getUnifiedPushMessage();
    final VariantType variantType = msg.getVariantType();
    final Collection<Variant> variants = msg.getVariants();
    final String lastTokenFromPreviousBatch = msg.getLastTokenFromPreviousBatch();
    final SenderConfiguration configuration =
        senderConfiguration.select(new SenderTypeLiteral(variantType)).get();
    final PushMessageInformation pushMessageInformation = msg.getPushMessageInformation();
    int serialId = msg.getLastSerialId();

    logger.debug("Received message from queue: " + message.getMessage().getAlert());

    final Criteria criteria = message.getCriteria();
    final List<String> categories = criteria.getCategories();
    final List<String> aliases = criteria.getAliases();
    final List<String> deviceTypes = criteria.getDeviceTypes();

    logger.info(
        String.format(
            "Preparing message delivery and loading tokens for the %s 3rd-party Push Network (for %d variants)",
            variantType, variants.size()));

    for (Variant variant : variants) {

      try {

        ResultsStream<String> tokenStream;
        final Set<String> topics = new TreeSet<>();
        final boolean isAndroid = variantType.equals(VariantType.ANDROID);

        // the entire batch size
        int batchesToLoad = configuration.batchesToLoad();

        // Some checks for GCM, because of GCM-3 topics
        boolean gcmTopicRequest = (isAndroid && TokenLoaderUtils.isGCMTopicRequest(criteria));
        if (gcmTopicRequest) {

          // If we are able to do push for GCM topics...

          // 1)
          // find all topics, BUT only on the very first round of batches
          // otherwise after 10 (or what ever the max. is) another request would be sent to that
          // topic
          if (serialId == 0) {
            topics.addAll(TokenLoaderUtils.extractGCMTopics(criteria, variant.getVariantID()));

            // topics are handled as a first extra batch,
            // therefore we have to adjust the number by adding this extra batch
            batchesToLoad = batchesToLoad + 1;
          }

          // 2) always load the legacy tokens, for all number of batch iterations
          tokenStream =
              clientInstallationService
                  .findAllOldGoogleCloudMessagingDeviceTokenForVariantIDByCriteria(
                      variant.getVariantID(),
                      categories,
                      aliases,
                      deviceTypes,
                      configuration.tokensToLoad(),
                      lastTokenFromPreviousBatch)
                  .fetchSize(configuration.batchSize())
                  .executeQuery();
        } else {
          tokenStream =
              clientInstallationService
                  .findAllDeviceTokenForVariantIDByCriteria(
                      variant.getVariantID(),
                      categories,
                      aliases,
                      deviceTypes,
                      configuration.tokensToLoad(),
                      lastTokenFromPreviousBatch)
                  .fetchSize(configuration.batchSize())
                  .executeQuery();
        }

        String lastTokenInBatch = null;
        int tokensLoaded = 0;
        for (int batchNumber = 0; batchNumber < batchesToLoad; batchNumber++) {

          // increasing the serial ID,
          // to make sure it's properly read from all block
          ++serialId;

          final Set<String> tokens = new TreeSet<>();

          // On Android, the first batch is for GCM3 topics
          // legacy tokens are submitted in the batch #2 and later
          if (isAndroid && batchNumber == 0 && !topics.isEmpty()) {
            tokens.addAll(topics);
          } else {
            for (int i = 0; i < configuration.batchSize() && tokenStream.next(); i++) {
              lastTokenInBatch = tokenStream.get();
              tokens.add(lastTokenInBatch);
              tokensLoaded += 1;
            }
          }

          if (tokens.size() > 0) {
            if (tryToDispatchTokens(
                new MessageHolderWithTokens(
                    msg.getPushMessageInformation(), message, variant, tokens, serialId))) {
              logger.info(
                  String.format(
                      "Loaded batch #%s, containing %d tokens, for %s variant (%s)",
                      serialId,
                      tokens.size(),
                      variant.getType().getTypeName(),
                      variant.getVariantID()));
            } else {
              logger.debug(
                  String.format(
                      "Failing token loading transaction for batch token #%s for %s variant (%s), since queue is full, will retry...",
                      serialId, variant.getType().getTypeName(), variant.getVariantID()));
              context.setRollbackOnly();
              return;
            }

            // using combined key of variant and PMI (AGPUSH-1585):
            batchLoaded.fire(
                new BatchLoadedEvent(
                    variant.getVariantID() + ":" + msg.getPushMessageInformation().getId()));
            if (serialId == MessageHolderWithVariants.INITIAL_SERIAL_ID) {
              triggerVariantMetricCollection.fire(
                  new TriggerVariantMetricCollectionEvent(
                      msg.getPushMessageInformation(), variant));
            }
          } else {
            logger.debug(
                String.format(
                    "Ending batch processing: No more tokens for batch #%s available", serialId));
            break;
          }
        }

        // should we trigger next transaction?
        if (tokensLoaded >= configuration.tokensToLoad()) {
          logger.debug(
              String.format(
                  "Ending token loading transaction for %s variant (%s)",
                  variant.getType().getTypeName(), variant.getVariantID()));
          nextBatchEvent.fire(
              new MessageHolderWithVariants(
                  msg.getPushMessageInformation(),
                  message,
                  msg.getVariantType(),
                  variants,
                  serialId,
                  lastTokenInBatch));
        } else {
          logger.debug(
              String.format(
                  "All batches for %s variant were loaded (%s)",
                  variant.getType().getTypeName(), pushMessageInformation.getId()));

          // using combined key of variant and PMI (AGPUSH-1585):
          allBatchesLoaded.fire(
              new AllBatchesLoadedEvent(
                  variant.getVariantID() + ":" + msg.getPushMessageInformation().getId()));
          triggerVariantMetricCollection.fire(
              new TriggerVariantMetricCollectionEvent(pushMessageInformation, variant));

          if (tokensLoaded == 0 && lastTokenFromPreviousBatch == null) {
            // no tokens were loaded at all!
            if (gcmTopicRequest) {
              logger.debug("No legacy(non-InstanceID) tokens found. Just pure GCM topic requests");
            } else {
              logger.warn("Check your push query: Not a single token was loaded from the DB!");
            }

            VariantMetricInformation variantMetricInformation = new VariantMetricInformation();
            variantMetricInformation.setPushMessageInformation(msg.getPushMessageInformation());
            variantMetricInformation.setVariantID(variant.getVariantID());
            variantMetricInformation.setDeliveryStatus(Boolean.TRUE);
            dispatchVariantMetricEvent.fire(variantMetricInformation);
          }
        }
      } catch (ResultStreamException e) {
        logger.error("Failed to load batch of tokens", e);
      }
    }
  }