package com.cyou.cpush.apns.core; import io.netty.bootstrap.SSLBootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.EventLoop; import io.netty.channel.EventLoopGroup; import io.netty.handler.ssl.SslHandler; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.FutureListener; import io.netty.util.concurrent.GenericProgressiveFutureListener; import io.netty.util.concurrent.ProgressivePromise; import io.netty.util.internal.logging.InternalLogger; import io.netty.util.internal.logging.InternalLoggerFactory; import java.util.ArrayList; import java.util.List; import com.cyou.cpush.apns.ApnsConnection; import com.cyou.cpush.apns.conf.Credentials; import com.cyou.cpush.apns.handler.ApnsInboundHandler; import com.cyou.cpush.apns.handler.ApnsOutboundHandler; import com.cyou.cpush.apns.notification.DefaultNotification; import com.cyou.cpush.apns.notification.Device; import com.cyou.cpush.apns.notification.ErrorPacket; import com.cyou.cpush.apns.notification.Notification; import com.cyou.cpush.apns.notification.Payload; public final class DefaultApnsConnection implements ApnsConnection { /** * configuration, such as p12 certification and password */ private Credentials credential; private final SSLBootstrap bootstrap; private PushBuffer buffer; public DefaultApnsConnection(Credentials credential) { this.credential = credential; this.bootstrap = BootstrapFactory.create(credential); buffer = PushBuffer.newInstance(this); } @Override public Future<Void> push(Notification notification) { NotificationPromise promise = buffer.push(notification); return promise; } @Override public Future<Iterable<ErrorPacket>> push(Notification... message) { ApnsShortConnectionThread thread = new ApnsShortConnectionThread(bootstrap, message); return thread.send(); } EventLoopGroup group() { return bootstrap.group(); } Credentials credential() { return credential; } } class ApnsShortConnectionThread { private static final InternalLogger log = InternalLoggerFactory .getInstance(DefaultApnsConnection.class); private static final int MAX_HANDSHAKE_RETRY = 3; private final SSLBootstrap bootstrap; /** * all the notification which will be sent */ private final SortedNotification[] notifications; /** * all the sending failure notification received from apns server */ private final List<ErrorPacket> failedNotifications; /** * the next index of notification which will be sent */ private int nextIndex = 0; /** * the index at which the sending stopped */ private int stopIndex = -1; /** * times of handshake in this connection session */ private int handshakeTimes = 0; public static final Notification TAIL_INVALID_NOTIFICATION = new DefaultNotification( new Device(""), new Payload("")); ApnsShortConnectionThread(SSLBootstrap bootstrap, Notification... message) { this.bootstrap = bootstrap; this.eventLoop = bootstrap.group().next(); // this.conf = conf; if (message != null) { this.notifications = new SortedNotification[message.length + 1]; for (int i = 0; i < message.length; i++) { this.notifications[i] = new SortedNotification(message[i], i); } this.notifications[message.length] = new SortedNotification( TAIL_INVALID_NOTIFICATION, message.length); } else { this.notifications = new SortedNotification[] {}; } failedNotifications = new ArrayList<ErrorPacket>(); } private final EventLoop eventLoop; public Future<Iterable<ErrorPacket>> send() { final NotificationsPromise promise = new NotificationsPromise(eventLoop); /* listen on the progress of the pushing, detect disconnected and reconnect */ promise .addListener(new GenericProgressiveFutureListener<ProgressivePromise<Iterable<ErrorPacket>>>() { @Override public void operationComplete( ProgressivePromise<Iterable<ErrorPacket>> future) throws Exception { // NOOP } @Override public void operationProgressed( ProgressivePromise<Iterable<ErrorPacket>> future, long progress, long total) throws Exception { ApnsShortConnectionThread connection = ApnsShortConnectionThread.this; byte code = (byte) (progress & 0xffffffff); int identifier = (int) (progress >> 32); /* relocated the index if a notification can not be delivered */ connection.relocatedIndex(identifier); if (connection.isDone()) { /* if the index is the tail invalid notification, set Success */ if (log.isDebugEnabled()) { log.debug("a pushing progress has done."); } future.setSuccess(connection.getFailedNotifications()); } else { /* add the failed notification to the FailureNotification List */ if (log.isDebugEnabled()) { log.debug(String .format( "a notification could not be delivered, identifier: %d, error-response code: %d", identifier, code)); } connection.addFailureNotification(identifier, code); /* * reconnect to APNS server in order to send the rest of * notification */ ApnsShortConnectionThread.connect(connection, promise); } } }); connect(this, promise); return promise; } private static void connect(final ApnsShortConnectionThread connection, final NotificationsPromise promise) { /* create a new channel */ Future<Channel> future = connect0(connection, promise); /* listen on the connected and handshake success event */ future.addListener(new FutureListener<Channel>() { @Override public void operationComplete(Future<Channel> future) throws Exception { if (future.isSuccess()) { if (log.isDebugEnabled()) { log.debug("a new channel has been registered and connected to the APNS server"); } /* write data to the channel after handshake success */ send(connection, future.get()); } else { connection.handshakeTimes++; if (connection.handshakeTimes <= MAX_HANDSHAKE_RETRY) { log.warn(String .format( "Channel connected to the APNS server failed, will retry %s time.", connection.handshakeTimes)); connect(connection, promise); } else { log.warn(String .format( "Channel connected to the APNS server failed. Abandon connection after %d times connect", connection.handshakeTimes)); promise.setFailure(future.cause()); } } } }); } private static Future<Channel> connect0( final ApnsShortConnectionThread connection, final NotificationsPromise promise) { /* create a new channel and initial several handlers */ ChannelFuture connFuture = connection.bootstrap.connect( connection.eventLoop, new ApnsOutboundHandler(), new ApnsInboundHandler(promise)); final Channel channel = connFuture.channel(); return channel.pipeline().get(SslHandler.class).handshakeFuture(); } private static void send(final ApnsShortConnectionThread connection, final Channel channel) { for (int i = connection.getNextIndex(); i < connection.getNotifications().length; i++) { channel.write(connection.notifications[i]); } } class SortedNotification implements Notification { private Notification principal; private int identifier; SortedNotification(Notification notification, int identifier) { this.principal = notification; this.identifier = identifier; } @Override public Device getDevice() { return principal.getDevice(); } @Override public Payload getPayload() { return principal.getPayload(); } @Override public int getIdentifier() { return identifier; } Notification principal() { return principal; } } protected void addFailureNotification(int identifier, byte code) { if (identifier >= 0 && identifier < notifications.length) { ErrorPacket ep = new ErrorPacket(code, identifier); ep.setNotification(notifications[identifier].principal()); failedNotifications.add(ep); } } protected void relocatedIndex(int stop) { this.stopIndex = stop; this.nextIndex = this.stopIndex + 1; } protected Notification[] getNotifications() { return notifications; } protected int getNextIndex() { return nextIndex; } protected int getStopIndex() { return stopIndex; } public boolean isDone() { return this.getStopIndex() >= this.getNotifications().length - 1 || this.getNextIndex() >= this.getNotifications().length; } public List<ErrorPacket> getFailedNotifications() { return failedNotifications; } public static void main(String args[]) { } }