
cn.ipokerface.aps.PushClient Maven / Gradle / Ivy
Show all versions of apple-pns-java Show documentation
package cn.ipokerface.aps;
import cn.ipokerface.aps.auth.ApnsSignKey;
import cn.ipokerface.aps.channel.ChannelFactory;
import cn.ipokerface.aps.channel.ChannelPool;
import cn.ipokerface.aps.notification.Notification;
import cn.ipokerface.aps.notification.NotificationFuture;
import cn.ipokerface.aps.response.NotificationResponse;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.handler.codec.http2.Http2FrameLogger;
import io.netty.handler.ssl.SslContext;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.InetSocketAddress;
import java.time.Duration;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Created by PokerFace
* Create Date 2020-08-23.
* Email: [email protected]
* Version 1.0.0
*
* Description:
*/
public class PushClient {
private Logger logger = LoggerFactory.getLogger(PushClient.class);
private final AtomicBoolean closed = new AtomicBoolean(false);
private EventLoopGroup eventLoopGroup;
private ChannelPool channelPool;
private PushClientMetricsListener listener;
/**
* constructor...
*
* @param env
* @param topic
* @param sslContext
* @param signKey
* @param connectionTimeout
* @param idleInterval
* @param listener
* @param frameLogger
*/
protected PushClient(Environment env, String topic,
SslContext sslContext,
ApnsSignKey signKey,
Duration tokenExpiration,
Duration connectionTimeout,
Duration idleInterval,
PushClientMetricsListener listener,
Http2FrameLogger frameLogger,
int poolCapacity
) {
if (env == null) throw new IllegalArgumentException("Envrionment is not set.");
if (StringUtils.isEmpty(topic)) throw new IllegalArgumentException("Default topic is not set...");
this.eventLoopGroup = new NioEventLoopGroup(1);
this.listener = listener != null ? listener : new PushClientMetricsListener.ClientMetricsListenerAdapter();
ChannelFactory channelFactory = new ChannelFactory(InetSocketAddress.createUnresolved(env.getHost(), env.getPort()),
this.eventLoopGroup,
sslContext,signKey,tokenExpiration,
connectionTimeout,idleInterval,frameLogger, topic);
this.channelPool = new ChannelPool(this, channelFactory, poolCapacity, this.eventLoopGroup.next(), this.listener);
}
/**
*
Sends a push notification to the APNs gateway.
*
* This method returns a {@code Future} that indicates whether the notification was accepted or rejected by the
* gateway. If the notification was accepted, it may be delivered to its destination device at some time in the
* future, but final delivery is not guaranteed. Rejections should be considered permanent failures, and callers
* should not attempt to re-send the notification.
*
* The returned {@code Future} may fail with an exception if the notification could not be sent. Failures to
* send a notification to the gateway—i.e. those that fail with exceptions—should generally be considered
* non-permanent, and callers should attempt to re-send the notification when the underlying problem has been
* resolved.
*
* @param notification the notification to send to the APNs gateway
*
* @param the type of notification to be sent
*
* @return a {@code Future} that will complete when the notification has been either accepted or rejected by the
* APNs gateway
*
* @since 0.8
*/
public NotificationFuture> sendNotification(final T notification) {
final NotificationFuture> responseFuture =
new NotificationFuture<>(notification);
if (!this.closed.get()) {
this.channelPool.acquire().addListener((GenericFutureListener>) acquireFuture -> {
if (acquireFuture.isSuccess()) {
final Channel channel = acquireFuture.getNow();
channel.writeAndFlush(responseFuture).addListener((GenericFutureListener) future -> {
if (future.isSuccess()) {
listener.handleNotificationSend(PushClient.this, notification.getApnsId());
}
});
channelPool.release(channel);
} else {
responseFuture.completeExceptionally(acquireFuture.cause());
}
});
responseFuture.whenComplete((response, cause) -> {
if (response != null) {
if (response.isSuccess()) {
listener.handleNotificationAccepted(PushClient.this, notification.getApnsId());
} else {
listener.handleNotificationRejected(PushClient.this, notification.getApnsId());
}
} else {
listener.handleWriteFailure(PushClient.this, notification.getApnsId());
}
});
} else {
responseFuture.completeExceptionally(new IllegalStateException("Client has been closed and can no longer send push notifications."));
}
return responseFuture;
}
/**
* Gracefully shuts down the client, closing all connections and releasing all persistent resources. The
* disconnection process will wait until notifications that have been sent to the APNs server have been either
* accepted or rejected. Note that some notifications passed to
* {@link PushClient#sendNotification(Notification)} may still be enqueued and not yet sent by the time the
* shutdown process begins; the {@code Futures} associated with those notifications will fail.
*
* The returned {@code Future} will be marked as complete when all connections in this client's pool have closed
* completely and (if no {@code EventLoopGroup} was provided at construction time) the client's event loop group has
* shut down. If the client has already shut down, the returned {@code Future} will be marked as complete
* immediately.
*
* Clients may not be reused once they have been closed.
*
* @return a {@code Future} that will be marked as complete when the client has finished shutting down
*
* @since 0.11
*/
public CompletableFuture close() {
logger.info("Shutting down.");
final CompletableFuture closeFuture;
if (this.closed.compareAndSet(false, true)) {
closeFuture = new CompletableFuture<>();
this.channelPool.close().addListener((GenericFutureListener>) closePoolFuture -> {
eventLoopGroup.shutdownGracefully().addListener(future -> closeFuture.complete(null));
});
} else {
closeFuture = CompletableFuture.completedFuture(null);
}
return closeFuture;
}
/**
* 环境
*/
public enum Environment {
Sandbox ("api.sandbox.push.apple.com", 443, true),
Production ("api.push.apple.com", 443, true)
;
private String host;
private int port;
private boolean ssl;
private Environment(String url, int port,boolean ssl) {
this.host = url;
this.port = port;
this.ssl = ssl;
}
public String getHost() {
return host;
}
public int getPort() {
return port;
}
public boolean isSsl() {
return ssl;
}}
}