/* * Copyright 2009, Mahmood Ali. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * Neither the name of Mahmood Ali. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.notnoop.apns.integration; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; import com.notnoop.apns.APNS; import com.notnoop.apns.ApnsDelegate; import com.notnoop.apns.StartSendingApnsDelegate; import com.notnoop.apns.ApnsNotification; import com.notnoop.apns.ApnsService; import com.notnoop.apns.DeliveryError; import com.notnoop.apns.EnhancedApnsNotification; import com.notnoop.apns.SimpleApnsNotification; import com.notnoop.apns.utils.ApnsServerStub; import com.notnoop.apns.utils.FixedCertificates; import org.junit.*; import static com.notnoop.apns.utils.FixedCertificates.*; @SuppressWarnings("deprecation") public class ApnsConnectionCacheTest { private ApnsServerStub server; private static SimpleApnsNotification msg1 = new SimpleApnsNotification("a87d8878d878a79", "{\"aps\":{}}"); private static SimpleApnsNotification msg2 = new SimpleApnsNotification("a87d8878d878a88", "{\"aps\":{}}"); private static EnhancedApnsNotification eMsg1 = new EnhancedApnsNotification(EnhancedApnsNotification.INCREMENT_ID(), 1, "a87d8878d878a88", "{\"aps\":{}}"); private static EnhancedApnsNotification eMsg2 = new EnhancedApnsNotification(EnhancedApnsNotification.INCREMENT_ID(), 1, "a87d8878d878a88", "{\"aps\":{}}"); private static EnhancedApnsNotification eMsg3 = new EnhancedApnsNotification(EnhancedApnsNotification.INCREMENT_ID(), 1, "a87d8878d878a88", "{\"aps\":{}}"); @Before public void startup() { } @After public void tearDown() { server.stop(); server = null; } /** * Test1 to make sure that after rejected notification * in-flight notifications are re-transmitted * * @throws InterruptedException */ @Test(timeout = 5000) public void handleReTransmissionError5Good1Bad7Good() throws InterruptedException { server = new ApnsServerStub(FixedCertificates.serverContext().getServerSocketFactory()); //5 success 1 fail 7 success 7 resent final CountDownLatch sync = new CountDownLatch(20); final AtomicInteger numResent = new AtomicInteger(); final AtomicInteger numSent = new AtomicInteger(); final AtomicInteger numStartSend = new AtomicInteger(); int EXPECTED_RESEND_COUNT = 7; int EXPECTED_SEND_COUNT = 12; server.getWaitForError().acquire(); server.start(); ApnsService service = APNS.newService().withSSLContext(clientContext()) .withGatewayDestination(LOCALHOST, server.getEffectiveGatewayPort()) .withDelegate(new StartSendingApnsDelegate() { public void startSending(final ApnsNotification message, final boolean resent) { if (!resent) { numStartSend.incrementAndGet(); } } public void messageSent(ApnsNotification message, boolean resent) { if (!resent) { numSent.incrementAndGet(); } sync.countDown(); } public void messageSendFailed(ApnsNotification message, Throwable e) { numSent.decrementAndGet(); } public void connectionClosed(DeliveryError e, int messageIdentifier) { } public void cacheLengthExceeded(int newCacheLength) { } public void notificationsResent(int resendCount) { numResent.set(resendCount); } }) .build(); server.stopAt(eMsg1.length() * 5 + eMsg2.length() + eMsg3.length() * 14); for (int i = 0; i < 5; ++i) { service.push(eMsg1); } service.push(eMsg2); for (int i = 0; i < 7; ++i) { service.push(eMsg3); } server.sendError(8, eMsg2.getIdentifier()); server.getWaitForError().release(); server.getMessages().acquire(); sync.await(); Assert.assertEquals(EXPECTED_RESEND_COUNT, numResent.get()); Assert.assertEquals(EXPECTED_SEND_COUNT, numSent.get()); Assert.assertEquals(EXPECTED_SEND_COUNT + 1, numStartSend.get()); } /** * Test2 to make sure that after rejected notification * in-flight notifications are re-transmitted * * @throws InterruptedException */ @Test(timeout = 5000) public void handleReTransmissionError1Good1Bad2Good() throws InterruptedException { server = new ApnsServerStub(FixedCertificates.serverContext().getServerSocketFactory()); final CountDownLatch sync = new CountDownLatch(6); final AtomicInteger numResent = new AtomicInteger(); final AtomicInteger numSent = new AtomicInteger(); final AtomicInteger numStartSend = new AtomicInteger(); int EXPECTED_RESEND_COUNT = 2; int EXPECTED_SEND_COUNT = 3; server.getWaitForError().acquire(); server.start(); ApnsService service = APNS.newService().withSSLContext(clientContext()) .withGatewayDestination(LOCALHOST, server.getEffectiveGatewayPort()) .withDelegate(new StartSendingApnsDelegate() { public void startSending(final ApnsNotification message, final boolean resent) { if (!resent) { numStartSend.incrementAndGet(); } } public void messageSent(ApnsNotification message, boolean resent) { if (!resent) { numSent.incrementAndGet(); } sync.countDown(); } public void messageSendFailed(ApnsNotification message, Throwable e) { numSent.decrementAndGet(); } public void connectionClosed(DeliveryError e, int messageIdentifier) { } public void cacheLengthExceeded(int newCacheLength) { } public void notificationsResent(int resendCount) { numResent.set(resendCount); } }) .build(); server.stopAt(msg1.length() * 3 + eMsg2.length() * 2); service.push(msg1); service.push(eMsg2); service.push(eMsg1); service.push(msg2); server.sendError(8, eMsg2.getIdentifier()); server.getWaitForError().release(); server.getMessages().acquire(); sync.await(); Assert.assertEquals(EXPECTED_RESEND_COUNT, numResent.get()); Assert.assertEquals(EXPECTED_SEND_COUNT, numSent.get()); Assert.assertEquals(EXPECTED_SEND_COUNT + 1, numStartSend.get()); } /** * Test to make sure single rejected notifications are returned * * @throws InterruptedException */ @Test(timeout = 5000) public void handleReTransmissionError1Bad() throws InterruptedException { server = new ApnsServerStub(FixedCertificates.serverContext().getServerSocketFactory()); final CountDownLatch sync = new CountDownLatch(1); final AtomicInteger numError = new AtomicInteger(); final AtomicInteger numStartSend = new AtomicInteger(); int EXPECTED_ERROR_COUNT = 1; server.getWaitForError().acquire(); server.start(); ApnsService service = APNS.newService().withSSLContext(clientContext()) .withGatewayDestination(LOCALHOST, server.getEffectiveGatewayPort()) .withDelegate(new StartSendingApnsDelegate() { public void startSending(final ApnsNotification message, final boolean resent) { if (!resent) { numStartSend.incrementAndGet(); } } public void messageSent(ApnsNotification message, boolean resent) { } public void messageSendFailed(ApnsNotification message, Throwable e) { numError.incrementAndGet(); sync.countDown(); } public void connectionClosed(DeliveryError e, int messageIdentifier) { } public void cacheLengthExceeded(int newCacheLength) { } public void notificationsResent(int resendCount) { } }) .build(); server.stopAt(eMsg1.length()); service.push(eMsg1); server.sendError(8, eMsg1.getIdentifier()); server.getWaitForError().release(); server.getMessages().acquire(); sync.await(); Assert.assertEquals(EXPECTED_ERROR_COUNT, numError.get()); Assert.assertEquals(EXPECTED_ERROR_COUNT, numStartSend.get()); } /** * Test to make sure that after rejected notification * in-flight notifications are re-transmitted with a queued connection * * @throws InterruptedException */ @Ignore("Fails because old ApnsServerStub does not accept() on the connection socket for all the time.") @Test(timeout = 10000) public void handleTransmissionErrorInQueuedConnection() throws InterruptedException { server = new ApnsServerStub(FixedCertificates.serverContext().getServerSocketFactory()); final AtomicInteger sync = new AtomicInteger(138); final AtomicInteger numResent = new AtomicInteger(); final AtomicInteger numSent = new AtomicInteger(); server.getWaitForError().acquire(); server.start(); ApnsService service = APNS.newService().withSSLContext(clientContext()) .withGatewayDestination(LOCALHOST, server.getEffectiveGatewayPort()) .asQueued() .withDelegate(new ApnsDelegate() { public void messageSent(ApnsNotification message, boolean resent) { if (!resent) { numSent.incrementAndGet(); } sync.getAndDecrement(); } public void messageSendFailed(ApnsNotification message, Throwable e) { numSent.decrementAndGet(); sync.incrementAndGet(); } public void connectionClosed(DeliveryError e, int messageIdentifier) { } public void cacheLengthExceeded(int newCacheLength) { } public void notificationsResent(int resendCount) { numResent.set(resendCount); sync.getAndAdd(resendCount); } }) .build(); server.stopAt(eMsg3.length() * 50 + msg1.length() * 3 + eMsg2.length() * 2 + eMsg1.length() * 85); for (int i = 0; i < 50; ++i) { service.push(eMsg3); } service.push(msg1); service.push(eMsg2); service.push(eMsg1); service.push(msg2); for (int i = 0; i < 85; ++i) { service.push(eMsg1); } server.sendError(8, eMsg2.getIdentifier()); server.getWaitForError().release(); server.getMessages().acquire(); while(sync.get() != 0) { Thread.yield(); } } /** * Test to make sure that if the cache length is violated we get * a notification * * @throws InterruptedException */ @Test(timeout = 5000) public void cacheLengthNotification() throws InterruptedException { server = new ApnsServerStub(FixedCertificates.serverContext().getServerSocketFactory()); final CountDownLatch sync = new CountDownLatch(1); int ORIGINAL_CACHE_LENGTH = 100; final AtomicInteger modifiedCacheLength = new AtomicInteger(); server.getWaitForError().acquire(); server.start(); ApnsService service = APNS.newService().withSSLContext(clientContext()) .withGatewayDestination(LOCALHOST, server.getEffectiveGatewayPort()) .withDelegate(new ApnsDelegate() { public void messageSent(ApnsNotification message, boolean resent) { } public void messageSendFailed(ApnsNotification message, Throwable e) { } public void connectionClosed(DeliveryError e, int messageIdentifier) { } public void cacheLengthExceeded(int newCacheLength) { modifiedCacheLength.set(newCacheLength); sync.countDown(); } public void notificationsResent(int resendCount) { } }) .build(); server.stopAt(eMsg1.length() * 5 + eMsg2.length() + eMsg3.length() * 14); for (int i = 0; i < 5; ++i) { service.push(eMsg1); } service.push(eMsg2); for (int i = 0; i < 101; ++i) { service.push(eMsg3); } server.sendError(8, eMsg2.getIdentifier()); server.getWaitForError().release(); server.getMessages().acquire(); sync.await(); Assert.assertTrue(ORIGINAL_CACHE_LENGTH < modifiedCacheLength.get()); } }