/** * 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); } } }