/**
* JBoss, Home of Professional Open Source
* Copyright Red Hat, Inc., and individual contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jboss.aerogear.unifiedpush.message.serviceHolder;
import com.notnoop.apns.ApnsService;
import org.jboss.aerogear.unifiedpush.api.VariantType;
import org.jboss.aerogear.unifiedpush.message.event.VariantCompletedEvent;
import org.jboss.aerogear.unifiedpush.message.holder.MessageHolderWithVariants;
import org.jboss.aerogear.unifiedpush.service.ClientInstallationService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Resource;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.event.Observes;
import javax.inject.Inject;
import javax.jms.Queue;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
/**
* This cache creates and holds queue of used {@link ApnsService} with upper-bound limit of 10 created instances
* per given push message and variant. This allows for 10 concurrent connections to the APNs push network.
*
* Cache allows to return freed up services to the queue or free a slot for creating new services up to the limit.
*
* This cache also listens for {@link VariantCompletedEvent} event and stops all instantiated {@link ApnsService}s and frees the cache.
*
* @see AbstractServiceHolder#dequeueOrCreateNewService(String, String, ServiceConstructor)
* @see AbstractServiceHolder#queueFreedUpService(String, String, Object, ServiceDestroyer)
* @see AbstractServiceHolder#freeUpSlot(String, String)
*/
@ApplicationScoped
public class ApnsServiceHolder extends AbstractServiceHolder<ApnsService> {
private final Logger logger = LoggerFactory.getLogger(ApnsServiceHolder.class);
public static final int INSTANCE_LIMIT = 10;
public static final long INSTANCE_ACQUIRING_TIMEOUT = 7500;
public static final long DISPOSING_DELAY = 5000;
@Inject
private ClientInstallationService clientInstallationService;
@Resource(mappedName = "java:/queue/FreeServiceSlotQueue")
private Queue freeServiceCounterQueue;
public ApnsServiceHolder() {
super(INSTANCE_LIMIT, INSTANCE_ACQUIRING_TIMEOUT, DISPOSING_DELAY);
}
@Override
public Queue getFreeServiceSlotQueue() {
return freeServiceCounterQueue;
}
public void initializeHolderForVariants(@Observes MessageHolderWithVariants msg) throws ExecutionException {
if (msg.getVariantType() == VariantType.IOS) {
msg.getVariants().forEach(variant -> initialize(msg.getPushMessageInformation().getId(), variant.getVariantID()));
}
}
/**
* Listen to {@link VariantCompletedEvent} to free up APNs bound resources after the (iOS) variant has
* been completely processed for push notification delivery.
*/
public void freeUpAvailableServices(@Observes VariantCompletedEvent variantCompleted) throws ExecutionException {
final String pushMessageInformationId = variantCompleted.getPushMessageInformationId();
String variantID = variantCompleted.getVariantID();
ApnsService service;
while ((service = this.dequeue(pushMessageInformationId, variantID)) != null) {
try {
try {
// 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:
if (! transformedTokens.isEmpty()) {
logger.info("Deleting '{}' inactive iOS installations", inactiveTokens.size());
clientInstallationService.removeInstallationsForVariantByDeviceTokens(variantID, transformedTokens);
}
} catch (Exception e) {
logger.error("Unable to detect and delete inactive devices", e);
}
// kill the service
service.stop();
} catch (Exception e) {
logger.error("Unable to stop ApnsService", e);
} finally {
// we will free up a slot anyway
this.freeUpSlot(pushMessageInformationId, variantID);
}
}
this.destroy(pushMessageInformationId, variantID);
}
/**
* The Java-APNs lib returns the tokens in UPPERCASE format, however, the iOS Devices submit the token in
* LOWER CASE format. This helper method performs a transformation
*/
private static Set<String> lowerCaseAllTokens(Set<String> inactiveTokens) {
return inactiveTokens.stream().map(String::toLowerCase).collect(Collectors.toSet());
}
}